Coverage Report

Created: 2023-09-21 18:56

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