1//===- VforkChecker.cpp -------- Vfork usage 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 vfork checker which checks for dangerous uses of vfork.
11//  Vforked process shares memory (including stack) with parent so it's
12//  range of actions is significantly limited: can't write variables,
13//  can't call functions not in whitelist, etc. For more details, see
14//  http://man7.org/linux/man-pages/man2/vfork.2.html
15//
16//  This checker checks for prohibited constructs in vforked process.
17//  The state transition diagram:
18//  PARENT ---(vfork() == 0)--> CHILD
19//                                   |
20//                                   --(*p = ...)--> bug
21//                                   |
22//                                   --foo()--> bug
23//                                   |
24//                                   --return--> bug
25//
26//===----------------------------------------------------------------------===//
27
28#include "ClangSACheckers.h"
29#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
30#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
31#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
32#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
33#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
34#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
35#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
36#include "clang/StaticAnalyzer/Core/Checker.h"
37#include "clang/StaticAnalyzer/Core/CheckerManager.h"
38#include "clang/AST/ParentMap.h"
39
40using namespace clang;
41using namespace ento;
42
43namespace {
44
45class VforkChecker : public Checker<check::PreCall, check::PostCall,
46                                    check::Bind, check::PreStmt<ReturnStmt>> {
47  mutable std::unique_ptr<BuiltinBug> BT;
48  mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkWhitelist;
49  mutable const IdentifierInfo *II_vfork;
50
51  static bool isChildProcess(const ProgramStateRef State);
52
53  bool isVforkCall(const Decl *D, CheckerContext &C) const;
54  bool isCallWhitelisted(const IdentifierInfo *II, CheckerContext &C) const;
55
56  void reportBug(const char *What, CheckerContext &C,
57                 const char *Details = nullptr) const;
58
59public:
60  VforkChecker() : II_vfork(nullptr) {}
61
62  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
63  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
64  void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
65  void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
66};
67
68} // end anonymous namespace
69
70// This trait holds region of variable that is assigned with vfork's
71// return value (this is the only region child is allowed to write).
72// VFORK_RESULT_INVALID means that we are in parent process.
73// VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.
74// Other values point to valid regions.
75REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)
76#define VFORK_RESULT_INVALID 0
77#define VFORK_RESULT_NONE ((void *)(uintptr_t)1)
78
79bool VforkChecker::isChildProcess(const ProgramStateRef State) {
80  return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;
81}
82
83bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {
84  auto FD = dyn_cast_or_null<FunctionDecl>(D);
85  if (!FD || !C.isCLibraryFunction(FD))
86    return false;
87
88  if (!II_vfork) {
89    ASTContext &AC = C.getASTContext();
90    II_vfork = &AC.Idents.get("vfork");
91  }
92
93  return FD->getIdentifier() == II_vfork;
94}
95
96// Returns true iff ok to call function after successful vfork.
97bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II,
98                                 CheckerContext &C) const {
99  if (VforkWhitelist.empty()) {
100    // According to manpage.
101    const char *ids[] = {
102      "_exit",
103      "_Exit",
104      "execl",
105      "execlp",
106      "execle",
107      "execv",
108      "execvp",
109      "execvpe",
110      nullptr
111    };
112
113    ASTContext &AC = C.getASTContext();
114    for (const char **id = ids; *id; ++id)
115      VforkWhitelist.insert(&AC.Idents.get(*id));
116  }
117
118  return VforkWhitelist.count(II);
119}
120
121void VforkChecker::reportBug(const char *What, CheckerContext &C,
122                             const char *Details) const {
123  if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
124    if (!BT)
125      BT.reset(new BuiltinBug(this,
126                              "Dangerous construct in a vforked process"));
127
128    SmallString<256> buf;
129    llvm::raw_svector_ostream os(buf);
130
131    os << What << " is prohibited after a successful vfork";
132
133    if (Details)
134      os << "; " << Details;
135
136    auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N);
137    // TODO: mark vfork call in BugReportVisitor
138    C.emitReport(std::move(Report));
139  }
140}
141
142// Detect calls to vfork and split execution appropriately.
143void VforkChecker::checkPostCall(const CallEvent &Call,
144                                 CheckerContext &C) const {
145  // We can't call vfork in child so don't bother
146  // (corresponding warning has already been emitted in checkPreCall).
147  ProgramStateRef State = C.getState();
148  if (isChildProcess(State))
149    return;
150
151  if (!isVforkCall(Call.getDecl(), C))
152    return;
153
154  // Get return value of vfork.
155  SVal VforkRetVal = Call.getReturnValue();
156  Optional<DefinedOrUnknownSVal> DVal =
157    VforkRetVal.getAs<DefinedOrUnknownSVal>();
158  if (!DVal)
159    return;
160
161  // Get assigned variable.
162  const ParentMap &PM = C.getLocationContext()->getParentMap();
163  const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());
164  const VarDecl *LhsDecl;
165  std::tie(LhsDecl, std::ignore) = parseAssignment(P);
166
167  // Get assigned memory region.
168  MemRegionManager &M = C.getStoreManager().getRegionManager();
169  const MemRegion *LhsDeclReg =
170    LhsDecl
171      ? M.getVarRegion(LhsDecl, C.getLocationContext())
172      : (const MemRegion *)VFORK_RESULT_NONE;
173
174  // Parent branch gets nonzero return value (according to manpage).
175  ProgramStateRef ParentState, ChildState;
176  std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);
177  C.addTransition(ParentState);
178  ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);
179  C.addTransition(ChildState);
180}
181
182// Prohibit calls to non-whitelist functions in child process.
183void VforkChecker::checkPreCall(const CallEvent &Call,
184                                CheckerContext &C) const {
185  ProgramStateRef State = C.getState();
186  if (isChildProcess(State)
187      && !isCallWhitelisted(Call.getCalleeIdentifier(), C))
188    reportBug("This function call", C);
189}
190
191// Prohibit writes in child process (except for vfork's lhs).
192void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S,
193                             CheckerContext &C) const {
194  ProgramStateRef State = C.getState();
195  if (!isChildProcess(State))
196    return;
197
198  const MemRegion *VforkLhs =
199    static_cast<const MemRegion *>(State->get<VforkResultRegion>());
200  const MemRegion *MR = L.getAsRegion();
201
202  // Child is allowed to modify only vfork's lhs.
203  if (!MR || MR == VforkLhs)
204    return;
205
206  reportBug("This assignment", C);
207}
208
209// Prohibit return from function in child process.
210void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
211  ProgramStateRef State = C.getState();
212  if (isChildProcess(State))
213    reportBug("Return", C, "call _exit() instead");
214}
215
216void ento::registerVforkChecker(CheckerManager &mgr) {
217  mgr.registerChecker<VforkChecker>();
218}
219