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