[memprof] Update YAML traits for writer purposes (#118720)
[llvm-project.git] / clang / lib / StaticAnalyzer / Checkers / ChrootChecker.cpp
blob99fc0a953ef178f99dc9e26eb0034761eb85f5b9
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 // 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 a
12 // recommended rule.
14 //===----------------------------------------------------------------------===//
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"
28 using namespace clang;
29 using namespace ento;
31 namespace {
32 enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };
33 } // namespace
35 // Track chroot state changes for success, failure, state change
36 // and "jail"
37 REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind)
38 namespace {
40 // This checker checks improper use of chroot.
41 // The state transitions
43 // -> ROOT_CHANGE_FAILED
44 // |
45 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
46 // | |
47 // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
48 // | |
49 // bug<--foo()-- JAIL_ENTERED<--foo()--
51 class ChrootChecker final : public Checker<eval::Call, check::PreCall> {
52 public:
53 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
54 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
56 private:
57 bool evalChroot(const CallEvent &Call, CheckerContext &C) const;
58 bool evalChdir(const CallEvent &Call, CheckerContext &C) const;
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};
65 bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
66 if (Chroot.matches(Call))
67 return evalChroot(Call, C);
69 if (Chdir.matches(Call))
70 return evalChdir(Call, C);
72 return false;
75 bool 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());
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)};
85 ProgramStateRef ChrootFailed = State->BindExpr(CE, LCtx, Minus1);
86 C.addTransition(ChrootFailed->set<ChrootState>(ROOT_CHANGE_FAILED));
88 ProgramStateRef ChrootSucceeded = State->BindExpr(CE, LCtx, Zero);
89 C.addTransition(ChrootSucceeded->set<ChrootState>(ROOT_CHANGED));
90 return true;
93 bool ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
94 ProgramStateRef State = C.getState();
96 // If there are no jail state, just return.
97 if (State->get<ChrootState>() == NO_CHROOT)
98 return false;
100 // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
101 SVal ArgVal = Call.getArgSVal(0);
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;
112 return false;
115 class ChrootInvocationVisitor final : public BugReporterVisitor {
116 public:
117 explicit ChrootInvocationVisitor(const CallDescription &Chroot)
118 : Chroot{Chroot} {}
120 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
121 BugReporterContext &BRC,
122 PathSensitiveBugReport &BR) override {
123 if (Satisfied)
124 return nullptr;
126 auto StmtP = N->getLocation().getAs<StmtPoint>();
127 if (!StmtP)
128 return nullptr;
130 const CallExpr *Call = StmtP->getStmtAs<CallExpr>();
131 if (!Call)
132 return nullptr;
134 if (!Chroot.matchesAsWritten(*Call))
135 return nullptr;
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);
144 void Profile(llvm::FoldingSetNodeID &ID) const override {
145 static bool Tag;
146 ID.AddPointer(&Tag);
149 private:
150 const CallDescription &Chroot;
151 bool Satisfied = false;
154 // Check the jail state before any function call except chroot and chdir().
155 void ChrootChecker::checkPreCall(const CallEvent &Call,
156 CheckerContext &C) const {
157 // Ignore chroot and chdir.
158 if (matchesAny(Call, Chroot, Chdir))
159 return;
161 // If jail state is not ROOT_CHANGED just return.
162 if (C.getState()->get<ChrootState>() != ROOT_CHANGED)
163 return;
165 // Generate bug report.
166 ExplodedNode *Err =
167 C.generateNonFatalErrorNode(C.getState(), C.getPredecessor());
168 if (!Err)
169 return;
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));
177 } // namespace
179 void ento::registerChrootChecker(CheckerManager &Mgr) {
180 Mgr.registerChecker<ChrootChecker>();
183 bool ento::shouldRegisterChrootChecker(const CheckerManager &) { return true; }