brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.3 KiB · 99fc0a9 Raw
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