xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (revision ce1a86251bb42fbbc7cc21feb416ed7a0bf411b7)
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 
24 using namespace clang;
25 using namespace ento;
26 using namespace std::placeholders;
27 
28 namespace {
29 
30 struct 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 
52 class StreamChecker;
53 struct FnDescription;
54 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
55                                    const CallEvent &, CheckerContext &)>;
56 
57 using ArgNoTy = unsigned int;
58 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
59 
60 struct FnDescription {
61   FnCheck PreFn;
62   FnCheck EvalFn;
63   ArgNoTy StreamArgNo;
64 };
65 
66 /// Get the value of the stream argument out of the passed call event.
67 /// The call should contain a function that is described by Desc.
68 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {
69   assert(Desc && Desc->StreamArgNo != ArgNone &&
70          "Try to get a non-existing stream argument.");
71   return Call.getArgSVal(Desc->StreamArgNo);
72 }
73 
74 class StreamChecker
75     : public Checker<check::PreCall, eval::Call, check::DeadSymbols> {
76   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
77       BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak;
78 
79 public:
80   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
81   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
82   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
83 
84 private:
85   CallDescriptionMap<FnDescription> FnDescriptions = {
86       {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
87       {{"freopen", 3},
88        {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
89       {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
90       {{"fclose", 1},
91        {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
92       {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3}},
93       {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3}},
94       {{"fseek", 3}, {&StreamChecker::preFseek, nullptr, 0}},
95       {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}},
96       {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}},
97       {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
98       {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
99       {{"clearerr", 1}, {&StreamChecker::preDefault, nullptr, 0}},
100       {{"feof", 1}, {&StreamChecker::preDefault, nullptr, 0}},
101       {{"ferror", 1}, {&StreamChecker::preDefault, nullptr, 0}},
102       {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}},
103   };
104 
105   void evalFopen(const FnDescription *Desc, const CallEvent &Call,
106                  CheckerContext &C) const;
107 
108   void preFreopen(const FnDescription *Desc, const CallEvent &Call,
109                   CheckerContext &C) const;
110   void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
111                    CheckerContext &C) const;
112 
113   void evalFclose(const FnDescription *Desc, const CallEvent &Call,
114                   CheckerContext &C) const;
115 
116   void preFseek(const FnDescription *Desc, const CallEvent &Call,
117                 CheckerContext &C) const;
118 
119   void preDefault(const FnDescription *Desc, const CallEvent &Call,
120                   CheckerContext &C) const;
121 
122   /// Check that the stream (in StreamVal) is not NULL.
123   /// If it can only be NULL a fatal error is emitted and nullptr returned.
124   /// Otherwise the return value is a new state where the stream is constrained
125   /// to be non-null.
126   ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C,
127                                       ProgramStateRef State) const;
128 
129   /// Check that the stream is the opened state.
130   /// If the stream is known to be not opened an error is generated
131   /// and nullptr returned, otherwise the original state is returned.
132   ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
133                                      ProgramStateRef State) const;
134 
135   /// Check the legality of the 'whence' argument of 'fseek'.
136   /// Generate error and return nullptr if it is found to be illegal.
137   /// Otherwise returns the state.
138   /// (State is not changed here because the "whence" value is already known.)
139   ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
140                                            ProgramStateRef State) const;
141 
142   /// Find the description data of the function called by a call event.
143   /// Returns nullptr if no function is recognized.
144   const FnDescription *lookupFn(const CallEvent &Call) const {
145     // Recognize "global C functions" with only integral or pointer arguments
146     // (and matching name) as stream functions.
147     if (!Call.isGlobalCFunction())
148       return nullptr;
149     for (auto P : Call.parameters()) {
150       QualType T = P->getType();
151       if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
152         return nullptr;
153     }
154 
155     return FnDescriptions.lookup(Call);
156   }
157 };
158 
159 } // end anonymous namespace
160 
161 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
162 
163 void StreamChecker::checkPreCall(const CallEvent &Call,
164                                  CheckerContext &C) const {
165   const FnDescription *Desc = lookupFn(Call);
166   if (!Desc || !Desc->PreFn)
167     return;
168 
169   Desc->PreFn(this, Desc, Call, C);
170 }
171 
172 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
173   const FnDescription *Desc = lookupFn(Call);
174   if (!Desc || !Desc->EvalFn)
175     return false;
176 
177   Desc->EvalFn(this, Desc, Call, C);
178 
179   return C.isDifferent();
180 }
181 
182 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
183                               CheckerContext &C) const {
184   ProgramStateRef State = C.getState();
185   SValBuilder &SVB = C.getSValBuilder();
186   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
187 
188   auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
189   if (!CE)
190     return;
191 
192   DefinedSVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
193                            .castAs<DefinedSVal>();
194   SymbolRef RetSym = RetVal.getAsSymbol();
195   assert(RetSym && "RetVal must be a symbol here.");
196 
197   State = State->BindExpr(CE, C.getLocationContext(), RetVal);
198 
199   // Bifurcate the state into two: one with a valid FILE* pointer, the other
200   // with a NULL.
201   ProgramStateRef StateNotNull, StateNull;
202   std::tie(StateNotNull, StateNull) =
203       C.getConstraintManager().assumeDual(State, RetVal);
204 
205   StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened());
206   StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed());
207 
208   C.addTransition(StateNotNull);
209   C.addTransition(StateNull);
210 }
211 
212 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
213                                CheckerContext &C) const {
214   // Do not allow NULL as passed stream pointer but allow a closed stream.
215   ProgramStateRef State = C.getState();
216   State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State);
217   if (!State)
218     return;
219 
220   C.addTransition(State);
221 }
222 
223 void StreamChecker::evalFreopen(const FnDescription *Desc,
224                                 const CallEvent &Call,
225                                 CheckerContext &C) const {
226   ProgramStateRef State = C.getState();
227 
228   auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
229   if (!CE)
230     return;
231 
232   Optional<DefinedSVal> StreamVal =
233       getStreamArg(Desc, Call).getAs<DefinedSVal>();
234   if (!StreamVal)
235     return;
236 
237   SymbolRef StreamSym = StreamVal->getAsSymbol();
238   // Do not care about concrete values for stream ("(FILE *)0x12345"?).
239   // FIXME: Are stdin, stdout, stderr such values?
240   if (!StreamSym)
241     return;
242 
243   // Generate state for non-failed case.
244   // Return value is the passed stream pointer.
245   // According to the documentations, the stream is closed first
246   // but any close error is ignored. The state changes to (or remains) opened.
247   ProgramStateRef StateRetNotNull =
248       State->BindExpr(CE, C.getLocationContext(), *StreamVal);
249   // Generate state for NULL return value.
250   // Stream switches to OpenFailed state.
251   ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(),
252                                                  C.getSValBuilder().makeNull());
253 
254   StateRetNotNull =
255       StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
256   StateRetNull =
257       StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
258 
259   C.addTransition(StateRetNotNull);
260   C.addTransition(StateRetNull);
261 }
262 
263 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
264                                CheckerContext &C) const {
265   ProgramStateRef State = C.getState();
266   SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
267   if (!Sym)
268     return;
269 
270   const StreamState *SS = State->get<StreamMap>(Sym);
271   if (!SS)
272     return;
273 
274   // Close the File Descriptor.
275   // Regardless if the close fails or not, stream becomes "closed"
276   // and can not be used any more.
277   State = State->set<StreamMap>(Sym, StreamState::getClosed());
278 
279   C.addTransition(State);
280 }
281 
282 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
283                              CheckerContext &C) const {
284   ProgramStateRef State = C.getState();
285   SVal StreamVal = getStreamArg(Desc, Call);
286   State = ensureStreamNonNull(StreamVal, C, State);
287   if (!State)
288     return;
289   State = ensureStreamOpened(StreamVal, C, State);
290   if (!State)
291     return;
292   State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
293   if (!State)
294     return;
295 
296   C.addTransition(State);
297 }
298 
299 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
300                                CheckerContext &C) const {
301   ProgramStateRef State = C.getState();
302   SVal StreamVal = getStreamArg(Desc, Call);
303   State = ensureStreamNonNull(StreamVal, C, State);
304   if (!State)
305     return;
306   State = ensureStreamOpened(StreamVal, C, State);
307   if (!State)
308     return;
309 
310   C.addTransition(State);
311 }
312 
313 ProgramStateRef
314 StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C,
315                                    ProgramStateRef State) const {
316   auto Stream = StreamVal.getAs<DefinedSVal>();
317   if (!Stream)
318     return State;
319 
320   ConstraintManager &CM = C.getConstraintManager();
321 
322   ProgramStateRef StateNotNull, StateNull;
323   std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream);
324 
325   if (!StateNotNull && StateNull) {
326     if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
327       if (!BT_nullfp)
328         BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
329                                        "Stream pointer might be NULL."));
330       C.emitReport(std::make_unique<PathSensitiveBugReport>(
331           *BT_nullfp, BT_nullfp->getDescription(), N));
332     }
333     return nullptr;
334   }
335 
336   return StateNotNull;
337 }
338 
339 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
340                                                   CheckerContext &C,
341                                                   ProgramStateRef State) const {
342   SymbolRef Sym = StreamVal.getAsSymbol();
343   if (!Sym)
344     return State;
345 
346   const StreamState *SS = State->get<StreamMap>(Sym);
347   if (!SS)
348     return State;
349 
350   if (SS->isClosed()) {
351     // Using a stream pointer after 'fclose' causes undefined behavior
352     // according to cppreference.com .
353     ExplodedNode *N = C.generateErrorNode();
354     if (N) {
355       if (!BT_UseAfterClose)
356         BT_UseAfterClose.reset(new BuiltinBug(this, "Closed stream",
357                                               "Stream might be already closed. "
358                                               "Causes undefined behaviour."));
359       C.emitReport(std::make_unique<PathSensitiveBugReport>(
360           *BT_UseAfterClose, BT_UseAfterClose->getDescription(), N));
361       return nullptr;
362     }
363 
364     return State;
365   }
366 
367   if (SS->isOpenFailed()) {
368     // Using a stream that has failed to open is likely to cause problems.
369     // This should usually not occur because stream pointer is NULL.
370     // But freopen can cause a state when stream pointer remains non-null but
371     // failed to open.
372     ExplodedNode *N = C.generateErrorNode();
373     if (N) {
374       if (!BT_UseAfterOpenFailed)
375         BT_UseAfterOpenFailed.reset(
376             new BuiltinBug(this, "Invalid stream",
377                            "Stream might be invalid after "
378                            "(re-)opening it has failed. "
379                            "Can cause undefined behaviour."));
380       C.emitReport(std::make_unique<PathSensitiveBugReport>(
381           *BT_UseAfterOpenFailed, BT_UseAfterOpenFailed->getDescription(), N));
382       return nullptr;
383     }
384     return State;
385   }
386 
387   return State;
388 }
389 
390 ProgramStateRef
391 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
392                                         ProgramStateRef State) const {
393   Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>();
394   if (!CI)
395     return State;
396 
397   int64_t X = CI->getValue().getSExtValue();
398   if (X >= 0 && X <= 2)
399     return State;
400 
401   if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
402     if (!BT_illegalwhence)
403       BT_illegalwhence.reset(
404           new BuiltinBug(this, "Illegal whence argument",
405                          "The whence argument to fseek() should be "
406                          "SEEK_SET, SEEK_END, or SEEK_CUR."));
407     C.emitReport(std::make_unique<PathSensitiveBugReport>(
408         *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
409     return nullptr;
410   }
411 
412   return State;
413 }
414 
415 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
416                                      CheckerContext &C) const {
417   ProgramStateRef State = C.getState();
418 
419   // TODO: Clean up the state.
420   const StreamMapTy &Map = State->get<StreamMap>();
421   for (const auto &I : Map) {
422     SymbolRef Sym = I.first;
423     const StreamState &SS = I.second;
424     if (!SymReaper.isDead(Sym) || !SS.isOpened())
425       continue;
426 
427     ExplodedNode *N = C.generateErrorNode();
428     if (!N)
429       continue;
430 
431     if (!BT_ResourceLeak)
432       BT_ResourceLeak.reset(
433           new BuiltinBug(this, "Resource Leak",
434                          "Opened File never closed. Potential Resource leak."));
435     C.emitReport(std::make_unique<PathSensitiveBugReport>(
436         *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
437   }
438 }
439 
440 void ento::registerStreamChecker(CheckerManager &mgr) {
441   mgr.registerChecker<StreamChecker>();
442 }
443 
444 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { return true; }
445