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