1 //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 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. 13 // 14 //===----------------------------------------------------------------------===// 15 16 #include "clang/AST/ASTContext.h" 17 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19 #include "clang/StaticAnalyzer/Core/Checker.h" 20 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 24 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 25 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 26 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 27 28 using namespace clang; 29 using namespace ento; 30 31 namespace { 32 enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED }; 33 } // namespace 34 35 // Track chroot state changes for success, failure, state change 36 // and "jail" 37 REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind) 38 namespace { 39 40 // This checker checks improper use of chroot. 41 // The state transitions 42 // 43 // -> ROOT_CHANGE_FAILED 44 // | 45 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED 46 // | | 47 // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)-- 48 // | | 49 // bug<--foo()-- JAIL_ENTERED<--foo()-- 50 // 51 class ChrootChecker final : public Checker<eval::Call, check::PreCall> { 52 public: 53 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 54 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 55 56 private: 57 bool evalChroot(const CallEvent &Call, CheckerContext &C) const; 58 bool evalChdir(const CallEvent &Call, CheckerContext &C) const; 59 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 }; 64 65 bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 66 if (Chroot.matches(Call)) 67 return evalChroot(Call, C); 68 69 if (Chdir.matches(Call)) 70 return evalChdir(Call, C); 71 72 return false; 73 } 74 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)}; 84 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; 91 } 92 93 bool ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { 94 ProgramStateRef State = C.getState(); 95 96 // If there are no jail state, just return. 97 if (State->get<ChrootState>() == NO_CHROOT) 98 return false; 99 100 // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. 101 SVal ArgVal = Call.getArgSVal(0); 102 103 if (const MemRegion *R = ArgVal.getAsRegion()) { 104 R = R->StripCasts(); 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 } 111 } 112 return false; 113 } 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 154 // Check the jail state before any function call except chroot and chdir(). 155 void ChrootChecker::checkPreCall(const CallEvent &Call, 156 CheckerContext &C) const { 157 // Ignore chroot and chdir. 158 if (matchesAny(Call, Chroot, Chdir)) 159 return; 160 161 // If jail state is not ROOT_CHANGED just return. 162 if (C.getState()->get<ChrootState>() != ROOT_CHANGED) 163 return; 164 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)); 175 } 176 177 } // namespace 178 179 void ento::registerChrootChecker(CheckerManager &Mgr) { 180 Mgr.registerChecker<ChrootChecker>(); 181 } 182 183 bool ento::shouldRegisterChrootChecker(const CheckerManager &) { return true; } 184