xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (revision 11bd3e5c6549a4983be454ccfbeb16e88c9532db)
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 
23 using namespace clang;
24 using namespace ento;
25 
26 namespace {
27 
28 /// Full state information about a stream pointer.
29 struct StreamState {
30   /// State of a stream symbol.
31   /// FIXME: We need maybe an "escaped" state later.
32   enum KindTy {
33     Opened, /// Stream is opened.
34     Closed, /// Closed stream (an invalid stream pointer after it was closed).
35     OpenFailed /// The last open operation has failed.
36   } State;
37 
38   /// The error state of a stream.
39   /// Valid only if the stream is opened.
40   /// It is assumed that feof and ferror flags are never true at the same time.
41   enum ErrorKindTy {
42     /// No error flag is set (or stream is not open).
43     NoError,
44     /// EOF condition (`feof` is true).
45     FEof,
46     /// Other generic (non-EOF) error (`ferror` is true).
47     FError,
48   } ErrorState = NoError;
49 
50   bool isOpened() const { return State == Opened; }
51   bool isClosed() const { return State == Closed; }
52   bool isOpenFailed() const { return State == OpenFailed; }
53 
54   bool isNoError() const {
55     assert(State == Opened && "Error undefined for closed stream.");
56     return ErrorState == NoError;
57   }
58   bool isFEof() const {
59     assert(State == Opened && "Error undefined for closed stream.");
60     return ErrorState == FEof;
61   }
62   bool isFError() const {
63     assert(State == Opened && "Error undefined for closed stream.");
64     return ErrorState == FError;
65   }
66 
67   bool operator==(const StreamState &X) const {
68     // In not opened state error should always NoError.
69     return State == X.State && ErrorState == X.ErrorState;
70   }
71 
72   static StreamState getOpened() { return StreamState{Opened}; }
73   static StreamState getClosed() { return StreamState{Closed}; }
74   static StreamState getOpenFailed() { return StreamState{OpenFailed}; }
75   static StreamState getOpenedWithFEof() { return StreamState{Opened, FEof}; }
76   static StreamState getOpenedWithFError() {
77     return StreamState{Opened, FError};
78   }
79 
80   void Profile(llvm::FoldingSetNodeID &ID) const {
81     ID.AddInteger(State);
82     ID.AddInteger(ErrorState);
83   }
84 };
85 
86 class StreamChecker;
87 struct FnDescription;
88 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
89                                    const CallEvent &, CheckerContext &)>;
90 
91 using ArgNoTy = unsigned int;
92 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
93 
94 struct FnDescription {
95   FnCheck PreFn;
96   FnCheck EvalFn;
97   ArgNoTy StreamArgNo;
98 };
99 
100 /// Get the value of the stream argument out of the passed call event.
101 /// The call should contain a function that is described by Desc.
102 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {
103   assert(Desc && Desc->StreamArgNo != ArgNone &&
104          "Try to get a non-existing stream argument.");
105   return Call.getArgSVal(Desc->StreamArgNo);
106 }
107 
108 /// Create a conjured symbol return value for a call expression.
109 DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) {
110   assert(CE && "Expecting a call expression.");
111 
112   const LocationContext *LCtx = C.getLocationContext();
113   return C.getSValBuilder()
114       .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
115       .castAs<DefinedSVal>();
116 }
117 
118 class StreamChecker
119     : public Checker<check::PreCall, eval::Call, check::DeadSymbols> {
120   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
121       BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak;
122 
123 public:
124   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
125   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
126   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
127 
128   /// If true, evaluate special testing stream functions.
129   bool TestMode = false;
130 
131 private:
132   CallDescriptionMap<FnDescription> FnDescriptions = {
133       {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
134       {{"freopen", 3},
135        {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
136       {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
137       {{"fclose", 1},
138        {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
139       {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3}},
140       {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3}},
141       {{"fseek", 3}, {&StreamChecker::preFseek, nullptr, 0}},
142       {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}},
143       {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}},
144       {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
145       {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
146       {{"clearerr", 1},
147        {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
148       {{"feof", 1},
149        {&StreamChecker::preDefault,
150         &StreamChecker::evalFeofFerror<&StreamState::isFEof>, 0}},
151       {{"ferror", 1},
152        {&StreamChecker::preDefault,
153         &StreamChecker::evalFeofFerror<&StreamState::isFError>, 0}},
154       {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}},
155   };
156 
157   CallDescriptionMap<FnDescription> FnTestDescriptions = {
158       {{"StreamTesterChecker_make_feof_stream", 1},
159        {nullptr,
160         &StreamChecker::evalSetFeofFerror<&StreamState::getOpenedWithFEof>, 0}},
161       {{"StreamTesterChecker_make_ferror_stream", 1},
162        {nullptr,
163         &StreamChecker::evalSetFeofFerror<&StreamState::getOpenedWithFError>,
164         0}},
165   };
166 
167   void evalFopen(const FnDescription *Desc, const CallEvent &Call,
168                  CheckerContext &C) const;
169 
170   void preFreopen(const FnDescription *Desc, const CallEvent &Call,
171                   CheckerContext &C) const;
172   void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
173                    CheckerContext &C) const;
174 
175   void evalFclose(const FnDescription *Desc, const CallEvent &Call,
176                   CheckerContext &C) const;
177 
178   void preFseek(const FnDescription *Desc, const CallEvent &Call,
179                 CheckerContext &C) const;
180 
181   void preDefault(const FnDescription *Desc, const CallEvent &Call,
182                   CheckerContext &C) const;
183 
184   void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
185                     CheckerContext &C) const;
186 
187   template <bool (StreamState::*IsOfError)() const>
188   void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
189                       CheckerContext &C) const;
190 
191   template <StreamState (*GetState)()>
192   void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
193                          CheckerContext &C) const;
194 
195   /// Check that the stream (in StreamVal) is not NULL.
196   /// If it can only be NULL a fatal error is emitted and nullptr returned.
197   /// Otherwise the return value is a new state where the stream is constrained
198   /// to be non-null.
199   ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C,
200                                       ProgramStateRef State) const;
201 
202   /// Check that the stream is the opened state.
203   /// If the stream is known to be not opened an error is generated
204   /// and nullptr returned, otherwise the original state is returned.
205   ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
206                                      ProgramStateRef State) const;
207 
208   /// Check the legality of the 'whence' argument of 'fseek'.
209   /// Generate error and return nullptr if it is found to be illegal.
210   /// Otherwise returns the state.
211   /// (State is not changed here because the "whence" value is already known.)
212   ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
213                                            ProgramStateRef State) const;
214 
215   /// Find the description data of the function called by a call event.
216   /// Returns nullptr if no function is recognized.
217   const FnDescription *lookupFn(const CallEvent &Call) const {
218     // Recognize "global C functions" with only integral or pointer arguments
219     // (and matching name) as stream functions.
220     if (!Call.isGlobalCFunction())
221       return nullptr;
222     for (auto P : Call.parameters()) {
223       QualType T = P->getType();
224       if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
225         return nullptr;
226     }
227 
228     return FnDescriptions.lookup(Call);
229   }
230 };
231 
232 } // end anonymous namespace
233 
234 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
235 
236 inline void assertStreamStateOpened(const StreamState *SS) {
237   assert(SS->isOpened() &&
238          "Previous create of error node for non-opened stream failed?");
239 }
240 
241 void StreamChecker::checkPreCall(const CallEvent &Call,
242                                  CheckerContext &C) const {
243   const FnDescription *Desc = lookupFn(Call);
244   if (!Desc || !Desc->PreFn)
245     return;
246 
247   Desc->PreFn(this, Desc, Call, C);
248 }
249 
250 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
251   const FnDescription *Desc = lookupFn(Call);
252   if (!Desc && TestMode)
253     Desc = FnTestDescriptions.lookup(Call);
254   if (!Desc || !Desc->EvalFn)
255     return false;
256 
257   Desc->EvalFn(this, Desc, Call, C);
258 
259   return C.isDifferent();
260 }
261 
262 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
263                               CheckerContext &C) const {
264   ProgramStateRef State = C.getState();
265   const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
266   if (!CE)
267     return;
268 
269   DefinedSVal RetVal = makeRetVal(C, CE);
270   SymbolRef RetSym = RetVal.getAsSymbol();
271   assert(RetSym && "RetVal must be a symbol here.");
272 
273   State = State->BindExpr(CE, C.getLocationContext(), RetVal);
274 
275   // Bifurcate the state into two: one with a valid FILE* pointer, the other
276   // with a NULL.
277   ProgramStateRef StateNotNull, StateNull;
278   std::tie(StateNotNull, StateNull) =
279       C.getConstraintManager().assumeDual(State, RetVal);
280 
281   StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened());
282   StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed());
283 
284   C.addTransition(StateNotNull);
285   C.addTransition(StateNull);
286 }
287 
288 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
289                                CheckerContext &C) const {
290   // Do not allow NULL as passed stream pointer but allow a closed stream.
291   ProgramStateRef State = C.getState();
292   State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State);
293   if (!State)
294     return;
295 
296   C.addTransition(State);
297 }
298 
299 void StreamChecker::evalFreopen(const FnDescription *Desc,
300                                 const CallEvent &Call,
301                                 CheckerContext &C) const {
302   ProgramStateRef State = C.getState();
303 
304   auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
305   if (!CE)
306     return;
307 
308   Optional<DefinedSVal> StreamVal =
309       getStreamArg(Desc, Call).getAs<DefinedSVal>();
310   if (!StreamVal)
311     return;
312 
313   SymbolRef StreamSym = StreamVal->getAsSymbol();
314   // Do not care about concrete values for stream ("(FILE *)0x12345"?).
315   // FIXME: Are stdin, stdout, stderr such values?
316   if (!StreamSym)
317     return;
318 
319   // Generate state for non-failed case.
320   // Return value is the passed stream pointer.
321   // According to the documentations, the stream is closed first
322   // but any close error is ignored. The state changes to (or remains) opened.
323   ProgramStateRef StateRetNotNull =
324       State->BindExpr(CE, C.getLocationContext(), *StreamVal);
325   // Generate state for NULL return value.
326   // Stream switches to OpenFailed state.
327   ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(),
328                                                  C.getSValBuilder().makeNull());
329 
330   StateRetNotNull =
331       StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
332   StateRetNull =
333       StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
334 
335   C.addTransition(StateRetNotNull);
336   C.addTransition(StateRetNull);
337 }
338 
339 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
340                                CheckerContext &C) const {
341   ProgramStateRef State = C.getState();
342   SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
343   if (!Sym)
344     return;
345 
346   const StreamState *SS = State->get<StreamMap>(Sym);
347   if (!SS)
348     return;
349 
350   assertStreamStateOpened(SS);
351 
352   // Close the File Descriptor.
353   // Regardless if the close fails or not, stream becomes "closed"
354   // and can not be used any more.
355   State = State->set<StreamMap>(Sym, StreamState::getClosed());
356 
357   C.addTransition(State);
358 }
359 
360 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
361                              CheckerContext &C) const {
362   ProgramStateRef State = C.getState();
363   SVal StreamVal = getStreamArg(Desc, Call);
364   State = ensureStreamNonNull(StreamVal, C, State);
365   if (!State)
366     return;
367   State = ensureStreamOpened(StreamVal, C, State);
368   if (!State)
369     return;
370   State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
371   if (!State)
372     return;
373 
374   C.addTransition(State);
375 }
376 
377 void StreamChecker::evalClearerr(const FnDescription *Desc,
378                                  const CallEvent &Call,
379                                  CheckerContext &C) const {
380   ProgramStateRef State = C.getState();
381   SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
382   if (!StreamSym)
383     return;
384 
385   const StreamState *SS = State->get<StreamMap>(StreamSym);
386   if (!SS)
387     return;
388 
389   assertStreamStateOpened(SS);
390 
391   if (SS->isNoError())
392     return;
393 
394   State = State->set<StreamMap>(StreamSym, StreamState::getOpened());
395   C.addTransition(State);
396 }
397 
398 template <bool (StreamState::*IsOfError)() const>
399 void StreamChecker::evalFeofFerror(const FnDescription *Desc,
400                                    const CallEvent &Call,
401                                    CheckerContext &C) const {
402   ProgramStateRef State = C.getState();
403   SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
404   if (!StreamSym)
405     return;
406 
407   const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
408   if (!CE)
409     return;
410 
411   const StreamState *SS = State->get<StreamMap>(StreamSym);
412   // Ignore the call if the stream is not tracked.
413   if (!SS)
414     return;
415 
416   assertStreamStateOpened(SS);
417 
418   if ((SS->*IsOfError)()) {
419     // Function returns nonzero.
420     DefinedSVal RetVal = makeRetVal(C, CE);
421     State = State->BindExpr(CE, C.getLocationContext(), RetVal);
422     State = State->assume(RetVal, true);
423     assert(State && "Assumption on new value should not fail.");
424   } else {
425     // Return zero.
426     State = State->BindExpr(CE, C.getLocationContext(),
427                             C.getSValBuilder().makeIntVal(0, false));
428   }
429   C.addTransition(State);
430 }
431 
432 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
433                                CheckerContext &C) const {
434   ProgramStateRef State = C.getState();
435   SVal StreamVal = getStreamArg(Desc, Call);
436   State = ensureStreamNonNull(StreamVal, C, State);
437   if (!State)
438     return;
439   State = ensureStreamOpened(StreamVal, C, State);
440   if (!State)
441     return;
442 
443   C.addTransition(State);
444 }
445 
446 template <StreamState (*GetState)()>
447 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
448                                       const CallEvent &Call,
449                                       CheckerContext &C) const {
450   ProgramStateRef State = C.getState();
451   SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
452   assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
453   State = State->set<StreamMap>(StreamSym, (*GetState)());
454   C.addTransition(State);
455 }
456 
457 ProgramStateRef
458 StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C,
459                                    ProgramStateRef State) const {
460   auto Stream = StreamVal.getAs<DefinedSVal>();
461   if (!Stream)
462     return State;
463 
464   ConstraintManager &CM = C.getConstraintManager();
465 
466   ProgramStateRef StateNotNull, StateNull;
467   std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream);
468 
469   if (!StateNotNull && StateNull) {
470     if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
471       if (!BT_nullfp)
472         BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
473                                        "Stream pointer might be NULL."));
474       C.emitReport(std::make_unique<PathSensitiveBugReport>(
475           *BT_nullfp, BT_nullfp->getDescription(), N));
476     }
477     return nullptr;
478   }
479 
480   return StateNotNull;
481 }
482 
483 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
484                                                   CheckerContext &C,
485                                                   ProgramStateRef State) const {
486   SymbolRef Sym = StreamVal.getAsSymbol();
487   if (!Sym)
488     return State;
489 
490   const StreamState *SS = State->get<StreamMap>(Sym);
491   if (!SS)
492     return State;
493 
494   if (SS->isClosed()) {
495     // Using a stream pointer after 'fclose' causes undefined behavior
496     // according to cppreference.com .
497     ExplodedNode *N = C.generateErrorNode();
498     if (N) {
499       if (!BT_UseAfterClose)
500         BT_UseAfterClose.reset(new BuiltinBug(this, "Closed stream",
501                                               "Stream might be already closed. "
502                                               "Causes undefined behaviour."));
503       C.emitReport(std::make_unique<PathSensitiveBugReport>(
504           *BT_UseAfterClose, BT_UseAfterClose->getDescription(), N));
505       return nullptr;
506     }
507 
508     return State;
509   }
510 
511   if (SS->isOpenFailed()) {
512     // Using a stream that has failed to open is likely to cause problems.
513     // This should usually not occur because stream pointer is NULL.
514     // But freopen can cause a state when stream pointer remains non-null but
515     // failed to open.
516     ExplodedNode *N = C.generateErrorNode();
517     if (N) {
518       if (!BT_UseAfterOpenFailed)
519         BT_UseAfterOpenFailed.reset(
520             new BuiltinBug(this, "Invalid stream",
521                            "Stream might be invalid after "
522                            "(re-)opening it has failed. "
523                            "Can cause undefined behaviour."));
524       C.emitReport(std::make_unique<PathSensitiveBugReport>(
525           *BT_UseAfterOpenFailed, BT_UseAfterOpenFailed->getDescription(), N));
526       return nullptr;
527     }
528     return State;
529   }
530 
531   return State;
532 }
533 
534 ProgramStateRef
535 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
536                                         ProgramStateRef State) const {
537   Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>();
538   if (!CI)
539     return State;
540 
541   int64_t X = CI->getValue().getSExtValue();
542   if (X >= 0 && X <= 2)
543     return State;
544 
545   if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
546     if (!BT_illegalwhence)
547       BT_illegalwhence.reset(
548           new BuiltinBug(this, "Illegal whence argument",
549                          "The whence argument to fseek() should be "
550                          "SEEK_SET, SEEK_END, or SEEK_CUR."));
551     C.emitReport(std::make_unique<PathSensitiveBugReport>(
552         *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
553     return nullptr;
554   }
555 
556   return State;
557 }
558 
559 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
560                                      CheckerContext &C) const {
561   ProgramStateRef State = C.getState();
562 
563   // TODO: Clean up the state.
564   const StreamMapTy &Map = State->get<StreamMap>();
565   for (const auto &I : Map) {
566     SymbolRef Sym = I.first;
567     const StreamState &SS = I.second;
568     if (!SymReaper.isDead(Sym) || !SS.isOpened())
569       continue;
570 
571     ExplodedNode *N = C.generateErrorNode();
572     if (!N)
573       continue;
574 
575     if (!BT_ResourceLeak)
576       BT_ResourceLeak.reset(
577           new BuiltinBug(this, "Resource Leak",
578                          "Opened File never closed. Potential Resource leak."));
579     C.emitReport(std::make_unique<PathSensitiveBugReport>(
580         *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
581   }
582 }
583 
584 void ento::registerStreamChecker(CheckerManager &Mgr) {
585   Mgr.registerChecker<StreamChecker>();
586 }
587 
588 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
589   return true;
590 }
591 
592 void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
593   auto *Checker = Mgr.getChecker<StreamChecker>();
594   Checker->TestMode = true;
595 }
596 
597 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
598   return true;
599 }
600 
601