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.
11 //===----------------------------------------------------------------------===//
13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
24 using namespace clang
;
29 // enum value that represent the jail state
30 enum Kind
{ NO_CHROOT
, ROOT_CHANGED
, JAIL_ENTERED
};
32 bool isRootChanged(intptr_t k
) { return k
== ROOT_CHANGED
; }
33 //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
35 // This checker checks improper use of chroot.
36 // The state transition:
37 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
39 // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
41 // bug<--foo()-- JAIL_ENTERED<--foo()--
42 class ChrootChecker
: public Checker
<eval::Call
, check::PreCall
> {
43 // This bug refers to possibly break out of a chroot() jail.
44 mutable std::unique_ptr
<BugType
> BT_BreakJail
;
46 const CallDescription Chroot
{{"chroot"}, 1}, Chdir
{{"chdir"}, 1};
51 static void *getTag() {
56 bool evalCall(const CallEvent
&Call
, CheckerContext
&C
) const;
57 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
60 void evalChroot(const CallEvent
&Call
, CheckerContext
&C
) const;
61 void evalChdir(const CallEvent
&Call
, CheckerContext
&C
) const;
64 } // end anonymous namespace
66 bool ChrootChecker::evalCall(const CallEvent
&Call
, CheckerContext
&C
) const {
67 if (Chroot
.matches(Call
)) {
71 if (Chdir
.matches(Call
)) {
79 void ChrootChecker::evalChroot(const CallEvent
&Call
, CheckerContext
&C
) const {
80 ProgramStateRef state
= C
.getState();
81 ProgramStateManager
&Mgr
= state
->getStateManager();
83 // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
85 state
= Mgr
.addGDM(state
, ChrootChecker::getTag(), (void*) ROOT_CHANGED
);
86 C
.addTransition(state
);
89 void ChrootChecker::evalChdir(const CallEvent
&Call
, CheckerContext
&C
) const {
90 ProgramStateRef state
= C
.getState();
91 ProgramStateManager
&Mgr
= state
->getStateManager();
93 // If there are no jail state in the GDM, just return.
94 const void *k
= state
->FindGDM(ChrootChecker::getTag());
98 // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
99 const Expr
*ArgExpr
= Call
.getArgExpr(0);
100 SVal ArgVal
= C
.getSVal(ArgExpr
);
102 if (const MemRegion
*R
= ArgVal
.getAsRegion()) {
104 if (const StringRegion
* StrRegion
= dyn_cast
<StringRegion
>(R
)) {
105 const StringLiteral
* Str
= StrRegion
->getStringLiteral();
106 if (Str
->getString() == "/")
107 state
= Mgr
.addGDM(state
, ChrootChecker::getTag(),
108 (void*) JAIL_ENTERED
);
112 C
.addTransition(state
);
115 // Check the jail state before any function call except chroot and chdir().
116 void ChrootChecker::checkPreCall(const CallEvent
&Call
,
117 CheckerContext
&C
) const {
118 // Ignore chroot and chdir.
119 if (matchesAny(Call
, Chroot
, Chdir
))
122 // If jail state is ROOT_CHANGED, generate BugReport.
123 void *const* k
= C
.getState()->FindGDM(ChrootChecker::getTag());
125 if (isRootChanged((intptr_t) *k
))
126 if (ExplodedNode
*N
= C
.generateNonFatalErrorNode()) {
128 BT_BreakJail
.reset(new BugType(this, "Break out of jail"));
129 constexpr llvm::StringLiteral Msg
=
130 "No call of chdir(\"/\") immediately after chroot";
132 std::make_unique
<PathSensitiveBugReport
>(*BT_BreakJail
, Msg
, N
));
136 void ento::registerChrootChecker(CheckerManager
&mgr
) {
137 mgr
.registerChecker
<ChrootChecker
>();
140 bool ento::shouldRegisterChrootChecker(const CheckerManager
&mgr
) {