1e5dd7070Spatrick //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick // This file defines chroot checker, which checks improper use of chroot.
10e5dd7070Spatrick //
11e5dd7070Spatrick //===----------------------------------------------------------------------===//
12e5dd7070Spatrick
13e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h"
16e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17*12c85518Srobert #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
21e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
22e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
23e5dd7070Spatrick
24e5dd7070Spatrick using namespace clang;
25e5dd7070Spatrick using namespace ento;
26e5dd7070Spatrick
27e5dd7070Spatrick namespace {
28e5dd7070Spatrick
29e5dd7070Spatrick // enum value that represent the jail state
30e5dd7070Spatrick enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
31e5dd7070Spatrick
isRootChanged(intptr_t k)32e5dd7070Spatrick bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
33e5dd7070Spatrick //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
34e5dd7070Spatrick
35e5dd7070Spatrick // This checker checks improper use of chroot.
36e5dd7070Spatrick // The state transition:
37e5dd7070Spatrick // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38e5dd7070Spatrick // | |
39e5dd7070Spatrick // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
40e5dd7070Spatrick // | |
41e5dd7070Spatrick // bug<--foo()-- JAIL_ENTERED<--foo()--
42e5dd7070Spatrick class ChrootChecker : public Checker<eval::Call, check::PreCall> {
43e5dd7070Spatrick // This bug refers to possibly break out of a chroot() jail.
44e5dd7070Spatrick mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
45e5dd7070Spatrick
46*12c85518Srobert const CallDescription Chroot{{"chroot"}, 1}, Chdir{{"chdir"}, 1};
47e5dd7070Spatrick
48e5dd7070Spatrick public:
ChrootChecker()49e5dd7070Spatrick ChrootChecker() {}
50e5dd7070Spatrick
getTag()51e5dd7070Spatrick static void *getTag() {
52e5dd7070Spatrick static int x;
53e5dd7070Spatrick return &x;
54e5dd7070Spatrick }
55e5dd7070Spatrick
56e5dd7070Spatrick bool evalCall(const CallEvent &Call, CheckerContext &C) const;
57e5dd7070Spatrick void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
58e5dd7070Spatrick
59e5dd7070Spatrick private:
60e5dd7070Spatrick void evalChroot(const CallEvent &Call, CheckerContext &C) const;
61e5dd7070Spatrick void evalChdir(const CallEvent &Call, CheckerContext &C) const;
62e5dd7070Spatrick };
63e5dd7070Spatrick
64e5dd7070Spatrick } // end anonymous namespace
65e5dd7070Spatrick
evalCall(const CallEvent & Call,CheckerContext & C) const66e5dd7070Spatrick bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
67*12c85518Srobert if (Chroot.matches(Call)) {
68e5dd7070Spatrick evalChroot(Call, C);
69e5dd7070Spatrick return true;
70e5dd7070Spatrick }
71*12c85518Srobert if (Chdir.matches(Call)) {
72e5dd7070Spatrick evalChdir(Call, C);
73e5dd7070Spatrick return true;
74e5dd7070Spatrick }
75e5dd7070Spatrick
76e5dd7070Spatrick return false;
77e5dd7070Spatrick }
78e5dd7070Spatrick
evalChroot(const CallEvent & Call,CheckerContext & C) const79e5dd7070Spatrick void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
80e5dd7070Spatrick ProgramStateRef state = C.getState();
81e5dd7070Spatrick ProgramStateManager &Mgr = state->getStateManager();
82e5dd7070Spatrick
83e5dd7070Spatrick // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
84e5dd7070Spatrick // the GDM.
85e5dd7070Spatrick state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
86e5dd7070Spatrick C.addTransition(state);
87e5dd7070Spatrick }
88e5dd7070Spatrick
evalChdir(const CallEvent & Call,CheckerContext & C) const89e5dd7070Spatrick void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
90e5dd7070Spatrick ProgramStateRef state = C.getState();
91e5dd7070Spatrick ProgramStateManager &Mgr = state->getStateManager();
92e5dd7070Spatrick
93e5dd7070Spatrick // If there are no jail state in the GDM, just return.
94e5dd7070Spatrick const void *k = state->FindGDM(ChrootChecker::getTag());
95e5dd7070Spatrick if (!k)
96e5dd7070Spatrick return;
97e5dd7070Spatrick
98e5dd7070Spatrick // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
99e5dd7070Spatrick const Expr *ArgExpr = Call.getArgExpr(0);
100e5dd7070Spatrick SVal ArgVal = C.getSVal(ArgExpr);
101e5dd7070Spatrick
102e5dd7070Spatrick if (const MemRegion *R = ArgVal.getAsRegion()) {
103e5dd7070Spatrick R = R->StripCasts();
104e5dd7070Spatrick if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
105e5dd7070Spatrick const StringLiteral* Str = StrRegion->getStringLiteral();
106e5dd7070Spatrick if (Str->getString() == "/")
107e5dd7070Spatrick state = Mgr.addGDM(state, ChrootChecker::getTag(),
108e5dd7070Spatrick (void*) JAIL_ENTERED);
109e5dd7070Spatrick }
110e5dd7070Spatrick }
111e5dd7070Spatrick
112e5dd7070Spatrick C.addTransition(state);
113e5dd7070Spatrick }
114e5dd7070Spatrick
115e5dd7070Spatrick // Check the jail state before any function call except chroot and chdir().
checkPreCall(const CallEvent & Call,CheckerContext & C) const116e5dd7070Spatrick void ChrootChecker::checkPreCall(const CallEvent &Call,
117e5dd7070Spatrick CheckerContext &C) const {
118e5dd7070Spatrick // Ignore chroot and chdir.
119*12c85518Srobert if (matchesAny(Call, Chroot, Chdir))
120e5dd7070Spatrick return;
121e5dd7070Spatrick
122e5dd7070Spatrick // If jail state is ROOT_CHANGED, generate BugReport.
123e5dd7070Spatrick void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
124e5dd7070Spatrick if (k)
125e5dd7070Spatrick if (isRootChanged((intptr_t) *k))
126e5dd7070Spatrick if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
127e5dd7070Spatrick if (!BT_BreakJail)
128e5dd7070Spatrick BT_BreakJail.reset(new BuiltinBug(
129e5dd7070Spatrick this, "Break out of jail", "No call of chdir(\"/\") immediately "
130e5dd7070Spatrick "after chroot"));
131e5dd7070Spatrick C.emitReport(std::make_unique<PathSensitiveBugReport>(
132e5dd7070Spatrick *BT_BreakJail, BT_BreakJail->getDescription(), N));
133e5dd7070Spatrick }
134e5dd7070Spatrick }
135e5dd7070Spatrick
registerChrootChecker(CheckerManager & mgr)136e5dd7070Spatrick void ento::registerChrootChecker(CheckerManager &mgr) {
137e5dd7070Spatrick mgr.registerChecker<ChrootChecker>();
138e5dd7070Spatrick }
139e5dd7070Spatrick
shouldRegisterChrootChecker(const CheckerManager & mgr)140ec727ea7Spatrick bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) {
141e5dd7070Spatrick return true;
142e5dd7070Spatrick }
143