184 lines · cpp
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-exception6//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 a12// 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 28using namespace clang;29using namespace ento;30 31namespace {32enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };33} // namespace34 35// Track chroot state changes for success, failure, state change36// and "jail"37REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind)38namespace {39 40// This checker checks improper use of chroot.41// The state transitions42//43// -> ROOT_CHANGE_FAILED44// |45// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED46// | |47// ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--48// | |49// bug<--foo()-- JAIL_ENTERED<--foo()--50//51class ChrootChecker final : public Checker<eval::Call, check::PreCall> {52public:53 bool evalCall(const CallEvent &Call, CheckerContext &C) const;54 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;55 56private: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 65bool 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 75bool 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 93bool 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 115class ChrootInvocationVisitor final : public BugReporterVisitor {116public: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 149private:150 const CallDescription &Chroot;151 bool Satisfied = false;152};153 154// Check the jail state before any function call except chroot and chdir().155void 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} // namespace178 179void ento::registerChrootChecker(CheckerManager &Mgr) {180 Mgr.registerChecker<ChrootChecker>();181}182 183bool ento::shouldRegisterChrootChecker(const CheckerManager &) { return true; }184