10b57cec5SDimitry Andric //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric // 90b57cec5SDimitry Andric // This file defines chroot checker, which checks improper use of chroot. 100b57cec5SDimitry Andric // 110b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 120b57cec5SDimitry Andric 130b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 140b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 150b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 160b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/CheckerManager.h" 17349cc55cSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 200b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 220b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 230b57cec5SDimitry Andric 240b57cec5SDimitry Andric using namespace clang; 250b57cec5SDimitry Andric using namespace ento; 260b57cec5SDimitry Andric 270b57cec5SDimitry Andric namespace { 280b57cec5SDimitry Andric 290b57cec5SDimitry Andric // enum value that represent the jail state 300b57cec5SDimitry Andric enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED }; 310b57cec5SDimitry Andric 320b57cec5SDimitry Andric bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } 330b57cec5SDimitry Andric //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; } 340b57cec5SDimitry Andric 350b57cec5SDimitry Andric // This checker checks improper use of chroot. 360b57cec5SDimitry Andric // The state transition: 370b57cec5SDimitry Andric // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED 380b57cec5SDimitry Andric // | | 390b57cec5SDimitry Andric // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)-- 400b57cec5SDimitry Andric // | | 410b57cec5SDimitry Andric // bug<--foo()-- JAIL_ENTERED<--foo()-- 420b57cec5SDimitry Andric class ChrootChecker : public Checker<eval::Call, check::PreCall> { 430b57cec5SDimitry Andric // This bug refers to possibly break out of a chroot() jail. 44647cbc5dSDimitry Andric const BugType BT_BreakJail{this, "Break out of jail"}; 450b57cec5SDimitry Andric 46*0fca6ea1SDimitry Andric const CallDescription Chroot{CDM::CLibrary, {"chroot"}, 1}, 47*0fca6ea1SDimitry Andric Chdir{CDM::CLibrary, {"chdir"}, 1}; 480b57cec5SDimitry Andric 490b57cec5SDimitry Andric public: 500b57cec5SDimitry Andric ChrootChecker() {} 510b57cec5SDimitry Andric 520b57cec5SDimitry Andric static void *getTag() { 530b57cec5SDimitry Andric static int x; 540b57cec5SDimitry Andric return &x; 550b57cec5SDimitry Andric } 560b57cec5SDimitry Andric 570b57cec5SDimitry Andric bool evalCall(const CallEvent &Call, CheckerContext &C) const; 580b57cec5SDimitry Andric void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 590b57cec5SDimitry Andric 600b57cec5SDimitry Andric private: 610b57cec5SDimitry Andric void evalChroot(const CallEvent &Call, CheckerContext &C) const; 620b57cec5SDimitry Andric void evalChdir(const CallEvent &Call, CheckerContext &C) const; 630b57cec5SDimitry Andric }; 640b57cec5SDimitry Andric 650b57cec5SDimitry Andric } // end anonymous namespace 660b57cec5SDimitry Andric 670b57cec5SDimitry Andric bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 68349cc55cSDimitry Andric if (Chroot.matches(Call)) { 690b57cec5SDimitry Andric evalChroot(Call, C); 700b57cec5SDimitry Andric return true; 710b57cec5SDimitry Andric } 72349cc55cSDimitry Andric if (Chdir.matches(Call)) { 730b57cec5SDimitry Andric evalChdir(Call, C); 740b57cec5SDimitry Andric return true; 750b57cec5SDimitry Andric } 760b57cec5SDimitry Andric 770b57cec5SDimitry Andric return false; 780b57cec5SDimitry Andric } 790b57cec5SDimitry Andric 800b57cec5SDimitry Andric void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const { 810b57cec5SDimitry Andric ProgramStateRef state = C.getState(); 820b57cec5SDimitry Andric ProgramStateManager &Mgr = state->getStateManager(); 830b57cec5SDimitry Andric 840b57cec5SDimitry Andric // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in 850b57cec5SDimitry Andric // the GDM. 860b57cec5SDimitry Andric state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED); 870b57cec5SDimitry Andric C.addTransition(state); 880b57cec5SDimitry Andric } 890b57cec5SDimitry Andric 900b57cec5SDimitry Andric void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { 910b57cec5SDimitry Andric ProgramStateRef state = C.getState(); 920b57cec5SDimitry Andric ProgramStateManager &Mgr = state->getStateManager(); 930b57cec5SDimitry Andric 940b57cec5SDimitry Andric // If there are no jail state in the GDM, just return. 950b57cec5SDimitry Andric const void *k = state->FindGDM(ChrootChecker::getTag()); 960b57cec5SDimitry Andric if (!k) 970b57cec5SDimitry Andric return; 980b57cec5SDimitry Andric 990b57cec5SDimitry Andric // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. 1000b57cec5SDimitry Andric const Expr *ArgExpr = Call.getArgExpr(0); 1010b57cec5SDimitry Andric SVal ArgVal = C.getSVal(ArgExpr); 1020b57cec5SDimitry Andric 1030b57cec5SDimitry Andric if (const MemRegion *R = ArgVal.getAsRegion()) { 1040b57cec5SDimitry Andric R = R->StripCasts(); 1050b57cec5SDimitry Andric if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) { 1060b57cec5SDimitry Andric const StringLiteral* Str = StrRegion->getStringLiteral(); 1070b57cec5SDimitry Andric if (Str->getString() == "/") 1080b57cec5SDimitry Andric state = Mgr.addGDM(state, ChrootChecker::getTag(), 1090b57cec5SDimitry Andric (void*) JAIL_ENTERED); 1100b57cec5SDimitry Andric } 1110b57cec5SDimitry Andric } 1120b57cec5SDimitry Andric 1130b57cec5SDimitry Andric C.addTransition(state); 1140b57cec5SDimitry Andric } 1150b57cec5SDimitry Andric 1160b57cec5SDimitry Andric // Check the jail state before any function call except chroot and chdir(). 1170b57cec5SDimitry Andric void ChrootChecker::checkPreCall(const CallEvent &Call, 1180b57cec5SDimitry Andric CheckerContext &C) const { 1190b57cec5SDimitry Andric // Ignore chroot and chdir. 120349cc55cSDimitry Andric if (matchesAny(Call, Chroot, Chdir)) 1210b57cec5SDimitry Andric return; 1220b57cec5SDimitry Andric 1230b57cec5SDimitry Andric // If jail state is ROOT_CHANGED, generate BugReport. 1240b57cec5SDimitry Andric void *const* k = C.getState()->FindGDM(ChrootChecker::getTag()); 1250b57cec5SDimitry Andric if (k) 1260b57cec5SDimitry Andric if (isRootChanged((intptr_t) *k)) 1270b57cec5SDimitry Andric if (ExplodedNode *N = C.generateNonFatalErrorNode()) { 1285f757f3fSDimitry Andric constexpr llvm::StringLiteral Msg = 1295f757f3fSDimitry Andric "No call of chdir(\"/\") immediately after chroot"; 1305f757f3fSDimitry Andric C.emitReport( 131647cbc5dSDimitry Andric std::make_unique<PathSensitiveBugReport>(BT_BreakJail, Msg, N)); 1320b57cec5SDimitry Andric } 1330b57cec5SDimitry Andric } 1340b57cec5SDimitry Andric 1350b57cec5SDimitry Andric void ento::registerChrootChecker(CheckerManager &mgr) { 1360b57cec5SDimitry Andric mgr.registerChecker<ChrootChecker>(); 1370b57cec5SDimitry Andric } 1380b57cec5SDimitry Andric 1395ffd83dbSDimitry Andric bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) { 1400b57cec5SDimitry Andric return true; 1410b57cec5SDimitry Andric } 142