7
7
// ===----------------------------------------------------------------------===//
8
8
//
9
9
// This file defines chroot checker, which checks improper use of chroot.
10
+ // This is described by the SEI Cert C rule POS05-C.
11
+ // The checker is a warning not a hard failure since it only checks for a
12
+ // recommended rule.
10
13
//
11
14
// ===----------------------------------------------------------------------===//
12
15
16
+ #include " clang/AST/ASTContext.h"
13
17
#include " clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14
18
#include " clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15
19
#include " clang/StaticAnalyzer/Core/Checker.h"
@@ -25,117 +29,155 @@ using namespace clang;
25
29
using namespace ento ;
26
30
27
31
namespace {
32
+ enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };
33
+ } // namespace
28
34
29
- // enum value that represent the jail state
30
- enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
31
-
32
- bool isRootChanged (intptr_t k) { return k == ROOT_CHANGED; }
33
- // bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
35
+ // Track chroot state changes for success, failure, state change
36
+ // and "jail"
37
+ REGISTER_TRAIT_WITH_PROGRAMSTATE (ChrootState, ChrootKind)
38
+ namespace {
34
39
35
40
// This checker checks improper use of chroot.
36
- // The state transition:
41
+ // The state transitions
42
+ //
43
+ // -> ROOT_CHANGE_FAILED
44
+ // |
37
45
// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38
46
// | |
39
47
// ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
40
48
// | |
41
49
// bug<--foo()-- JAIL_ENTERED<--foo()--
42
- class ChrootChecker : public Checker <eval::Call, check::PreCall> {
43
- // This bug refers to possibly break out of a chroot() jail.
44
- const BugType BT_BreakJail{this , " Break out of jail" };
45
-
46
- const CallDescription Chroot{CDM::CLibrary, {" chroot" }, 1 },
47
- Chdir{CDM::CLibrary, {" chdir" }, 1 };
48
-
50
+ //
51
+ class ChrootChecker final : public Checker<eval::Call, check::PreCall> {
49
52
public:
50
- ChrootChecker () {}
51
-
52
- static void *getTag () {
53
- static int x;
54
- return &x;
55
- }
56
-
57
53
bool evalCall (const CallEvent &Call, CheckerContext &C) const ;
58
54
void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
59
55
60
56
private:
61
- void evalChroot (const CallEvent &Call, CheckerContext &C) const ;
62
- void evalChdir (const CallEvent &Call, CheckerContext &C) const ;
63
- };
57
+ bool evalChroot (const CallEvent &Call, CheckerContext &C) const ;
58
+ bool evalChdir (const CallEvent &Call, CheckerContext &C) const ;
64
59
65
- } // end anonymous namespace
60
+ const BugType BreakJailBug{this , " Break out of jail" };
61
+ const CallDescription Chroot{CDM::CLibrary, {" chroot" }, 1 };
62
+ const CallDescription Chdir{CDM::CLibrary, {" chdir" }, 1 };
63
+ };
66
64
67
65
bool ChrootChecker::evalCall (const CallEvent &Call, CheckerContext &C) const {
68
- if (Chroot.matches (Call)) {
69
- evalChroot (Call, C);
70
- return true ;
71
- }
72
- if (Chdir.matches (Call)) {
73
- evalChdir (Call, C);
74
- return true ;
75
- }
66
+ if (Chroot.matches (Call))
67
+ return evalChroot (Call, C);
68
+
69
+ if (Chdir.matches (Call))
70
+ return evalChdir (Call, C);
76
71
77
72
return false ;
78
73
}
79
74
80
- void ChrootChecker::evalChroot (const CallEvent &Call, CheckerContext &C) const {
81
- ProgramStateRef state = C.getState ();
82
- ProgramStateManager &Mgr = state->getStateManager ();
75
+ bool ChrootChecker::evalChroot (const CallEvent &Call, CheckerContext &C) const {
76
+ BasicValueFactory &BVF = C.getSValBuilder ().getBasicValueFactory ();
77
+ const LocationContext *LCtx = C.getLocationContext ();
78
+ ProgramStateRef State = C.getState ();
79
+ const auto *CE = cast<CallExpr>(Call.getOriginExpr ());
80
+
81
+ const QualType IntTy = C.getASTContext ().IntTy ;
82
+ SVal Zero = nonloc::ConcreteInt{BVF.getValue (0 , IntTy)};
83
+ SVal Minus1 = nonloc::ConcreteInt{BVF.getValue (-1 , IntTy)};
83
84
84
- // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
85
- // the GDM.
86
- state = Mgr.addGDM (state, ChrootChecker::getTag (), (void *) ROOT_CHANGED);
87
- C.addTransition (state);
85
+ ProgramStateRef ChrootFailed = State->BindExpr (CE, LCtx, Minus1);
86
+ C.addTransition (ChrootFailed->set <ChrootState>(ROOT_CHANGE_FAILED));
87
+
88
+ ProgramStateRef ChrootSucceeded = State->BindExpr (CE, LCtx, Zero);
89
+ C.addTransition (ChrootSucceeded->set <ChrootState>(ROOT_CHANGED));
90
+ return true ;
88
91
}
89
92
90
- void ChrootChecker::evalChdir (const CallEvent &Call, CheckerContext &C) const {
91
- ProgramStateRef state = C.getState ();
92
- ProgramStateManager &Mgr = state->getStateManager ();
93
+ bool ChrootChecker::evalChdir (const CallEvent &Call, CheckerContext &C) const {
94
+ ProgramStateRef State = C.getState ();
93
95
94
- // If there are no jail state in the GDM, just return.
95
- const void *k = state->FindGDM (ChrootChecker::getTag ());
96
- if (!k)
97
- return ;
96
+ // If there are no jail state, just return.
97
+ if (State->get <ChrootState>() == NO_CHROOT)
98
+ return false ;
98
99
99
100
// After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
100
- const Expr *ArgExpr = Call.getArgExpr (0 );
101
- SVal ArgVal = C.getSVal (ArgExpr);
101
+ SVal ArgVal = Call.getArgSVal (0 );
102
102
103
103
if (const MemRegion *R = ArgVal.getAsRegion ()) {
104
104
R = R->StripCasts ();
105
- if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
106
- const StringLiteral* Str = StrRegion->getStringLiteral ();
107
- if (Str-> getString () == " / " )
108
- state = Mgr. addGDM (state, ChrootChecker::getTag (),
109
- ( void *) JAIL_ENTERED);
105
+ if (const auto * StrRegion = dyn_cast<StringRegion>(R)) {
106
+ if ( StrRegion->getStringLiteral ()-> getString () == " / " ) {
107
+ C. addTransition (State-> set <ChrootState>(JAIL_ENTERED));
108
+ return true ;
109
+ }
110
110
}
111
111
}
112
-
113
- C.addTransition (state);
112
+ return false ;
114
113
}
115
114
115
+ class ChrootInvocationVisitor final : public BugReporterVisitor {
116
+ public:
117
+ explicit ChrootInvocationVisitor (const CallDescription &Chroot)
118
+ : Chroot{Chroot} {}
119
+
120
+ PathDiagnosticPieceRef VisitNode (const ExplodedNode *N,
121
+ BugReporterContext &BRC,
122
+ PathSensitiveBugReport &BR) override {
123
+ if (Satisfied)
124
+ return nullptr ;
125
+
126
+ auto StmtP = N->getLocation ().getAs <StmtPoint>();
127
+ if (!StmtP)
128
+ return nullptr ;
129
+
130
+ const CallExpr *Call = StmtP->getStmtAs <CallExpr>();
131
+ if (!Call)
132
+ return nullptr ;
133
+
134
+ if (!Chroot.matchesAsWritten (*Call))
135
+ return nullptr ;
136
+
137
+ Satisfied = true ;
138
+ PathDiagnosticLocation Pos (Call, BRC.getSourceManager (),
139
+ N->getLocationContext ());
140
+ return std::make_shared<PathDiagnosticEventPiece>(Pos, " chroot called here" ,
141
+ /* addPosRange=*/ true );
142
+ }
143
+
144
+ void Profile (llvm::FoldingSetNodeID &ID) const override {
145
+ static bool Tag;
146
+ ID.AddPointer (&Tag);
147
+ }
148
+
149
+ private:
150
+ const CallDescription &Chroot;
151
+ bool Satisfied = false ;
152
+ };
153
+
116
154
// Check the jail state before any function call except chroot and chdir().
117
155
void ChrootChecker::checkPreCall (const CallEvent &Call,
118
156
CheckerContext &C) const {
119
157
// Ignore chroot and chdir.
120
158
if (matchesAny (Call, Chroot, Chdir))
121
159
return ;
122
160
123
- // If jail state is ROOT_CHANGED, generate BugReport.
124
- void *const * k = C.getState ()->FindGDM (ChrootChecker::getTag ());
125
- if (k)
126
- if (isRootChanged ((intptr_t ) *k))
127
- if (ExplodedNode *N = C.generateNonFatalErrorNode ()) {
128
- constexpr llvm::StringLiteral Msg =
129
- " No call of chdir(\" /\" ) immediately after chroot" ;
130
- C.emitReport (
131
- std::make_unique<PathSensitiveBugReport>(BT_BreakJail, Msg, N));
132
- }
133
- }
161
+ // If jail state is not ROOT_CHANGED just return.
162
+ if (C.getState ()->get <ChrootState>() != ROOT_CHANGED)
163
+ return ;
134
164
135
- void ento::registerChrootChecker (CheckerManager &mgr) {
136
- mgr.registerChecker <ChrootChecker>();
165
+ // Generate bug report.
166
+ ExplodedNode *Err =
167
+ C.generateNonFatalErrorNode (C.getState (), C.getPredecessor ());
168
+ if (!Err)
169
+ return ;
170
+
171
+ auto R = std::make_unique<PathSensitiveBugReport>(
172
+ BreakJailBug, R"( No call of chdir("/") immediately after chroot)" , Err);
173
+ R->addVisitor <ChrootInvocationVisitor>(Chroot);
174
+ C.emitReport (std::move (R));
137
175
}
138
176
139
- bool ento::shouldRegisterChrootChecker (const CheckerManager &mgr) {
140
- return true ;
177
+ } // namespace
178
+
179
+ void ento::registerChrootChecker (CheckerManager &Mgr) {
180
+ Mgr.registerChecker <ChrootChecker>();
141
181
}
182
+
183
+ bool ento::shouldRegisterChrootChecker (const CheckerManager &) { return true ; }
0 commit comments