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