1//===- Chrootchecker.cpp -------- Basic security checks ---------*- C++ -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file defines chroot checker, which checks improper use of chroot. 11// 12//===----------------------------------------------------------------------===// 13 14#include "ClangSACheckers.h" 15#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 16#include "clang/StaticAnalyzer/Core/Checker.h" 17#include "clang/StaticAnalyzer/Core/CheckerManager.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#include "llvm/ADT/ImmutableMap.h" 23 24using namespace clang; 25using namespace ento; 26 27namespace { 28 29// enum value that represent the jail state 30enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED }; 31 32bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } 33//bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; } 34 35// This checker checks improper use of chroot. 36// The state transition: 37// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED 38// | | 39// ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)-- 40// | | 41// bug<--foo()-- JAIL_ENTERED<--foo()-- 42class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > { 43 mutable IdentifierInfo *II_chroot, *II_chdir; 44 // This bug refers to possibly break out of a chroot() jail. 45 mutable std::unique_ptr<BuiltinBug> BT_BreakJail; 46 47public: 48 ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {} 49 50 static void *getTag() { 51 static int x; 52 return &x; 53 } 54 55 bool evalCall(const CallExpr *CE, CheckerContext &C) const; 56 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 57 58private: 59 void Chroot(CheckerContext &C, const CallExpr *CE) const; 60 void Chdir(CheckerContext &C, const CallExpr *CE) const; 61}; 62 63} // end anonymous namespace 64 65bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { 66 const FunctionDecl *FD = C.getCalleeDecl(CE); 67 if (!FD) 68 return false; 69 70 ASTContext &Ctx = C.getASTContext(); 71 if (!II_chroot) 72 II_chroot = &Ctx.Idents.get("chroot"); 73 if (!II_chdir) 74 II_chdir = &Ctx.Idents.get("chdir"); 75 76 if (FD->getIdentifier() == II_chroot) { 77 Chroot(C, CE); 78 return true; 79 } 80 if (FD->getIdentifier() == II_chdir) { 81 Chdir(C, CE); 82 return true; 83 } 84 85 return false; 86} 87 88void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { 89 ProgramStateRef state = C.getState(); 90 ProgramStateManager &Mgr = state->getStateManager(); 91 92 // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in 93 // the GDM. 94 state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED); 95 C.addTransition(state); 96} 97 98void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { 99 ProgramStateRef state = C.getState(); 100 ProgramStateManager &Mgr = state->getStateManager(); 101 102 // If there are no jail state in the GDM, just return. 103 const void *k = state->FindGDM(ChrootChecker::getTag()); 104 if (!k) 105 return; 106 107 // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. 108 const Expr *ArgExpr = CE->getArg(0); 109 SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext()); 110 111 if (const MemRegion *R = ArgVal.getAsRegion()) { 112 R = R->StripCasts(); 113 if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) { 114 const StringLiteral* Str = StrRegion->getStringLiteral(); 115 if (Str->getString() == "/") 116 state = Mgr.addGDM(state, ChrootChecker::getTag(), 117 (void*) JAIL_ENTERED); 118 } 119 } 120 121 C.addTransition(state); 122} 123 124// Check the jail state before any function call except chroot and chdir(). 125void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { 126 const FunctionDecl *FD = C.getCalleeDecl(CE); 127 if (!FD) 128 return; 129 130 ASTContext &Ctx = C.getASTContext(); 131 if (!II_chroot) 132 II_chroot = &Ctx.Idents.get("chroot"); 133 if (!II_chdir) 134 II_chdir = &Ctx.Idents.get("chdir"); 135 136 // Ingnore chroot and chdir. 137 if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir) 138 return; 139 140 // If jail state is ROOT_CHANGED, generate BugReport. 141 void *const* k = C.getState()->FindGDM(ChrootChecker::getTag()); 142 if (k) 143 if (isRootChanged((intptr_t) *k)) 144 if (ExplodedNode *N = C.generateNonFatalErrorNode()) { 145 if (!BT_BreakJail) 146 BT_BreakJail.reset(new BuiltinBug( 147 this, "Break out of jail", "No call of chdir(\"/\") immediately " 148 "after chroot")); 149 C.emitReport(llvm::make_unique<BugReport>( 150 *BT_BreakJail, BT_BreakJail->getDescription(), N)); 151 } 152} 153 154void ento::registerChrootChecker(CheckerManager &mgr) { 155 mgr.registerChecker<ChrootChecker>(); 156} 157