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