1010//
1111// ===----------------------------------------------------------------------===//
1212
13+ #include " clang/AST/ASTContext.h"
14+ #include " clang/Basic/TargetInfo.h"
1315#include " clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1416#include " clang/StaticAnalyzer/Core/BugReporter/BugType.h"
1517#include " clang/StaticAnalyzer/Core/Checker.h"
2426using namespace clang ;
2527using namespace ento ;
2628
27- namespace {
28-
2929// 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 };
3131
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 {
3441
3542// This checker checks improper use of chroot.
36- // The state transition:
43+ // The state transitions
44+ //
45+ // -> ROOT_CHANGE_FAILED
46+ // |
3747// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
3848// | |
3949// ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
4050// | |
4151// bug<--foo()-- JAIL_ENTERED<--foo()--
52+ //
4253class ChrootChecker : public Checker <eval::Call, check::PreCall> {
4354 // This bug refers to possibly break out of a chroot() jail.
4455 const BugType BT_BreakJail{this , " Break out of jail" };
@@ -49,20 +60,17 @@ class ChrootChecker : public Checker<eval::Call, check::PreCall> {
4960public:
5061 ChrootChecker () {}
5162
52- static void *getTag () {
53- static int x;
54- return &x;
55- }
56-
5763 bool evalCall (const CallEvent &Call, CheckerContext &C) const ;
5864 void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
5965
6066private:
6167 void evalChroot (const CallEvent &Call, CheckerContext &C) const ;
6268 void evalChdir (const CallEvent &Call, CheckerContext &C) const ;
63- };
6469
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+ };
6674
6775bool ChrootChecker::evalCall (const CallEvent &Call, CheckerContext &C) const {
6876 if (Chroot.matches (Call)) {
@@ -80,19 +88,53 @@ bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
8088void ChrootChecker::evalChroot (const CallEvent &Call, CheckerContext &C) const {
8189 ProgramStateRef state = C.getState ();
8290 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 ();
8395
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+ }
88131}
89132
90133void ChrootChecker::evalChdir (const CallEvent &Call, CheckerContext &C) const {
91134 ProgramStateRef state = C.getState ();
92- ProgramStateManager &Mgr = state->getStateManager ();
93135
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>( );
96138 if (!k)
97139 return ;
98140
@@ -104,15 +146,35 @@ void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
104146 R = R->StripCasts ();
105147 if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
106148 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+ }
110152 }
111153 }
112154
113155 C.addTransition (state);
114156}
115157
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+
116178// Check the jail state before any function call except chroot and chdir().
117179void ChrootChecker::checkPreCall (const CallEvent &Call,
118180 CheckerContext &C) const {
@@ -121,17 +183,40 @@ void ChrootChecker::checkPreCall(const CallEvent &Call,
121183 return ;
122184
123185 // 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+ }
133216}
134217
218+ } // namespace
219+
135220void ento::registerChrootChecker (CheckerManager &mgr) {
136221 mgr.registerChecker <ChrootChecker>();
137222}
0 commit comments