1*67e74705SXin Li //===- Chrootchecker.cpp -------- Basic security checks ---------*- C++ -*-===//
2*67e74705SXin Li //
3*67e74705SXin Li // The LLVM Compiler Infrastructure
4*67e74705SXin Li //
5*67e74705SXin Li // This file is distributed under the University of Illinois Open Source
6*67e74705SXin Li // License. See LICENSE.TXT for details.
7*67e74705SXin Li //
8*67e74705SXin Li //===----------------------------------------------------------------------===//
9*67e74705SXin Li //
10*67e74705SXin Li // This file defines chroot checker, which checks improper use of chroot.
11*67e74705SXin Li //
12*67e74705SXin Li //===----------------------------------------------------------------------===//
13*67e74705SXin Li
14*67e74705SXin Li #include "ClangSACheckers.h"
15*67e74705SXin Li #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
16*67e74705SXin Li #include "clang/StaticAnalyzer/Core/Checker.h"
17*67e74705SXin Li #include "clang/StaticAnalyzer/Core/CheckerManager.h"
18*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
19*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
20*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
21*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
22*67e74705SXin Li #include "llvm/ADT/ImmutableMap.h"
23*67e74705SXin Li
24*67e74705SXin Li using namespace clang;
25*67e74705SXin Li using namespace ento;
26*67e74705SXin Li
27*67e74705SXin Li namespace {
28*67e74705SXin Li
29*67e74705SXin Li // enum value that represent the jail state
30*67e74705SXin Li enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
31*67e74705SXin Li
isRootChanged(intptr_t k)32*67e74705SXin Li bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
33*67e74705SXin Li //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
34*67e74705SXin Li
35*67e74705SXin Li // This checker checks improper use of chroot.
36*67e74705SXin Li // The state transition:
37*67e74705SXin Li // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
38*67e74705SXin Li // | |
39*67e74705SXin Li // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
40*67e74705SXin Li // | |
41*67e74705SXin Li // bug<--foo()-- JAIL_ENTERED<--foo()--
42*67e74705SXin Li class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > {
43*67e74705SXin Li mutable IdentifierInfo *II_chroot, *II_chdir;
44*67e74705SXin Li // This bug refers to possibly break out of a chroot() jail.
45*67e74705SXin Li mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
46*67e74705SXin Li
47*67e74705SXin Li public:
ChrootChecker()48*67e74705SXin Li ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {}
49*67e74705SXin Li
getTag()50*67e74705SXin Li static void *getTag() {
51*67e74705SXin Li static int x;
52*67e74705SXin Li return &x;
53*67e74705SXin Li }
54*67e74705SXin Li
55*67e74705SXin Li bool evalCall(const CallExpr *CE, CheckerContext &C) const;
56*67e74705SXin Li void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
57*67e74705SXin Li
58*67e74705SXin Li private:
59*67e74705SXin Li void Chroot(CheckerContext &C, const CallExpr *CE) const;
60*67e74705SXin Li void Chdir(CheckerContext &C, const CallExpr *CE) const;
61*67e74705SXin Li };
62*67e74705SXin Li
63*67e74705SXin Li } // end anonymous namespace
64*67e74705SXin Li
evalCall(const CallExpr * CE,CheckerContext & C) const65*67e74705SXin Li bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
66*67e74705SXin Li const FunctionDecl *FD = C.getCalleeDecl(CE);
67*67e74705SXin Li if (!FD)
68*67e74705SXin Li return false;
69*67e74705SXin Li
70*67e74705SXin Li ASTContext &Ctx = C.getASTContext();
71*67e74705SXin Li if (!II_chroot)
72*67e74705SXin Li II_chroot = &Ctx.Idents.get("chroot");
73*67e74705SXin Li if (!II_chdir)
74*67e74705SXin Li II_chdir = &Ctx.Idents.get("chdir");
75*67e74705SXin Li
76*67e74705SXin Li if (FD->getIdentifier() == II_chroot) {
77*67e74705SXin Li Chroot(C, CE);
78*67e74705SXin Li return true;
79*67e74705SXin Li }
80*67e74705SXin Li if (FD->getIdentifier() == II_chdir) {
81*67e74705SXin Li Chdir(C, CE);
82*67e74705SXin Li return true;
83*67e74705SXin Li }
84*67e74705SXin Li
85*67e74705SXin Li return false;
86*67e74705SXin Li }
87*67e74705SXin Li
Chroot(CheckerContext & C,const CallExpr * CE) const88*67e74705SXin Li void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
89*67e74705SXin Li ProgramStateRef state = C.getState();
90*67e74705SXin Li ProgramStateManager &Mgr = state->getStateManager();
91*67e74705SXin Li
92*67e74705SXin Li // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
93*67e74705SXin Li // the GDM.
94*67e74705SXin Li state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
95*67e74705SXin Li C.addTransition(state);
96*67e74705SXin Li }
97*67e74705SXin Li
Chdir(CheckerContext & C,const CallExpr * CE) const98*67e74705SXin Li void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
99*67e74705SXin Li ProgramStateRef state = C.getState();
100*67e74705SXin Li ProgramStateManager &Mgr = state->getStateManager();
101*67e74705SXin Li
102*67e74705SXin Li // If there are no jail state in the GDM, just return.
103*67e74705SXin Li const void *k = state->FindGDM(ChrootChecker::getTag());
104*67e74705SXin Li if (!k)
105*67e74705SXin Li return;
106*67e74705SXin Li
107*67e74705SXin Li // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
108*67e74705SXin Li const Expr *ArgExpr = CE->getArg(0);
109*67e74705SXin Li SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext());
110*67e74705SXin Li
111*67e74705SXin Li if (const MemRegion *R = ArgVal.getAsRegion()) {
112*67e74705SXin Li R = R->StripCasts();
113*67e74705SXin Li if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
114*67e74705SXin Li const StringLiteral* Str = StrRegion->getStringLiteral();
115*67e74705SXin Li if (Str->getString() == "/")
116*67e74705SXin Li state = Mgr.addGDM(state, ChrootChecker::getTag(),
117*67e74705SXin Li (void*) JAIL_ENTERED);
118*67e74705SXin Li }
119*67e74705SXin Li }
120*67e74705SXin Li
121*67e74705SXin Li C.addTransition(state);
122*67e74705SXin Li }
123*67e74705SXin Li
124*67e74705SXin Li // Check the jail state before any function call except chroot and chdir().
checkPreStmt(const CallExpr * CE,CheckerContext & C) const125*67e74705SXin Li void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
126*67e74705SXin Li const FunctionDecl *FD = C.getCalleeDecl(CE);
127*67e74705SXin Li if (!FD)
128*67e74705SXin Li return;
129*67e74705SXin Li
130*67e74705SXin Li ASTContext &Ctx = C.getASTContext();
131*67e74705SXin Li if (!II_chroot)
132*67e74705SXin Li II_chroot = &Ctx.Idents.get("chroot");
133*67e74705SXin Li if (!II_chdir)
134*67e74705SXin Li II_chdir = &Ctx.Idents.get("chdir");
135*67e74705SXin Li
136*67e74705SXin Li // Ingnore chroot and chdir.
137*67e74705SXin Li if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
138*67e74705SXin Li return;
139*67e74705SXin Li
140*67e74705SXin Li // If jail state is ROOT_CHANGED, generate BugReport.
141*67e74705SXin Li void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
142*67e74705SXin Li if (k)
143*67e74705SXin Li if (isRootChanged((intptr_t) *k))
144*67e74705SXin Li if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
145*67e74705SXin Li if (!BT_BreakJail)
146*67e74705SXin Li BT_BreakJail.reset(new BuiltinBug(
147*67e74705SXin Li this, "Break out of jail", "No call of chdir(\"/\") immediately "
148*67e74705SXin Li "after chroot"));
149*67e74705SXin Li C.emitReport(llvm::make_unique<BugReport>(
150*67e74705SXin Li *BT_BreakJail, BT_BreakJail->getDescription(), N));
151*67e74705SXin Li }
152*67e74705SXin Li }
153*67e74705SXin Li
registerChrootChecker(CheckerManager & mgr)154*67e74705SXin Li void ento::registerChrootChecker(CheckerManager &mgr) {
155*67e74705SXin Li mgr.registerChecker<ChrootChecker>();
156*67e74705SXin Li }
157