1524b3c18SFangrui Song //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
2d99bd55aSTed Kremenek //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6d99bd55aSTed Kremenek //
7d99bd55aSTed Kremenek //===----------------------------------------------------------------------===//
8d99bd55aSTed Kremenek //
9d99bd55aSTed Kremenek // This file defines chroot checker, which checks improper use of chroot.
10d99bd55aSTed Kremenek //
11d99bd55aSTed Kremenek //===----------------------------------------------------------------------===//
12d99bd55aSTed Kremenek
1376a21502SKristof Umann #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
143a02247dSChandler Carruth #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
156a5674ffSArgyrios Kyrtzidis #include "clang/StaticAnalyzer/Core/Checker.h"
16507ff53eSArgyrios Kyrtzidis #include "clang/StaticAnalyzer/Core/CheckerManager.h"
170b9d3a6eSBalazs Benics #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
1844820630SArtem Dergachev #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19da02a25aSArgyrios Kyrtzidis #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20001fd5b4STed Kremenek #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
21001fd5b4STed Kremenek #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
22f8cbac4bSTed Kremenek #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
230a4f3f43SEugene Zelenko
24d99bd55aSTed Kremenek using namespace clang;
25d99bd55aSTed Kremenek using namespace ento;
26d99bd55aSTed Kremenek
27d99bd55aSTed Kremenek namespace {
28d99bd55aSTed Kremenek
29d99bd55aSTed Kremenek // enum value that represent the jail state
30d99bd55aSTed Kremenek enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
31d99bd55aSTed Kremenek
isRootChanged(intptr_t k)32d99bd55aSTed Kremenek bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
33d99bd55aSTed Kremenek //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
34d99bd55aSTed Kremenek
35d99bd55aSTed Kremenek // This checker checks improper use of chroot.
36d99bd55aSTed Kremenek // The state transition:
37d99bd55aSTed Kremenek // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38d99bd55aSTed Kremenek // | |
39d99bd55aSTed Kremenek // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
40d99bd55aSTed Kremenek // | |
41d99bd55aSTed Kremenek // bug<--foo()-- JAIL_ENTERED<--foo()--
4244820630SArtem Dergachev class ChrootChecker : public Checker<eval::Call, check::PreCall> {
43d99bd55aSTed Kremenek // This bug refers to possibly break out of a chroot() jail.
44b8984329SAhmed Charles mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
45d99bd55aSTed Kremenek
4644820630SArtem Dergachev const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1};
4744820630SArtem Dergachev
48d99bd55aSTed Kremenek public:
ChrootChecker()4944820630SArtem Dergachev ChrootChecker() {}
50d99bd55aSTed Kremenek
getTag()51d99bd55aSTed Kremenek static void *getTag() {
52d99bd55aSTed Kremenek static int x;
53d99bd55aSTed Kremenek return &x;
54d99bd55aSTed Kremenek }
55d99bd55aSTed Kremenek
5644820630SArtem Dergachev bool evalCall(const CallEvent &Call, CheckerContext &C) const;
5744820630SArtem Dergachev void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
58d99bd55aSTed Kremenek
59d99bd55aSTed Kremenek private:
6044820630SArtem Dergachev void evalChroot(const CallEvent &Call, CheckerContext &C) const;
6144820630SArtem Dergachev void evalChdir(const CallEvent &Call, CheckerContext &C) const;
62d99bd55aSTed Kremenek };
63d99bd55aSTed Kremenek
64d99bd55aSTed Kremenek } // end anonymous namespace
65d99bd55aSTed Kremenek
evalCall(const CallEvent & Call,CheckerContext & C) const6644820630SArtem Dergachev bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
67*f18da190SBalazs Benics if (Chroot.matches(Call)) {
6844820630SArtem Dergachev evalChroot(Call, C);
69d99bd55aSTed Kremenek return true;
70d99bd55aSTed Kremenek }
71*f18da190SBalazs Benics if (Chdir.matches(Call)) {
7244820630SArtem Dergachev evalChdir(Call, C);
73d99bd55aSTed Kremenek return true;
74d99bd55aSTed Kremenek }
75d99bd55aSTed Kremenek
76d99bd55aSTed Kremenek return false;
77d99bd55aSTed Kremenek }
78d99bd55aSTed Kremenek
evalChroot(const CallEvent & Call,CheckerContext & C) const7944820630SArtem Dergachev void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
8049b1e38eSTed Kremenek ProgramStateRef state = C.getState();
81001fd5b4STed Kremenek ProgramStateManager &Mgr = state->getStateManager();
82d99bd55aSTed Kremenek
83d99bd55aSTed Kremenek // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
84d99bd55aSTed Kremenek // the GDM.
85d99bd55aSTed Kremenek state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
86da4c8d68SAnna Zaks C.addTransition(state);
87d99bd55aSTed Kremenek }
88d99bd55aSTed Kremenek
evalChdir(const CallEvent & Call,CheckerContext & C) const8944820630SArtem Dergachev void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
9049b1e38eSTed Kremenek ProgramStateRef state = C.getState();
91001fd5b4STed Kremenek ProgramStateManager &Mgr = state->getStateManager();
92d99bd55aSTed Kremenek
93d99bd55aSTed Kremenek // If there are no jail state in the GDM, just return.
94d99bd55aSTed Kremenek const void *k = state->FindGDM(ChrootChecker::getTag());
95d99bd55aSTed Kremenek if (!k)
96d99bd55aSTed Kremenek return;
97d99bd55aSTed Kremenek
98d99bd55aSTed Kremenek // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
9944820630SArtem Dergachev const Expr *ArgExpr = Call.getArgExpr(0);
100d703ec94SGeorge Karpenkov SVal ArgVal = C.getSVal(ArgExpr);
101d99bd55aSTed Kremenek
102d99bd55aSTed Kremenek if (const MemRegion *R = ArgVal.getAsRegion()) {
103d99bd55aSTed Kremenek R = R->StripCasts();
104d99bd55aSTed Kremenek if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
105d99bd55aSTed Kremenek const StringLiteral* Str = StrRegion->getStringLiteral();
106d99bd55aSTed Kremenek if (Str->getString() == "/")
107d99bd55aSTed Kremenek state = Mgr.addGDM(state, ChrootChecker::getTag(),
108d99bd55aSTed Kremenek (void*) JAIL_ENTERED);
109d99bd55aSTed Kremenek }
110d99bd55aSTed Kremenek }
111d99bd55aSTed Kremenek
112da4c8d68SAnna Zaks C.addTransition(state);
113d99bd55aSTed Kremenek }
114d99bd55aSTed Kremenek
115d99bd55aSTed Kremenek // Check the jail state before any function call except chroot and chdir().
checkPreCall(const CallEvent & Call,CheckerContext & C) const11644820630SArtem Dergachev void ChrootChecker::checkPreCall(const CallEvent &Call,
11744820630SArtem Dergachev CheckerContext &C) const {
1182a8c18d9SAlexander Kornienko // Ignore chroot and chdir.
119*f18da190SBalazs Benics if (matchesAny(Call, Chroot, Chdir))
120d99bd55aSTed Kremenek return;
121d99bd55aSTed Kremenek
122d99bd55aSTed Kremenek // If jail state is ROOT_CHANGED, generate BugReport.
123c6aa531aSAnna Zaks void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
124d99bd55aSTed Kremenek if (k)
125d99bd55aSTed Kremenek if (isRootChanged((intptr_t) *k))
126e39bd407SDevin Coughlin if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
127d99bd55aSTed Kremenek if (!BT_BreakJail)
1284aca9b1cSAlexander Kornienko BT_BreakJail.reset(new BuiltinBug(
1294aca9b1cSAlexander Kornienko this, "Break out of jail", "No call of chdir(\"/\") immediately "
130da02a25aSArgyrios Kyrtzidis "after chroot"));
1312f169e7cSArtem Dergachev C.emitReport(std::make_unique<PathSensitiveBugReport>(
1328d3a7a56SAaron Ballman *BT_BreakJail, BT_BreakJail->getDescription(), N));
133d99bd55aSTed Kremenek }
134d99bd55aSTed Kremenek }
135da02a25aSArgyrios Kyrtzidis
registerChrootChecker(CheckerManager & mgr)136da02a25aSArgyrios Kyrtzidis void ento::registerChrootChecker(CheckerManager &mgr) {
137da02a25aSArgyrios Kyrtzidis mgr.registerChecker<ChrootChecker>();
138da02a25aSArgyrios Kyrtzidis }
139058a7a45SKristof Umann
shouldRegisterChrootChecker(const CheckerManager & mgr)140bda3dd0dSKirstóf Umann bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) {
141058a7a45SKristof Umann return true;
142058a7a45SKristof Umann }
143