Clang Project

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