clang 20.0.0git
ChrootChecker.cpp
Go to the documentation of this file.
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.
13//
14//===----------------------------------------------------------------------===//
15
27
28using namespace clang;
29using namespace ento;
30
31namespace {
32enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };
33} // namespace
34
35// Track chroot state changes for success, failure, state change
36// and "jail"
37REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind)
38namespace {
39
40// This checker checks improper use of chroot.
41// The state transitions
42//
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()--
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
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;
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} // namespace
178
179void ento::registerChrootChecker(CheckerManager &Mgr) {
180 Mgr.registerChecker<ChrootChecker>();
181}
182
183bool ento::shouldRegisterChrootChecker(const CheckerManager &) { return true; }
Defines the clang::ASTContext interface.
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2874
It wraps the AnalysisDeclContext to represent both the call stack with the help of StackFrameContext ...
std::optional< T > getAs() const
Convert to the specified ProgramPoint type, returning std::nullopt if this ProgramPoint is not of the...
Definition: ProgramPoint.h:147
A (possibly-)qualified type.
Definition: Type.h:929
const SourceManager & getSourceManager() const
Definition: BugReporter.h:737
BugReporterVisitors are used to add custom diagnostics along a path.
virtual void Profile(llvm::FoldingSetNodeID &ID) const =0
virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, PathSensitiveBugReport &BR)=0
Return a diagnostic piece which should be associated with the given node.
A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:153
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
ProgramPoint getLocation() const
getLocation - Returns the edge associated with the given node.
const LocationContext * getLocationContext() const
MemRegion - The root abstract class for all memory regions.
Definition: MemRegion.h:97
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition: SVals.h:56
const MemRegion * getAsRegion() const
Definition: SVals.cpp:120
Value representing integer constant.
Definition: SVals.h:300
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
bool Zero(InterpState &S, CodePtr OpPC)
Definition: Interp.h:2408
The JSON file list parser is used to communicate input to InstallAPI.