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