1//===-- StreamChecker.cpp -----------------------------------------*- 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 checkers that model and check stream handling functions.
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/CallEvent.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 <functional>
23
24using namespace clang;
25using namespace ento;
26using namespace std::placeholders;
27
28namespace {
29
30struct StreamState {
31  enum Kind { Opened, Closed, OpenFailed, Escaped } K;
32
33  StreamState(Kind k) : K(k) {}
34
35  bool isOpened() const { return K == Opened; }
36  bool isClosed() const { return K == Closed; }
37  //bool isOpenFailed() const { return K == OpenFailed; }
38  //bool isEscaped() const { return K == Escaped; }
39
40  bool operator==(const StreamState &X) const { return K == X.K; }
41
42  static StreamState getOpened() { return StreamState(Opened); }
43  static StreamState getClosed() { return StreamState(Closed); }
44  static StreamState getOpenFailed() { return StreamState(OpenFailed); }
45  static StreamState getEscaped() { return StreamState(Escaped); }
46
47  void Profile(llvm::FoldingSetNodeID &ID) const {
48    ID.AddInteger(K);
49  }
50};
51
52class StreamChecker : public Checker<eval::Call,
53                                     check::DeadSymbols > {
54  mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
55      BT_doubleclose, BT_ResourceLeak;
56
57public:
58  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
59  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
60
61private:
62  using FnCheck = std::function<void(const StreamChecker *, const CallEvent &,
63                                     CheckerContext &)>;
64
65  CallDescriptionMap<FnCheck> Callbacks = {
66      {{"fopen"}, &StreamChecker::evalFopen},
67      {{"freopen", 3}, &StreamChecker::evalFreopen},
68      {{"tmpfile"}, &StreamChecker::evalFopen},
69      {{"fclose", 1}, &StreamChecker::evalFclose},
70      {{"fread", 4},
71       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
72      {{"fwrite", 4},
73       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
74      {{"fseek", 3}, &StreamChecker::evalFseek},
75      {{"ftell", 1},
76       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
77      {{"rewind", 1},
78       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
79      {{"fgetpos", 2},
80       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
81      {{"fsetpos", 2},
82       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
83      {{"clearerr", 1},
84       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
85      {{"feof", 1},
86       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
87      {{"ferror", 1},
88       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
89      {{"fileno", 1},
90       std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
91  };
92
93  void evalFopen(const CallEvent &Call, CheckerContext &C) const;
94  void evalFreopen(const CallEvent &Call, CheckerContext &C) const;
95  void evalFclose(const CallEvent &Call, CheckerContext &C) const;
96  void evalFseek(const CallEvent &Call, CheckerContext &C) const;
97
98  void checkArgNullStream(const CallEvent &Call, CheckerContext &C,
99                          unsigned ArgI) const;
100  bool checkNullStream(SVal SV, CheckerContext &C,
101                       ProgramStateRef &State) const;
102  void checkFseekWhence(SVal SV, CheckerContext &C,
103                        ProgramStateRef &State) const;
104  bool checkDoubleClose(const CallEvent &Call, CheckerContext &C,
105                        ProgramStateRef &State) const;
106};
107
108} // end anonymous namespace
109
110REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
111
112
113bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
114  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
115  if (!FD || FD->getKind() != Decl::Function)
116    return false;
117
118  // Recognize "global C functions" with only integral or pointer arguments
119  // (and matching name) as stream functions.
120  if (!Call.isGlobalCFunction())
121    return false;
122  for (auto P : Call.parameters()) {
123    QualType T = P->getType();
124    if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
125      return false;
126  }
127
128  const FnCheck *Callback = Callbacks.lookup(Call);
129  if (!Callback)
130    return false;
131
132  (*Callback)(this, Call, C);
133
134  return C.isDifferent();
135}
136
137void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const {
138  ProgramStateRef state = C.getState();
139  SValBuilder &svalBuilder = C.getSValBuilder();
140  const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
141  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
142  if (!CE)
143    return;
144
145  DefinedSVal RetVal =
146      svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
147          .castAs<DefinedSVal>();
148  state = state->BindExpr(CE, C.getLocationContext(), RetVal);
149
150  ConstraintManager &CM = C.getConstraintManager();
151  // Bifurcate the state into two: one with a valid FILE* pointer, the other
152  // with a NULL.
153  ProgramStateRef stateNotNull, stateNull;
154  std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
155
156  SymbolRef Sym = RetVal.getAsSymbol();
157  assert(Sym && "RetVal must be a symbol here.");
158  stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened());
159  stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed());
160
161  C.addTransition(stateNotNull);
162  C.addTransition(stateNull);
163}
164
165void StreamChecker::evalFreopen(const CallEvent &Call,
166                                CheckerContext &C) const {
167  ProgramStateRef State = C.getState();
168
169  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
170  if (!CE)
171    return;
172
173  Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>();
174  if (!StreamVal)
175    return;
176  // Do not allow NULL as passed stream pointer.
177  // This is not specified in the man page but may crash on some system.
178  checkNullStream(*StreamVal, C, State);
179  // Check if error was generated.
180  if (C.isDifferent())
181    return;
182
183  SymbolRef StreamSym = StreamVal->getAsSymbol();
184  // Do not care about special values for stream ("(FILE *)0x12345"?).
185  if (!StreamSym)
186    return;
187
188  // Generate state for non-failed case.
189  // Return value is the passed stream pointer.
190  // According to the documentations, the stream is closed first
191  // but any close error is ignored. The state changes to (or remains) opened.
192  ProgramStateRef StateRetNotNull =
193      State->BindExpr(CE, C.getLocationContext(), *StreamVal);
194  // Generate state for NULL return value.
195  // Stream switches to OpenFailed state.
196  ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(),
197                                                 C.getSValBuilder().makeNull());
198
199  StateRetNotNull =
200      StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
201  StateRetNull =
202      StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
203
204  C.addTransition(StateRetNotNull);
205  C.addTransition(StateRetNull);
206}
207
208void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const {
209  ProgramStateRef State = C.getState();
210  if (checkDoubleClose(Call, C, State))
211    C.addTransition(State);
212}
213
214void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const {
215  const Expr *AE2 = Call.getArgExpr(2);
216  if (!AE2)
217    return;
218
219  ProgramStateRef State = C.getState();
220
221  bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State);
222  // Check if error was generated.
223  if (C.isDifferent())
224    return;
225
226  // Check the legality of the 'whence' argument of 'fseek'.
227  checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State);
228
229  if (!C.isDifferent() && StateChanged)
230    C.addTransition(State);
231
232  return;
233}
234
235void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C,
236                                       unsigned ArgI) const {
237  ProgramStateRef State = C.getState();
238  if (checkNullStream(Call.getArgSVal(ArgI), C, State))
239    C.addTransition(State);
240}
241
242bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C,
243                                    ProgramStateRef &State) const {
244  Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
245  if (!DV)
246    return false;
247
248  ConstraintManager &CM = C.getConstraintManager();
249  ProgramStateRef StateNotNull, StateNull;
250  std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV);
251
252  if (!StateNotNull && StateNull) {
253    if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
254      if (!BT_nullfp)
255        BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
256                                       "Stream pointer might be NULL."));
257      C.emitReport(std::make_unique<PathSensitiveBugReport>(
258          *BT_nullfp, BT_nullfp->getDescription(), N));
259    }
260    return false;
261  }
262
263  if (StateNotNull) {
264    State = StateNotNull;
265    return true;
266  }
267
268  return false;
269}
270
271void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C,
272                                     ProgramStateRef &State) const {
273  Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>();
274  if (!CI)
275    return;
276
277  int64_t X = CI->getValue().getSExtValue();
278  if (X >= 0 && X <= 2)
279    return;
280
281  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
282    if (!BT_illegalwhence)
283      BT_illegalwhence.reset(
284          new BuiltinBug(this, "Illegal whence argument",
285                         "The whence argument to fseek() should be "
286                         "SEEK_SET, SEEK_END, or SEEK_CUR."));
287    C.emitReport(std::make_unique<PathSensitiveBugReport>(
288        *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
289  }
290}
291
292bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C,
293                                     ProgramStateRef &State) const {
294  SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
295  if (!Sym)
296    return false;
297
298  const StreamState *SS = State->get<StreamMap>(Sym);
299
300  // If the file stream is not tracked, return.
301  if (!SS)
302    return false;
303
304  // Check: Double close a File Descriptor could cause undefined behaviour.
305  // Conforming to man-pages
306  if (SS->isClosed()) {
307    ExplodedNode *N = C.generateErrorNode();
308    if (N) {
309      if (!BT_doubleclose)
310        BT_doubleclose.reset(new BuiltinBug(
311            this, "Double fclose", "Try to close a file Descriptor already"
312                                   " closed. Cause undefined behaviour."));
313      C.emitReport(std::make_unique<PathSensitiveBugReport>(
314          *BT_doubleclose, BT_doubleclose->getDescription(), N));
315    }
316    return false;
317  }
318
319  // Close the File Descriptor.
320  State = State->set<StreamMap>(Sym, StreamState::getClosed());
321
322  return true;
323}
324
325void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
326                                     CheckerContext &C) const {
327  ProgramStateRef State = C.getState();
328
329  // TODO: Clean up the state.
330  const StreamMapTy &Map = State->get<StreamMap>();
331  for (const auto &I: Map) {
332    SymbolRef Sym = I.first;
333    const StreamState &SS = I.second;
334    if (!SymReaper.isDead(Sym) || !SS.isOpened())
335      continue;
336
337    ExplodedNode *N = C.generateErrorNode();
338    if (!N)
339      continue;
340
341    if (!BT_ResourceLeak)
342      BT_ResourceLeak.reset(
343          new BuiltinBug(this, "Resource Leak",
344                         "Opened File never closed. Potential Resource leak."));
345    C.emitReport(std::make_unique<PathSensitiveBugReport>(
346        *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
347  }
348}
349
350void ento::registerStreamChecker(CheckerManager &mgr) {
351  mgr.registerChecker<StreamChecker>();
352}
353
354bool ento::shouldRegisterStreamChecker(const LangOptions &LO) {
355  return true;
356}
357