1 //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
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
7 //===----------------------------------------------------------------------===//
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
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
;
32 enum ChrootKind
{ NO_CHROOT
, ROOT_CHANGED
, ROOT_CHANGE_FAILED
, JAIL_ENTERED
};
35 // Track chroot state changes for success, failure, state change
37 REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState
, ChrootKind
)
40 // This checker checks improper use of chroot.
41 // The state transitions
43 // -> ROOT_CHANGE_FAILED
45 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
47 // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
49 // bug<--foo()-- JAIL_ENTERED<--foo()--
51 class ChrootChecker final
: public Checker
<eval::Call
, check::PreCall
> {
53 bool evalCall(const CallEvent
&Call
, CheckerContext
&C
) const;
54 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
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
);
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
));
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
)
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()) {
105 if (const auto *StrRegion
= dyn_cast
<StringRegion
>(R
)) {
106 if (StrRegion
->getStringLiteral()->getString() == "/") {
107 C
.addTransition(State
->set
<ChrootState
>(JAIL_ENTERED
));
115 class ChrootInvocationVisitor final
: public BugReporterVisitor
{
117 explicit ChrootInvocationVisitor(const CallDescription
&Chroot
)
120 PathDiagnosticPieceRef
VisitNode(const ExplodedNode
*N
,
121 BugReporterContext
&BRC
,
122 PathSensitiveBugReport
&BR
) override
{
126 auto StmtP
= N
->getLocation().getAs
<StmtPoint
>();
130 const CallExpr
*Call
= StmtP
->getStmtAs
<CallExpr
>();
134 if (!Chroot
.matchesAsWritten(*Call
))
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
{
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
))
161 // If jail state is not ROOT_CHANGED just return.
162 if (C
.getState()->get
<ChrootState
>() != ROOT_CHANGED
)
165 // Generate bug report.
167 C
.generateNonFatalErrorNode(C
.getState(), C
.getPredecessor());
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
));
179 void ento::registerChrootChecker(CheckerManager
&Mgr
) {
180 Mgr
.registerChecker
<ChrootChecker
>();
183 bool ento::shouldRegisterChrootChecker(const CheckerManager
&) { return true; }