xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick //  This file defines chroot checker, which checks improper use of chroot.
10e5dd7070Spatrick //
11e5dd7070Spatrick //===----------------------------------------------------------------------===//
12e5dd7070Spatrick 
13e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h"
16e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17*12c85518Srobert #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
21e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
22e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
23e5dd7070Spatrick 
24e5dd7070Spatrick using namespace clang;
25e5dd7070Spatrick using namespace ento;
26e5dd7070Spatrick 
27e5dd7070Spatrick namespace {
28e5dd7070Spatrick 
29e5dd7070Spatrick // enum value that represent the jail state
30e5dd7070Spatrick enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
31e5dd7070Spatrick 
isRootChanged(intptr_t k)32e5dd7070Spatrick bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
33e5dd7070Spatrick //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
34e5dd7070Spatrick 
35e5dd7070Spatrick // This checker checks improper use of chroot.
36e5dd7070Spatrick // The state transition:
37e5dd7070Spatrick // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38e5dd7070Spatrick //                                  |                               |
39e5dd7070Spatrick //         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)--
40e5dd7070Spatrick //                                  |                               |
41e5dd7070Spatrick //                      bug<--foo()--          JAIL_ENTERED<--foo()--
42e5dd7070Spatrick class ChrootChecker : public Checker<eval::Call, check::PreCall> {
43e5dd7070Spatrick   // This bug refers to possibly break out of a chroot() jail.
44e5dd7070Spatrick   mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
45e5dd7070Spatrick 
46*12c85518Srobert   const CallDescription Chroot{{"chroot"}, 1}, Chdir{{"chdir"}, 1};
47e5dd7070Spatrick 
48e5dd7070Spatrick public:
ChrootChecker()49e5dd7070Spatrick   ChrootChecker() {}
50e5dd7070Spatrick 
getTag()51e5dd7070Spatrick   static void *getTag() {
52e5dd7070Spatrick     static int x;
53e5dd7070Spatrick     return &x;
54e5dd7070Spatrick   }
55e5dd7070Spatrick 
56e5dd7070Spatrick   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
57e5dd7070Spatrick   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
58e5dd7070Spatrick 
59e5dd7070Spatrick private:
60e5dd7070Spatrick   void evalChroot(const CallEvent &Call, CheckerContext &C) const;
61e5dd7070Spatrick   void evalChdir(const CallEvent &Call, CheckerContext &C) const;
62e5dd7070Spatrick };
63e5dd7070Spatrick 
64e5dd7070Spatrick } // end anonymous namespace
65e5dd7070Spatrick 
evalCall(const CallEvent & Call,CheckerContext & C) const66e5dd7070Spatrick bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
67*12c85518Srobert   if (Chroot.matches(Call)) {
68e5dd7070Spatrick     evalChroot(Call, C);
69e5dd7070Spatrick     return true;
70e5dd7070Spatrick   }
71*12c85518Srobert   if (Chdir.matches(Call)) {
72e5dd7070Spatrick     evalChdir(Call, C);
73e5dd7070Spatrick     return true;
74e5dd7070Spatrick   }
75e5dd7070Spatrick 
76e5dd7070Spatrick   return false;
77e5dd7070Spatrick }
78e5dd7070Spatrick 
evalChroot(const CallEvent & Call,CheckerContext & C) const79e5dd7070Spatrick void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
80e5dd7070Spatrick   ProgramStateRef state = C.getState();
81e5dd7070Spatrick   ProgramStateManager &Mgr = state->getStateManager();
82e5dd7070Spatrick 
83e5dd7070Spatrick   // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
84e5dd7070Spatrick   // the GDM.
85e5dd7070Spatrick   state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
86e5dd7070Spatrick   C.addTransition(state);
87e5dd7070Spatrick }
88e5dd7070Spatrick 
evalChdir(const CallEvent & Call,CheckerContext & C) const89e5dd7070Spatrick void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
90e5dd7070Spatrick   ProgramStateRef state = C.getState();
91e5dd7070Spatrick   ProgramStateManager &Mgr = state->getStateManager();
92e5dd7070Spatrick 
93e5dd7070Spatrick   // If there are no jail state in the GDM, just return.
94e5dd7070Spatrick   const void *k = state->FindGDM(ChrootChecker::getTag());
95e5dd7070Spatrick   if (!k)
96e5dd7070Spatrick     return;
97e5dd7070Spatrick 
98e5dd7070Spatrick   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
99e5dd7070Spatrick   const Expr *ArgExpr = Call.getArgExpr(0);
100e5dd7070Spatrick   SVal ArgVal = C.getSVal(ArgExpr);
101e5dd7070Spatrick 
102e5dd7070Spatrick   if (const MemRegion *R = ArgVal.getAsRegion()) {
103e5dd7070Spatrick     R = R->StripCasts();
104e5dd7070Spatrick     if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
105e5dd7070Spatrick       const StringLiteral* Str = StrRegion->getStringLiteral();
106e5dd7070Spatrick       if (Str->getString() == "/")
107e5dd7070Spatrick         state = Mgr.addGDM(state, ChrootChecker::getTag(),
108e5dd7070Spatrick                            (void*) JAIL_ENTERED);
109e5dd7070Spatrick     }
110e5dd7070Spatrick   }
111e5dd7070Spatrick 
112e5dd7070Spatrick   C.addTransition(state);
113e5dd7070Spatrick }
114e5dd7070Spatrick 
115e5dd7070Spatrick // Check the jail state before any function call except chroot and chdir().
checkPreCall(const CallEvent & Call,CheckerContext & C) const116e5dd7070Spatrick void ChrootChecker::checkPreCall(const CallEvent &Call,
117e5dd7070Spatrick                                  CheckerContext &C) const {
118e5dd7070Spatrick   // Ignore chroot and chdir.
119*12c85518Srobert   if (matchesAny(Call, Chroot, Chdir))
120e5dd7070Spatrick     return;
121e5dd7070Spatrick 
122e5dd7070Spatrick   // If jail state is ROOT_CHANGED, generate BugReport.
123e5dd7070Spatrick   void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
124e5dd7070Spatrick   if (k)
125e5dd7070Spatrick     if (isRootChanged((intptr_t) *k))
126e5dd7070Spatrick       if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
127e5dd7070Spatrick         if (!BT_BreakJail)
128e5dd7070Spatrick           BT_BreakJail.reset(new BuiltinBug(
129e5dd7070Spatrick               this, "Break out of jail", "No call of chdir(\"/\") immediately "
130e5dd7070Spatrick                                          "after chroot"));
131e5dd7070Spatrick         C.emitReport(std::make_unique<PathSensitiveBugReport>(
132e5dd7070Spatrick             *BT_BreakJail, BT_BreakJail->getDescription(), N));
133e5dd7070Spatrick       }
134e5dd7070Spatrick }
135e5dd7070Spatrick 
registerChrootChecker(CheckerManager & mgr)136e5dd7070Spatrick void ento::registerChrootChecker(CheckerManager &mgr) {
137e5dd7070Spatrick   mgr.registerChecker<ChrootChecker>();
138e5dd7070Spatrick }
139e5dd7070Spatrick 
shouldRegisterChrootChecker(const CheckerManager & mgr)140ec727ea7Spatrick bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) {
141e5dd7070Spatrick   return true;
142e5dd7070Spatrick }
143