10
10
//
11
11
// ===----------------------------------------------------------------------===//
12
12
13
+ #include " clang/AST/ASTContext.h"
14
+ #include " clang/Basic/TargetInfo.h"
13
15
#include " clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14
16
#include " clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15
17
#include " clang/StaticAnalyzer/Core/Checker.h"
24
26
using namespace clang ;
25
27
using namespace ento ;
26
28
27
- namespace {
28
-
29
29
// enum value that represent the jail state
30
- enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
30
+ enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED , JAIL_ENTERED };
31
31
32
- bool isRootChanged (intptr_t k) { return k == ROOT_CHANGED; }
33
- // bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
32
+ // Track chroot state changes for success, failure, state change
33
+ // and "jail"
34
+ REGISTER_TRAIT_WITH_PROGRAMSTATE (ChrootState, ChrootKind)
35
+
36
+ // Track the call expression to chroot for accurate
37
+ // warning messages
38
+ REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootCall, const Expr *)
39
+
40
+ namespace {
34
41
35
42
// This checker checks improper use of chroot.
36
- // The state transition:
43
+ // The state transitions
44
+ //
45
+ // -> ROOT_CHANGE_FAILED
46
+ // |
37
47
// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38
48
// | |
39
49
// ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
40
50
// | |
41
51
// bug<--foo()-- JAIL_ENTERED<--foo()--
52
+ //
42
53
class ChrootChecker : public Checker <eval::Call, check::PreCall> {
43
54
// This bug refers to possibly break out of a chroot() jail.
44
55
const BugType BT_BreakJail{this , " Break out of jail" };
@@ -49,20 +60,17 @@ class ChrootChecker : public Checker<eval::Call, check::PreCall> {
49
60
public:
50
61
ChrootChecker () {}
51
62
52
- static void *getTag () {
53
- static int x;
54
- return &x;
55
- }
56
-
57
63
bool evalCall (const CallEvent &Call, CheckerContext &C) const ;
58
64
void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
59
65
60
66
private:
61
67
void evalChroot (const CallEvent &Call, CheckerContext &C) const ;
62
68
void evalChdir (const CallEvent &Call, CheckerContext &C) const ;
63
- };
64
69
65
- } // end anonymous namespace
70
+ // / Searches for the ExplodedNode where chroot was called.
71
+ static const ExplodedNode *getAcquisitionSite (const ExplodedNode *N,
72
+ CheckerContext &C);
73
+ };
66
74
67
75
bool ChrootChecker::evalCall (const CallEvent &Call, CheckerContext &C) const {
68
76
if (Chroot.matches (Call)) {
@@ -80,19 +88,53 @@ bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
80
88
void ChrootChecker::evalChroot (const CallEvent &Call, CheckerContext &C) const {
81
89
ProgramStateRef state = C.getState ();
82
90
ProgramStateManager &Mgr = state->getStateManager ();
91
+ const TargetInfo &TI = C.getASTContext ().getTargetInfo ();
92
+ SValBuilder &SVB = C.getSValBuilder ();
93
+ BasicValueFactory &BVF = SVB.getBasicValueFactory ();
94
+ ConstraintManager &CM = Mgr.getConstraintManager ();
83
95
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);
96
+ const QualType sIntTy = C.getASTContext ().getIntTypeForBitwidth (
97
+ /* DestWidth=*/ TI.getIntWidth (), /* Signed=*/ true );
98
+
99
+ const Expr *ChrootCE = Call.getOriginExpr ();
100
+ if (!ChrootCE)
101
+ return ;
102
+ const auto *CE = cast<CallExpr>(Call.getOriginExpr ());
103
+
104
+ const LocationContext *LCtx = C.getLocationContext ();
105
+ NonLoc RetVal =
106
+ C.getSValBuilder ()
107
+ .conjureSymbolVal (nullptr , ChrootCE, LCtx, sIntTy , C.blockCount ())
108
+ .castAs <NonLoc>();
109
+
110
+ ProgramStateRef StateChrootFailed, StateChrootSuccess;
111
+ std::tie (StateChrootFailed, StateChrootSuccess) = state->assume (RetVal);
112
+
113
+ const llvm::APSInt &Zero = BVF.getValue (0 , sIntTy );
114
+ const llvm::APSInt &Minus1 = BVF.getValue (-1 , sIntTy );
115
+
116
+ if (StateChrootFailed) {
117
+ StateChrootFailed = CM.assumeInclusiveRange (StateChrootFailed, RetVal,
118
+ Minus1, Minus1, true );
119
+ StateChrootFailed = StateChrootFailed->set <ChrootState>(ROOT_CHANGE_FAILED);
120
+ StateChrootFailed = StateChrootFailed->set <ChrootCall>(ChrootCE);
121
+ C.addTransition (StateChrootFailed->BindExpr (CE, LCtx, RetVal));
122
+ }
123
+
124
+ if (StateChrootSuccess) {
125
+ StateChrootSuccess =
126
+ CM.assumeInclusiveRange (StateChrootSuccess, RetVal, Zero, Zero, true );
127
+ StateChrootSuccess = StateChrootSuccess->set <ChrootState>(ROOT_CHANGED);
128
+ StateChrootSuccess = StateChrootSuccess->set <ChrootCall>(ChrootCE);
129
+ C.addTransition (StateChrootSuccess->BindExpr (CE, LCtx, RetVal));
130
+ }
88
131
}
89
132
90
133
void ChrootChecker::evalChdir (const CallEvent &Call, CheckerContext &C) const {
91
134
ProgramStateRef state = C.getState ();
92
- ProgramStateManager &Mgr = state->getStateManager ();
93
135
94
- // If there are no jail state in the GDM , just return.
95
- const void * k = state-> FindGDM ( ChrootChecker::getTag () );
136
+ // If there are no jail state, just return.
137
+ const ChrootKind k = C. getState ()-> get <ChrootState>( );
96
138
if (!k)
97
139
return ;
98
140
@@ -104,15 +146,35 @@ void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
104
146
R = R->StripCasts ();
105
147
if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
106
148
const StringLiteral* Str = StrRegion->getStringLiteral ();
107
- if (Str->getString () == " /" )
108
- state = Mgr. addGDM ( state, ChrootChecker::getTag (),
109
- ( void *) JAIL_ENTERED);
149
+ if (Str->getString () == " /" ) {
150
+ state = state-> set <ChrootState>(JAIL_ENTERED);
151
+ }
110
152
}
111
153
}
112
154
113
155
C.addTransition (state);
114
156
}
115
157
158
+ const ExplodedNode *ChrootChecker::getAcquisitionSite (const ExplodedNode *N,
159
+ CheckerContext &C) {
160
+ ProgramStateRef State = N->getState ();
161
+ // When bug type is resource leak, exploded node N may not have state info
162
+ // for leaked file descriptor, but predecessor should have it.
163
+ if (!State->get <ChrootCall>())
164
+ N = N->getFirstPred ();
165
+
166
+ const ExplodedNode *Pred = N;
167
+ while (N) {
168
+ State = N->getState ();
169
+ if (!State->get <ChrootCall>())
170
+ return Pred;
171
+ Pred = N;
172
+ N = N->getFirstPred ();
173
+ }
174
+
175
+ return nullptr ;
176
+ }
177
+
116
178
// Check the jail state before any function call except chroot and chdir().
117
179
void ChrootChecker::checkPreCall (const CallEvent &Call,
118
180
CheckerContext &C) const {
@@ -121,17 +183,40 @@ void ChrootChecker::checkPreCall(const CallEvent &Call,
121
183
return ;
122
184
123
185
// 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
- }
186
+ const ChrootKind k = C.getState ()->get <ChrootState>();
187
+ if (k == ROOT_CHANGED) {
188
+ ExplodedNode *Err =
189
+ C.generateNonFatalErrorNode (C.getState (), C.getPredecessor ());
190
+ if (!Err)
191
+ return ;
192
+ const Expr *ChrootExpr = C.getState ()->get <ChrootCall>();
193
+
194
+ const ExplodedNode *ChrootCallNode = getAcquisitionSite (Err, C);
195
+ assert (ChrootCallNode && " Could not find place of stream opening." );
196
+
197
+ PathDiagnosticLocation LocUsedForUniqueing;
198
+ if (const Stmt *ChrootStmt = ChrootCallNode->getStmtForDiagnostics ())
199
+ LocUsedForUniqueing = PathDiagnosticLocation::createBegin (
200
+ ChrootStmt, C.getSourceManager (),
201
+ ChrootCallNode->getLocationContext ());
202
+
203
+ std::unique_ptr<PathSensitiveBugReport> R =
204
+ std::make_unique<PathSensitiveBugReport>(
205
+ BT_BreakJail, " No call of chdir(\" /\" ) immediately after chroot" ,
206
+ Err, LocUsedForUniqueing,
207
+ ChrootCallNode->getLocationContext ()->getDecl ());
208
+
209
+ R->addNote (" chroot called here" ,
210
+ PathDiagnosticLocation::create (ChrootCallNode->getLocation (),
211
+ C.getSourceManager ()),
212
+ {ChrootExpr->getSourceRange ()});
213
+
214
+ C.emitReport (std::move (R));
215
+ }
133
216
}
134
217
218
+ } // namespace
219
+
135
220
void ento::registerChrootChecker (CheckerManager &mgr) {
136
221
mgr.registerChecker <ChrootChecker>();
137
222
}
0 commit comments