1 //=== FuchsiaHandleChecker.cpp - Find handle leaks/double closes -*- 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 checker checks if the handle of Fuchsia is properly used according to
10 // following rules.
11 //   - If a handle is acquired, it should be released before execution
12 //        ends.
13 //   - If a handle is released, it should not be released again.
14 //   - If a handle is released, it should not be used for other purposes
15 //        such as I/O.
16 //
17 // In this checker, each tracked handle is associated with a state. When the
18 // handle variable is passed to different function calls or syscalls, its state
19 // changes. The state changes can be generally represented by following ASCII
20 // Art:
21 //
22 //
23 //                              +-+---------v-+         +------------+
24 //       acquire_func succeeded |             | Escape  |            |
25 //            +----------------->  Allocated  +--------->  Escaped   <--+
26 //            |                 |             |         |            |  |
27 //            |                 +-----+------++         +------------+  |
28 //            |                       |      |                          |
29 //            |         release_func  |      +--+                       |
30 //            |                       |         | handle  +--------+    |
31 //            |                       |         | dies    |        |    |
32 //            |                  +----v-----+   +---------> Leaked |    |
33 //            |                  |          |             |(REPORT)|    |
34 // +----------+--+               | Released | Escape      +--------+    |
35 // |             |               |          +---------------------------+
36 // | Not tracked <--+            +----+---+-+
37 // |             |  |                 |   |        As argument by value
38 // +------+------+  |    release_func |   +------+ in function call
39 //        |         |                 |          | or by reference in
40 //        |         |                 |          | use_func call
41 //        +---------+            +----v-----+    |     +-----------+
42 //        acquire_func failed    | Double   |    +-----> Use after |
43 //                               | released |          | released  |
44 //                               | (REPORT) |          | (REPORT)  |
45 //                               +----------+          +-----------+
46 //
47 // acquire_func represents the functions or syscalls that may acquire a handle.
48 // release_func represents the functions or syscalls that may release a handle.
49 // use_func represents the functions or syscall that requires an open handle.
50 //
51 // If a tracked handle dies in "Released" or "Not Tracked" state, we assume it
52 // is properly used. Otherwise a bug and will be reported.
53 //
54 // Note that, the analyzer does not always know for sure if a function failed
55 // or succeeded. In those cases we use the state MaybeAllocated.
56 // Thus, the diagramm above captures the intent, not implementation details.
57 //
58 // Due to the fact that the number of handle related syscalls in Fuchsia
59 // is large, we adopt the annotation attributes to descript syscalls'
60 // operations(acquire/release/use) on handles instead of hardcoding
61 // everything in the checker.
62 //
63 // We use following annotation attributes for handle related syscalls or
64 // functions:
65 //  1. __attribute__((acquire_handle("Fuchsia"))) |handle will be acquired
66 //  2. __attribute__((release_handle("Fuchsia"))) |handle will be released
67 //  3. __attribute__((use_handle("Fuchsia"))) |handle will not transit to
68 //     escaped state, it also needs to be open.
69 //
70 // For example, an annotated syscall:
71 //   zx_status_t zx_channel_create(
72 //   uint32_t options,
73 //   zx_handle_t* out0 __attribute__((acquire_handle("Fuchsia"))) ,
74 //   zx_handle_t* out1 __attribute__((acquire_handle("Fuchsia"))));
75 // denotes a syscall which will acquire two handles and save them to 'out0' and
76 // 'out1' when succeeded.
77 //
78 //===----------------------------------------------------------------------===//
79 
80 #include "clang/AST/Attr.h"
81 #include "clang/AST/Decl.h"
82 #include "clang/AST/Type.h"
83 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
84 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
85 #include "clang/StaticAnalyzer/Core/Checker.h"
86 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
87 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
88 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
89 #include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h"
90 #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
91 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
92 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
93 
94 using namespace clang;
95 using namespace ento;
96 
97 namespace {
98 
99 static const StringRef HandleTypeName = "zx_handle_t";
100 static const StringRef ErrorTypeName = "zx_status_t";
101 
102 class HandleState {
103 private:
104   enum class Kind { MaybeAllocated, Allocated, Released, Escaped } K;
105   SymbolRef ErrorSym;
106   HandleState(Kind K, SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {}
107 
108 public:
109   bool operator==(const HandleState &Other) const {
110     return K == Other.K && ErrorSym == Other.ErrorSym;
111   }
112   bool isAllocated() const { return K == Kind::Allocated; }
113   bool maybeAllocated() const { return K == Kind::MaybeAllocated; }
114   bool isReleased() const { return K == Kind::Released; }
115   bool isEscaped() const { return K == Kind::Escaped; }
116 
117   static HandleState getMaybeAllocated(SymbolRef ErrorSym) {
118     return HandleState(Kind::MaybeAllocated, ErrorSym);
119   }
120   static HandleState getAllocated(ProgramStateRef State, HandleState S) {
121     assert(S.maybeAllocated());
122     assert(State->getConstraintManager()
123                .isNull(State, S.getErrorSym())
124                .isConstrained());
125     return HandleState(Kind::Allocated, nullptr);
126   }
127   static HandleState getReleased() {
128     return HandleState(Kind::Released, nullptr);
129   }
130   static HandleState getEscaped() {
131     return HandleState(Kind::Escaped, nullptr);
132   }
133 
134   SymbolRef getErrorSym() const { return ErrorSym; }
135 
136   void Profile(llvm::FoldingSetNodeID &ID) const {
137     ID.AddInteger(static_cast<int>(K));
138     ID.AddPointer(ErrorSym);
139   }
140 
141   LLVM_DUMP_METHOD void dump(raw_ostream &OS) const {
142     switch (K) {
143 #define CASE(ID)                                                               \
144   case ID:                                                                     \
145     OS << #ID;                                                                 \
146     break;
147       CASE(Kind::MaybeAllocated)
148       CASE(Kind::Allocated)
149       CASE(Kind::Released)
150       CASE(Kind::Escaped)
151     }
152   }
153 
154   LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); }
155 };
156 
157 template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) {
158   return D->hasAttr<Attr>() && D->getAttr<Attr>()->getHandleType() == "Fuchsia";
159 }
160 
161 class FuchsiaHandleChecker
162     : public Checker<check::PostCall, check::PreCall, check::DeadSymbols,
163                      check::PointerEscape, eval::Assume> {
164   BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error",
165                       /*SuppressOnSink=*/true};
166   BugType DoubleReleaseBugType{this, "Fuchsia handle double release",
167                                "Fuchsia Handle Error"};
168   BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release",
169                                  "Fuchsia Handle Error"};
170 
171 public:
172   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
173   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
174   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
175   ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond,
176                              bool Assumption) const;
177   ProgramStateRef checkPointerEscape(ProgramStateRef State,
178                                      const InvalidatedSymbols &Escaped,
179                                      const CallEvent *Call,
180                                      PointerEscapeKind Kind) const;
181 
182   ExplodedNode *reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
183                             CheckerContext &C, ExplodedNode *Pred) const;
184 
185   void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range,
186                            CheckerContext &C) const;
187 
188   void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range,
189                           CheckerContext &C) const;
190 
191   void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C,
192                  const SourceRange *Range, const BugType &Type,
193                  StringRef Msg) const;
194 
195   void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
196                   const char *Sep) const override;
197 };
198 } // end anonymous namespace
199 
200 REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState)
201 
202 static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym,
203                                           CheckerContext &Ctx) {
204   ProgramStateRef State = N->getState();
205   // When bug type is handle leak, exploded node N does not have state info for
206   // leaking handle. Get the predecessor of N instead.
207   if (!State->get<HStateMap>(Sym))
208     N = N->getFirstPred();
209 
210   const ExplodedNode *Pred = N;
211   while (N) {
212     State = N->getState();
213     if (!State->get<HStateMap>(Sym)) {
214       const HandleState *HState = Pred->getState()->get<HStateMap>(Sym);
215       if (HState && (HState->isAllocated() || HState->maybeAllocated()))
216         return N;
217     }
218     Pred = N;
219     N = N->getFirstPred();
220   }
221   return nullptr;
222 }
223 
224 /// Returns the symbols extracted from the argument or null if it cannot be
225 /// found.
226 static SymbolRef getFuchsiaHandleSymbol(QualType QT, SVal Arg,
227                                         ProgramStateRef State) {
228   int PtrToHandleLevel = 0;
229   while (QT->isAnyPointerType() || QT->isReferenceType()) {
230     ++PtrToHandleLevel;
231     QT = QT->getPointeeType();
232   }
233   if (const auto *HandleType = QT->getAs<TypedefType>()) {
234     if (HandleType->getDecl()->getName() != HandleTypeName)
235       return nullptr;
236     if (PtrToHandleLevel > 1) {
237       // Not supported yet.
238       return nullptr;
239     }
240 
241     if (PtrToHandleLevel == 0) {
242       return Arg.getAsSymbol();
243     } else {
244       assert(PtrToHandleLevel == 1);
245       if (Optional<Loc> ArgLoc = Arg.getAs<Loc>())
246         return State->getSVal(*ArgLoc).getAsSymbol();
247     }
248   }
249   return nullptr;
250 }
251 
252 void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call,
253                                         CheckerContext &C) const {
254   ProgramStateRef State = C.getState();
255   const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
256   if (!FuncDecl) {
257     // Unknown call, escape by value handles. They are not covered by
258     // PointerEscape callback.
259     for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
260       if (SymbolRef Handle = Call.getArgSVal(Arg).getAsSymbol())
261         State = State->set<HStateMap>(Handle, HandleState::getEscaped());
262     }
263     C.addTransition(State);
264     return;
265   }
266 
267   for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
268     if (Arg >= FuncDecl->getNumParams())
269       break;
270     const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
271     SymbolRef Handle =
272         getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State);
273     if (!Handle)
274       continue;
275 
276     // Handled in checkPostCall.
277     if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD) ||
278         hasFuchsiaAttr<AcquireHandleAttr>(PVD))
279       continue;
280 
281     const HandleState *HState = State->get<HStateMap>(Handle);
282     if (!HState || HState->isEscaped())
283       continue;
284 
285     if (hasFuchsiaAttr<UseHandleAttr>(PVD) || PVD->getType()->isIntegerType()) {
286       if (HState->isReleased()) {
287         reportUseAfterFree(Handle, Call.getArgSourceRange(Arg), C);
288         return;
289       }
290     }
291     if (!hasFuchsiaAttr<UseHandleAttr>(PVD) &&
292         PVD->getType()->isIntegerType()) {
293       // Working around integer by-value escapes.
294       State = State->set<HStateMap>(Handle, HandleState::getEscaped());
295     }
296   }
297   C.addTransition(State);
298 }
299 
300 void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call,
301                                          CheckerContext &C) const {
302   const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
303   if (!FuncDecl)
304     return;
305 
306   ProgramStateRef State = C.getState();
307 
308   std::vector<std::function<std::string(BugReport & BR)>> Notes;
309   SymbolRef ResultSymbol = nullptr;
310   if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs<TypedefType>())
311     if (TypeDefTy->getDecl()->getName() == ErrorTypeName)
312       ResultSymbol = Call.getReturnValue().getAsSymbol();
313 
314   // Function returns an open handle.
315   if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) {
316     SymbolRef RetSym = Call.getReturnValue().getAsSymbol();
317     State =
318         State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(nullptr));
319   }
320 
321   for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) {
322     if (Arg >= FuncDecl->getNumParams())
323       break;
324     const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
325     SymbolRef Handle =
326         getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State);
327     if (!Handle)
328       continue;
329 
330     const HandleState *HState = State->get<HStateMap>(Handle);
331     if (HState && HState->isEscaped())
332       continue;
333     if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) {
334       if (HState && HState->isReleased()) {
335         reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C);
336         return;
337       } else {
338         Notes.push_back([Handle](BugReport &BR) {
339           auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
340           if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) {
341             return "Handle released here.";
342           } else
343             return "";
344         });
345         State = State->set<HStateMap>(Handle, HandleState::getReleased());
346       }
347     } else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) {
348       Notes.push_back([Handle](BugReport &BR) {
349         auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR);
350         if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) {
351           return "Handle allocated here.";
352         } else
353           return "";
354       });
355       State = State->set<HStateMap>(
356           Handle, HandleState::getMaybeAllocated(ResultSymbol));
357     }
358   }
359   const NoteTag *T = nullptr;
360   if (!Notes.empty()) {
361     T = C.getNoteTag(
362         [this, Notes{std::move(Notes)}](BugReport &BR) -> std::string {
363           if (&BR.getBugType() != &UseAfterReleaseBugType &&
364               &BR.getBugType() != &LeakBugType &&
365               &BR.getBugType() != &DoubleReleaseBugType)
366             return "";
367           for (auto &Note : Notes) {
368             std::string Text = Note(BR);
369             if (!Text.empty())
370               return Text;
371           }
372           return "";
373         });
374   }
375   C.addTransition(State, T);
376 }
377 
378 void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper,
379                                             CheckerContext &C) const {
380   ProgramStateRef State = C.getState();
381   SmallVector<SymbolRef, 2> LeakedSyms;
382   HStateMapTy TrackedHandles = State->get<HStateMap>();
383   for (auto &CurItem : TrackedHandles) {
384     if (!SymReaper.isDead(CurItem.first))
385       continue;
386     if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated())
387       LeakedSyms.push_back(CurItem.first);
388     State = State->remove<HStateMap>(CurItem.first);
389   }
390 
391   ExplodedNode *N = C.getPredecessor();
392   if (!LeakedSyms.empty())
393     N = reportLeaks(LeakedSyms, C, N);
394 
395   C.addTransition(State, N);
396 }
397 
398 // Acquiring a handle is not always successful. In Fuchsia most functions
399 // return a status code that determines the status of the handle.
400 // When we split the path based on this status code we know that on one
401 // path we do have the handle and on the other path the acquire failed.
402 // This method helps avoiding false positive leak warnings on paths where
403 // the function failed.
404 // Moreover, when a handle is known to be zero (the invalid handle),
405 // we no longer can follow the symbol on the path, becaue the constant
406 // zero will be used instead of the symbol. We also do not need to release
407 // an invalid handle, so we remove the corresponding symbol from the state.
408 ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State,
409                                                  SVal Cond,
410                                                  bool Assumption) const {
411   // TODO: add notes about successes/fails for APIs.
412   ConstraintManager &Cmr = State->getConstraintManager();
413   HStateMapTy TrackedHandles = State->get<HStateMap>();
414   for (auto &CurItem : TrackedHandles) {
415     ConditionTruthVal HandleVal = Cmr.isNull(State, CurItem.first);
416     if (HandleVal.isConstrainedTrue()) {
417       // The handle is invalid. We can no longer follow the symbol on this path.
418       State = State->remove<HStateMap>(CurItem.first);
419     }
420     SymbolRef ErrorSym = CurItem.second.getErrorSym();
421     if (!ErrorSym)
422       continue;
423     ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym);
424     if (ErrorVal.isConstrainedTrue()) {
425       // Allocation succeeded.
426       if (CurItem.second.maybeAllocated())
427         State = State->set<HStateMap>(
428             CurItem.first, HandleState::getAllocated(State, CurItem.second));
429     } else if (ErrorVal.isConstrainedFalse()) {
430       // Allocation failed.
431       if (CurItem.second.maybeAllocated())
432         State = State->remove<HStateMap>(CurItem.first);
433     }
434   }
435   return State;
436 }
437 
438 ProgramStateRef FuchsiaHandleChecker::checkPointerEscape(
439     ProgramStateRef State, const InvalidatedSymbols &Escaped,
440     const CallEvent *Call, PointerEscapeKind Kind) const {
441   const FunctionDecl *FuncDecl =
442       Call ? dyn_cast_or_null<FunctionDecl>(Call->getDecl()) : nullptr;
443 
444   llvm::DenseSet<SymbolRef> UnEscaped;
445   // Not all calls should escape our symbols.
446   if (FuncDecl &&
447       (Kind == PSK_DirectEscapeOnCall || Kind == PSK_IndirectEscapeOnCall ||
448        Kind == PSK_EscapeOutParameters)) {
449     for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) {
450       if (Arg >= FuncDecl->getNumParams())
451         break;
452       const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg);
453       SymbolRef Handle =
454           getFuchsiaHandleSymbol(PVD->getType(), Call->getArgSVal(Arg), State);
455       if (!Handle)
456         continue;
457       if (hasFuchsiaAttr<UseHandleAttr>(PVD) ||
458           hasFuchsiaAttr<ReleaseHandleAttr>(PVD))
459         UnEscaped.insert(Handle);
460     }
461   }
462 
463   // For out params, we have to deal with derived symbols. See
464   // MacOSKeychainAPIChecker for details.
465   for (auto I : State->get<HStateMap>()) {
466     if (Escaped.count(I.first) && !UnEscaped.count(I.first))
467       State = State->set<HStateMap>(I.first, HandleState::getEscaped());
468     if (const auto *SD = dyn_cast<SymbolDerived>(I.first)) {
469       auto ParentSym = SD->getParentSymbol();
470       if (Escaped.count(ParentSym))
471         State = State->set<HStateMap>(I.first, HandleState::getEscaped());
472     }
473   }
474 
475   return State;
476 }
477 
478 ExplodedNode *
479 FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
480                                   CheckerContext &C, ExplodedNode *Pred) const {
481   ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState(), Pred);
482   for (SymbolRef LeakedHandle : LeakedHandles) {
483     reportBug(LeakedHandle, ErrNode, C, nullptr, LeakBugType,
484               "Potential leak of handle");
485   }
486   return ErrNode;
487 }
488 
489 void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym,
490                                                const SourceRange &Range,
491                                                CheckerContext &C) const {
492   ExplodedNode *ErrNode = C.generateErrorNode(C.getState());
493   reportBug(HandleSym, ErrNode, C, &Range, DoubleReleaseBugType,
494             "Releasing a previously released handle");
495 }
496 
497 void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym,
498                                               const SourceRange &Range,
499                                               CheckerContext &C) const {
500   ExplodedNode *ErrNode = C.generateErrorNode(C.getState());
501   reportBug(HandleSym, ErrNode, C, &Range, UseAfterReleaseBugType,
502             "Using a previously released handle");
503 }
504 
505 void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode,
506                                      CheckerContext &C,
507                                      const SourceRange *Range,
508                                      const BugType &Type, StringRef Msg) const {
509   if (!ErrorNode)
510     return;
511 
512   std::unique_ptr<PathSensitiveBugReport> R;
513   if (Type.isSuppressOnSink()) {
514     const ExplodedNode *AcquireNode = getAcquireSite(ErrorNode, Sym, C);
515     if (AcquireNode) {
516       PathDiagnosticLocation LocUsedForUniqueing =
517           PathDiagnosticLocation::createBegin(
518               AcquireNode->getStmtForDiagnostics(), C.getSourceManager(),
519               AcquireNode->getLocationContext());
520 
521       R = std::make_unique<PathSensitiveBugReport>(
522           Type, Msg, ErrorNode, LocUsedForUniqueing,
523           AcquireNode->getLocationContext()->getDecl());
524     }
525   }
526   if (!R)
527     R = std::make_unique<PathSensitiveBugReport>(Type, Msg, ErrorNode);
528   if (Range)
529     R->addRange(*Range);
530   R->markInteresting(Sym);
531   C.emitReport(std::move(R));
532 }
533 
534 void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) {
535   mgr.registerChecker<FuchsiaHandleChecker>();
536 }
537 
538 bool ento::shouldRegisterFuchsiaHandleChecker(const LangOptions &LO) {
539   return true;
540 }
541 
542 void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State,
543                                       const char *NL, const char *Sep) const {
544 
545   HStateMapTy StateMap = State->get<HStateMap>();
546 
547   if (!StateMap.isEmpty()) {
548     Out << Sep << "FuchsiaHandleChecker :" << NL;
549     for (HStateMapTy::iterator I = StateMap.begin(), E = StateMap.end(); I != E;
550          ++I) {
551       I.getKey()->dumpToStream(Out);
552       Out << " : ";
553       I.getData().dump(Out);
554       Out << NL;
555     }
556   }
557 }
558