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