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 | // +-------------+ +------------+ |
24 | // acquire_func succeeded | | Escape | | |
25 | // +-----------------> Allocated +---------> Escaped <--+ |
26 | // | | | | | | |
27 | // | +-----+------++ +------------+ | |
28 | // | | | | |
29 | // acquire_func | release_func | +--+ | |
30 | // failed | | | 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 | // unowned | +----v-----+ | +-----------+ |
42 | // acquire_func | | Double | +-----> Use after | |
43 | // succeeded | | released | | released | |
44 | // | | (REPORT) | | (REPORT) | |
45 | // +---------------+ +----------+ +-----------+ |
46 | // | Allocated | |
47 | // | Unowned | release_func |
48 | // | +---------+ |
49 | // +---------------+ | |
50 | // | |
51 | // +-----v----------+ |
52 | // | Release of | |
53 | // | unowned handle | |
54 | // | (REPORT) | |
55 | // +----------------+ |
56 | // |
57 | // acquire_func represents the functions or syscalls that may acquire a handle. |
58 | // release_func represents the functions or syscalls that may release a handle. |
59 | // use_func represents the functions or syscall that requires an open handle. |
60 | // |
61 | // If a tracked handle dies in "Released" or "Not Tracked" state, we assume it |
62 | // is properly used. Otherwise a bug and will be reported. |
63 | // |
64 | // Note that, the analyzer does not always know for sure if a function failed |
65 | // or succeeded. In those cases we use the state MaybeAllocated. |
66 | // Thus, the diagram above captures the intent, not implementation details. |
67 | // |
68 | // Due to the fact that the number of handle related syscalls in Fuchsia |
69 | // is large, we adopt the annotation attributes to descript syscalls' |
70 | // operations(acquire/release/use) on handles instead of hardcoding |
71 | // everything in the checker. |
72 | // |
73 | // We use following annotation attributes for handle related syscalls or |
74 | // functions: |
75 | // 1. __attribute__((acquire_handle("Fuchsia"))) |handle will be acquired |
76 | // 2. __attribute__((release_handle("Fuchsia"))) |handle will be released |
77 | // 3. __attribute__((use_handle("Fuchsia"))) |handle will not transit to |
78 | // escaped state, it also needs to be open. |
79 | // |
80 | // For example, an annotated syscall: |
81 | // zx_status_t zx_channel_create( |
82 | // uint32_t options, |
83 | // zx_handle_t* out0 __attribute__((acquire_handle("Fuchsia"))) , |
84 | // zx_handle_t* out1 __attribute__((acquire_handle("Fuchsia")))); |
85 | // denotes a syscall which will acquire two handles and save them to 'out0' and |
86 | // 'out1' when succeeded. |
87 | // |
88 | //===----------------------------------------------------------------------===// |
89 | |
90 | #include "clang/AST/Attr.h" |
91 | #include "clang/AST/Decl.h" |
92 | #include "clang/AST/Type.h" |
93 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
94 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
95 | #include "clang/StaticAnalyzer/Core/Checker.h" |
96 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
97 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
98 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
99 | #include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" |
100 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" |
101 | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
102 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" |
103 | #include "llvm/ADT/StringExtras.h" |
104 | #include <optional> |
105 | |
106 | using namespace clang; |
107 | using namespace ento; |
108 | |
109 | namespace { |
110 | |
111 | static const StringRef HandleTypeName = "zx_handle_t" ; |
112 | static const StringRef ErrorTypeName = "zx_status_t" ; |
113 | |
114 | class HandleState { |
115 | private: |
116 | enum class Kind { MaybeAllocated, Allocated, Released, Escaped, Unowned } K; |
117 | SymbolRef ErrorSym; |
118 | HandleState(Kind K, SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {} |
119 | |
120 | public: |
121 | bool operator==(const HandleState &Other) const { |
122 | return K == Other.K && ErrorSym == Other.ErrorSym; |
123 | } |
124 | bool isAllocated() const { return K == Kind::Allocated; } |
125 | bool maybeAllocated() const { return K == Kind::MaybeAllocated; } |
126 | bool isReleased() const { return K == Kind::Released; } |
127 | bool isEscaped() const { return K == Kind::Escaped; } |
128 | bool isUnowned() const { return K == Kind::Unowned; } |
129 | |
130 | static HandleState getMaybeAllocated(SymbolRef ErrorSym) { |
131 | return HandleState(Kind::MaybeAllocated, ErrorSym); |
132 | } |
133 | static HandleState getAllocated(ProgramStateRef State, HandleState S) { |
134 | assert(S.maybeAllocated()); |
135 | assert(State->getConstraintManager() |
136 | .isNull(State, S.getErrorSym()) |
137 | .isConstrained()); |
138 | return HandleState(Kind::Allocated, nullptr); |
139 | } |
140 | static HandleState getReleased() { |
141 | return HandleState(Kind::Released, nullptr); |
142 | } |
143 | static HandleState getEscaped() { |
144 | return HandleState(Kind::Escaped, nullptr); |
145 | } |
146 | static HandleState getUnowned() { |
147 | return HandleState(Kind::Unowned, nullptr); |
148 | } |
149 | |
150 | SymbolRef getErrorSym() const { return ErrorSym; } |
151 | |
152 | void Profile(llvm::FoldingSetNodeID &ID) const { |
153 | ID.AddInteger(I: static_cast<int>(K)); |
154 | ID.AddPointer(Ptr: ErrorSym); |
155 | } |
156 | |
157 | LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { |
158 | switch (K) { |
159 | #define CASE(ID) \ |
160 | case ID: \ |
161 | OS << #ID; \ |
162 | break; |
163 | CASE(Kind::MaybeAllocated) |
164 | CASE(Kind::Allocated) |
165 | CASE(Kind::Released) |
166 | CASE(Kind::Escaped) |
167 | CASE(Kind::Unowned) |
168 | } |
169 | if (ErrorSym) { |
170 | OS << " ErrorSym: " ; |
171 | ErrorSym->dumpToStream(os&: OS); |
172 | } |
173 | } |
174 | |
175 | LLVM_DUMP_METHOD void dump() const { dump(OS&: llvm::errs()); } |
176 | }; |
177 | |
178 | template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) { |
179 | return D->hasAttr<Attr>() && D->getAttr<Attr>()->getHandleType() == "Fuchsia" ; |
180 | } |
181 | |
182 | template <typename Attr> static bool hasFuchsiaUnownedAttr(const Decl *D) { |
183 | return D->hasAttr<Attr>() && |
184 | D->getAttr<Attr>()->getHandleType() == "FuchsiaUnowned" ; |
185 | } |
186 | |
187 | class FuchsiaHandleChecker |
188 | : public Checker<check::PostCall, check::PreCall, check::DeadSymbols, |
189 | check::PointerEscape, eval::Assume> { |
190 | BugType LeakBugType{this, "Fuchsia handle leak" , "Fuchsia Handle Error" , |
191 | /*SuppressOnSink=*/true}; |
192 | BugType DoubleReleaseBugType{this, "Fuchsia handle double release" , |
193 | "Fuchsia Handle Error" }; |
194 | BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release" , |
195 | "Fuchsia Handle Error" }; |
196 | BugType ReleaseUnownedBugType{ |
197 | this, "Fuchsia handle release of unowned handle" , "Fuchsia Handle Error" }; |
198 | |
199 | public: |
200 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
201 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
202 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
203 | ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, |
204 | bool Assumption) const; |
205 | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
206 | const InvalidatedSymbols &Escaped, |
207 | const CallEvent *Call, |
208 | PointerEscapeKind Kind) const; |
209 | |
210 | ExplodedNode *reportLeaks(ArrayRef<SymbolRef> LeakedHandles, |
211 | CheckerContext &C, ExplodedNode *Pred) const; |
212 | |
213 | void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, |
214 | CheckerContext &C) const; |
215 | |
216 | void reportUnownedRelease(SymbolRef HandleSym, const SourceRange &Range, |
217 | CheckerContext &C) const; |
218 | |
219 | void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, |
220 | CheckerContext &C) const; |
221 | |
222 | void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C, |
223 | const SourceRange *Range, const BugType &Type, |
224 | StringRef Msg) const; |
225 | |
226 | void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, |
227 | const char *Sep) const override; |
228 | }; |
229 | } // end anonymous namespace |
230 | |
231 | REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) |
232 | |
233 | static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym, |
234 | CheckerContext &Ctx) { |
235 | ProgramStateRef State = N->getState(); |
236 | // When bug type is handle leak, exploded node N does not have state info for |
237 | // leaking handle. Get the predecessor of N instead. |
238 | if (!State->get<HStateMap>(key: Sym)) |
239 | N = N->getFirstPred(); |
240 | |
241 | const ExplodedNode *Pred = N; |
242 | while (N) { |
243 | State = N->getState(); |
244 | if (!State->get<HStateMap>(key: Sym)) { |
245 | const HandleState *HState = Pred->getState()->get<HStateMap>(key: Sym); |
246 | if (HState && (HState->isAllocated() || HState->maybeAllocated())) |
247 | return N; |
248 | } |
249 | Pred = N; |
250 | N = N->getFirstPred(); |
251 | } |
252 | return nullptr; |
253 | } |
254 | |
255 | namespace { |
256 | class FuchsiaHandleSymbolVisitor final : public SymbolVisitor { |
257 | public: |
258 | bool VisitSymbol(SymbolRef S) override { |
259 | if (const auto *HandleType = S->getType()->getAs<TypedefType>()) |
260 | if (HandleType->getDecl()->getName() == HandleTypeName) |
261 | Symbols.push_back(Elt: S); |
262 | return true; |
263 | } |
264 | |
265 | SmallVector<SymbolRef, 1024> GetSymbols() { return Symbols; } |
266 | |
267 | private: |
268 | SmallVector<SymbolRef, 1024> Symbols; |
269 | }; |
270 | } // end anonymous namespace |
271 | |
272 | /// Returns the symbols extracted from the argument or empty vector if it cannot |
273 | /// be found. It is unlikely to have over 1024 symbols in one argument. |
274 | static SmallVector<SymbolRef, 1024> |
275 | getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State) { |
276 | int PtrToHandleLevel = 0; |
277 | while (QT->isAnyPointerType() || QT->isReferenceType()) { |
278 | ++PtrToHandleLevel; |
279 | QT = QT->getPointeeType(); |
280 | } |
281 | if (QT->isStructureType()) { |
282 | // If we see a structure, see if there is any handle referenced by the |
283 | // structure. |
284 | FuchsiaHandleSymbolVisitor Visitor; |
285 | State->scanReachableSymbols(val: Arg, visitor&: Visitor); |
286 | return Visitor.GetSymbols(); |
287 | } |
288 | if (const auto *HandleType = QT->getAs<TypedefType>()) { |
289 | if (HandleType->getDecl()->getName() != HandleTypeName) |
290 | return {}; |
291 | if (PtrToHandleLevel > 1) |
292 | // Not supported yet. |
293 | return {}; |
294 | |
295 | if (PtrToHandleLevel == 0) { |
296 | SymbolRef Sym = Arg.getAsSymbol(); |
297 | if (Sym) { |
298 | return {Sym}; |
299 | } else { |
300 | return {}; |
301 | } |
302 | } else { |
303 | assert(PtrToHandleLevel == 1); |
304 | if (std::optional<Loc> ArgLoc = Arg.getAs<Loc>()) { |
305 | SymbolRef Sym = State->getSVal(LV: *ArgLoc).getAsSymbol(); |
306 | if (Sym) { |
307 | return {Sym}; |
308 | } else { |
309 | return {}; |
310 | } |
311 | } |
312 | } |
313 | } |
314 | return {}; |
315 | } |
316 | |
317 | void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, |
318 | CheckerContext &C) const { |
319 | ProgramStateRef State = C.getState(); |
320 | const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Val: Call.getDecl()); |
321 | if (!FuncDecl) { |
322 | // Unknown call, escape by value handles. They are not covered by |
323 | // PointerEscape callback. |
324 | for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { |
325 | if (SymbolRef Handle = Call.getArgSVal(Index: Arg).getAsSymbol()) |
326 | State = State->set<HStateMap>(K: Handle, E: HandleState::getEscaped()); |
327 | } |
328 | C.addTransition(State); |
329 | return; |
330 | } |
331 | |
332 | for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { |
333 | if (Arg >= FuncDecl->getNumParams()) |
334 | break; |
335 | const ParmVarDecl *PVD = FuncDecl->getParamDecl(i: Arg); |
336 | SmallVector<SymbolRef, 1024> Handles = |
337 | getFuchsiaHandleSymbols(QT: PVD->getType(), Arg: Call.getArgSVal(Index: Arg), State); |
338 | |
339 | // Handled in checkPostCall. |
340 | if (hasFuchsiaAttr<ReleaseHandleAttr>(D: PVD) || |
341 | hasFuchsiaAttr<AcquireHandleAttr>(D: PVD)) |
342 | continue; |
343 | |
344 | for (SymbolRef Handle : Handles) { |
345 | const HandleState *HState = State->get<HStateMap>(key: Handle); |
346 | if (!HState || HState->isEscaped()) |
347 | continue; |
348 | |
349 | if (hasFuchsiaAttr<UseHandleAttr>(D: PVD) || |
350 | PVD->getType()->isIntegerType()) { |
351 | if (HState->isReleased()) { |
352 | reportUseAfterFree(HandleSym: Handle, Range: Call.getArgSourceRange(Index: Arg), C); |
353 | return; |
354 | } |
355 | } |
356 | } |
357 | } |
358 | C.addTransition(State); |
359 | } |
360 | |
361 | void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, |
362 | CheckerContext &C) const { |
363 | const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Val: Call.getDecl()); |
364 | if (!FuncDecl) |
365 | return; |
366 | |
367 | // If we analyzed the function body, then ignore the annotations. |
368 | if (C.wasInlined) |
369 | return; |
370 | |
371 | ProgramStateRef State = C.getState(); |
372 | |
373 | std::vector<std::function<std::string(BugReport & BR)>> Notes; |
374 | SymbolRef ResultSymbol = nullptr; |
375 | if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs<TypedefType>()) |
376 | if (TypeDefTy->getDecl()->getName() == ErrorTypeName) |
377 | ResultSymbol = Call.getReturnValue().getAsSymbol(); |
378 | |
379 | // Function returns an open handle. |
380 | if (hasFuchsiaAttr<AcquireHandleAttr>(D: FuncDecl)) { |
381 | SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); |
382 | Notes.push_back(x: [RetSym, FuncDecl](BugReport &BR) -> std::string { |
383 | auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); |
384 | if (PathBR->getInterestingnessKind(sym: RetSym)) { |
385 | std::string SBuf; |
386 | llvm::raw_string_ostream OS(SBuf); |
387 | OS << "Function '" << FuncDecl->getDeclName() |
388 | << "' returns an open handle" ; |
389 | return SBuf; |
390 | } else |
391 | return "" ; |
392 | }); |
393 | State = |
394 | State->set<HStateMap>(K: RetSym, E: HandleState::getMaybeAllocated(ErrorSym: nullptr)); |
395 | } else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(D: FuncDecl)) { |
396 | // Function returns an unowned handle |
397 | SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); |
398 | Notes.push_back(x: [RetSym, FuncDecl](BugReport &BR) -> std::string { |
399 | auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); |
400 | if (PathBR->getInterestingnessKind(sym: RetSym)) { |
401 | std::string SBuf; |
402 | llvm::raw_string_ostream OS(SBuf); |
403 | OS << "Function '" << FuncDecl->getDeclName() |
404 | << "' returns an unowned handle" ; |
405 | return SBuf; |
406 | } else |
407 | return "" ; |
408 | }); |
409 | State = State->set<HStateMap>(K: RetSym, E: HandleState::getUnowned()); |
410 | } |
411 | |
412 | for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { |
413 | if (Arg >= FuncDecl->getNumParams()) |
414 | break; |
415 | const ParmVarDecl *PVD = FuncDecl->getParamDecl(i: Arg); |
416 | unsigned ParamDiagIdx = PVD->getFunctionScopeIndex() + 1; |
417 | SmallVector<SymbolRef, 1024> Handles = |
418 | getFuchsiaHandleSymbols(QT: PVD->getType(), Arg: Call.getArgSVal(Index: Arg), State); |
419 | |
420 | for (SymbolRef Handle : Handles) { |
421 | const HandleState *HState = State->get<HStateMap>(key: Handle); |
422 | if (HState && HState->isEscaped()) |
423 | continue; |
424 | if (hasFuchsiaAttr<ReleaseHandleAttr>(D: PVD)) { |
425 | if (HState && HState->isReleased()) { |
426 | reportDoubleRelease(HandleSym: Handle, Range: Call.getArgSourceRange(Index: Arg), C); |
427 | return; |
428 | } else if (HState && HState->isUnowned()) { |
429 | reportUnownedRelease(HandleSym: Handle, Range: Call.getArgSourceRange(Index: Arg), C); |
430 | return; |
431 | } else { |
432 | Notes.push_back(x: [Handle, ParamDiagIdx](BugReport &BR) -> std::string { |
433 | auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); |
434 | if (PathBR->getInterestingnessKind(sym: Handle)) { |
435 | std::string SBuf; |
436 | llvm::raw_string_ostream OS(SBuf); |
437 | OS << "Handle released through " << ParamDiagIdx |
438 | << llvm::getOrdinalSuffix(Val: ParamDiagIdx) << " parameter" ; |
439 | return SBuf; |
440 | } else |
441 | return "" ; |
442 | }); |
443 | State = State->set<HStateMap>(K: Handle, E: HandleState::getReleased()); |
444 | } |
445 | } else if (hasFuchsiaAttr<AcquireHandleAttr>(D: PVD)) { |
446 | Notes.push_back(x: [Handle, ParamDiagIdx](BugReport &BR) -> std::string { |
447 | auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); |
448 | if (PathBR->getInterestingnessKind(sym: Handle)) { |
449 | std::string SBuf; |
450 | llvm::raw_string_ostream OS(SBuf); |
451 | OS << "Handle allocated through " << ParamDiagIdx |
452 | << llvm::getOrdinalSuffix(Val: ParamDiagIdx) << " parameter" ; |
453 | return SBuf; |
454 | } else |
455 | return "" ; |
456 | }); |
457 | State = State->set<HStateMap>( |
458 | K: Handle, E: HandleState::getMaybeAllocated(ErrorSym: ResultSymbol)); |
459 | } else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(D: PVD)) { |
460 | Notes.push_back(x: [Handle, ParamDiagIdx](BugReport &BR) -> std::string { |
461 | auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); |
462 | if (PathBR->getInterestingnessKind(sym: Handle)) { |
463 | std::string SBuf; |
464 | llvm::raw_string_ostream OS(SBuf); |
465 | OS << "Unowned handle allocated through " << ParamDiagIdx |
466 | << llvm::getOrdinalSuffix(Val: ParamDiagIdx) << " parameter" ; |
467 | return SBuf; |
468 | } else |
469 | return "" ; |
470 | }); |
471 | State = State->set<HStateMap>(K: Handle, E: HandleState::getUnowned()); |
472 | } else if (!hasFuchsiaAttr<UseHandleAttr>(D: PVD) && |
473 | PVD->getType()->isIntegerType()) { |
474 | // Working around integer by-value escapes. |
475 | // The by-value escape would not be captured in checkPointerEscape. |
476 | // If the function was not analyzed (otherwise wasInlined should be |
477 | // true) and there is no annotation on the handle, we assume the handle |
478 | // is escaped. |
479 | State = State->set<HStateMap>(K: Handle, E: HandleState::getEscaped()); |
480 | } |
481 | } |
482 | } |
483 | const NoteTag *T = nullptr; |
484 | if (!Notes.empty()) { |
485 | T = C.getNoteTag(Cb: [this, Notes{std::move(Notes)}]( |
486 | PathSensitiveBugReport &BR) -> std::string { |
487 | if (&BR.getBugType() != &UseAfterReleaseBugType && |
488 | &BR.getBugType() != &LeakBugType && |
489 | &BR.getBugType() != &DoubleReleaseBugType && |
490 | &BR.getBugType() != &ReleaseUnownedBugType) |
491 | return "" ; |
492 | for (auto &Note : Notes) { |
493 | std::string Text = Note(BR); |
494 | if (!Text.empty()) |
495 | return Text; |
496 | } |
497 | return "" ; |
498 | }); |
499 | } |
500 | C.addTransition(State, Tag: T); |
501 | } |
502 | |
503 | void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
504 | CheckerContext &C) const { |
505 | ProgramStateRef State = C.getState(); |
506 | SmallVector<SymbolRef, 2> LeakedSyms; |
507 | HStateMapTy TrackedHandles = State->get<HStateMap>(); |
508 | for (auto &CurItem : TrackedHandles) { |
509 | SymbolRef ErrorSym = CurItem.second.getErrorSym(); |
510 | // Keeping zombie handle symbols. In case the error symbol is dying later |
511 | // than the handle symbol we might produce spurious leak warnings (in case |
512 | // we find out later from the status code that the handle allocation failed |
513 | // in the first place). |
514 | if (!SymReaper.isDead(sym: CurItem.first) || |
515 | (ErrorSym && !SymReaper.isDead(sym: ErrorSym))) |
516 | continue; |
517 | if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated()) |
518 | LeakedSyms.push_back(Elt: CurItem.first); |
519 | State = State->remove<HStateMap>(K: CurItem.first); |
520 | } |
521 | |
522 | ExplodedNode *N = C.getPredecessor(); |
523 | if (!LeakedSyms.empty()) |
524 | N = reportLeaks(LeakedHandles: LeakedSyms, C, Pred: N); |
525 | |
526 | C.addTransition(State, Pred: N); |
527 | } |
528 | |
529 | // Acquiring a handle is not always successful. In Fuchsia most functions |
530 | // return a status code that determines the status of the handle. |
531 | // When we split the path based on this status code we know that on one |
532 | // path we do have the handle and on the other path the acquire failed. |
533 | // This method helps avoiding false positive leak warnings on paths where |
534 | // the function failed. |
535 | // Moreover, when a handle is known to be zero (the invalid handle), |
536 | // we no longer can follow the symbol on the path, becaue the constant |
537 | // zero will be used instead of the symbol. We also do not need to release |
538 | // an invalid handle, so we remove the corresponding symbol from the state. |
539 | ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, |
540 | SVal Cond, |
541 | bool Assumption) const { |
542 | // TODO: add notes about successes/fails for APIs. |
543 | ConstraintManager &Cmr = State->getConstraintManager(); |
544 | HStateMapTy TrackedHandles = State->get<HStateMap>(); |
545 | for (auto &CurItem : TrackedHandles) { |
546 | ConditionTruthVal HandleVal = Cmr.isNull(State, Sym: CurItem.first); |
547 | if (HandleVal.isConstrainedTrue()) { |
548 | // The handle is invalid. We can no longer follow the symbol on this path. |
549 | State = State->remove<HStateMap>(K: CurItem.first); |
550 | } |
551 | SymbolRef ErrorSym = CurItem.second.getErrorSym(); |
552 | if (!ErrorSym) |
553 | continue; |
554 | ConditionTruthVal ErrorVal = Cmr.isNull(State, Sym: ErrorSym); |
555 | if (ErrorVal.isConstrainedTrue()) { |
556 | // Allocation succeeded. |
557 | if (CurItem.second.maybeAllocated()) |
558 | State = State->set<HStateMap>( |
559 | K: CurItem.first, E: HandleState::getAllocated(State, S: CurItem.second)); |
560 | } else if (ErrorVal.isConstrainedFalse()) { |
561 | // Allocation failed. |
562 | if (CurItem.second.maybeAllocated()) |
563 | State = State->remove<HStateMap>(K: CurItem.first); |
564 | } |
565 | } |
566 | return State; |
567 | } |
568 | |
569 | ProgramStateRef FuchsiaHandleChecker::checkPointerEscape( |
570 | ProgramStateRef State, const InvalidatedSymbols &Escaped, |
571 | const CallEvent *Call, PointerEscapeKind Kind) const { |
572 | const FunctionDecl *FuncDecl = |
573 | Call ? dyn_cast_or_null<FunctionDecl>(Val: Call->getDecl()) : nullptr; |
574 | |
575 | llvm::DenseSet<SymbolRef> UnEscaped; |
576 | // Not all calls should escape our symbols. |
577 | if (FuncDecl && |
578 | (Kind == PSK_DirectEscapeOnCall || Kind == PSK_IndirectEscapeOnCall || |
579 | Kind == PSK_EscapeOutParameters)) { |
580 | for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) { |
581 | if (Arg >= FuncDecl->getNumParams()) |
582 | break; |
583 | const ParmVarDecl *PVD = FuncDecl->getParamDecl(i: Arg); |
584 | SmallVector<SymbolRef, 1024> Handles = |
585 | getFuchsiaHandleSymbols(QT: PVD->getType(), Arg: Call->getArgSVal(Index: Arg), State); |
586 | for (SymbolRef Handle : Handles) { |
587 | if (hasFuchsiaAttr<UseHandleAttr>(D: PVD) || |
588 | hasFuchsiaAttr<ReleaseHandleAttr>(D: PVD)) { |
589 | UnEscaped.insert(V: Handle); |
590 | } |
591 | } |
592 | } |
593 | } |
594 | |
595 | // For out params, we have to deal with derived symbols. See |
596 | // MacOSKeychainAPIChecker for details. |
597 | for (auto I : State->get<HStateMap>()) { |
598 | if (Escaped.count(V: I.first) && !UnEscaped.count(V: I.first)) |
599 | State = State->set<HStateMap>(K: I.first, E: HandleState::getEscaped()); |
600 | if (const auto *SD = dyn_cast<SymbolDerived>(Val: I.first)) { |
601 | auto ParentSym = SD->getParentSymbol(); |
602 | if (Escaped.count(V: ParentSym)) |
603 | State = State->set<HStateMap>(K: I.first, E: HandleState::getEscaped()); |
604 | } |
605 | } |
606 | |
607 | return State; |
608 | } |
609 | |
610 | ExplodedNode * |
611 | FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles, |
612 | CheckerContext &C, ExplodedNode *Pred) const { |
613 | ExplodedNode *ErrNode = C.generateNonFatalErrorNode(State: C.getState(), Pred); |
614 | for (SymbolRef LeakedHandle : LeakedHandles) { |
615 | reportBug(Sym: LeakedHandle, ErrorNode: ErrNode, C, Range: nullptr, Type: LeakBugType, |
616 | Msg: "Potential leak of handle" ); |
617 | } |
618 | return ErrNode; |
619 | } |
620 | |
621 | void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, |
622 | const SourceRange &Range, |
623 | CheckerContext &C) const { |
624 | ExplodedNode *ErrNode = C.generateErrorNode(State: C.getState()); |
625 | reportBug(Sym: HandleSym, ErrorNode: ErrNode, C, Range: &Range, Type: DoubleReleaseBugType, |
626 | Msg: "Releasing a previously released handle" ); |
627 | } |
628 | |
629 | void FuchsiaHandleChecker::reportUnownedRelease(SymbolRef HandleSym, |
630 | const SourceRange &Range, |
631 | CheckerContext &C) const { |
632 | ExplodedNode *ErrNode = C.generateErrorNode(State: C.getState()); |
633 | reportBug(Sym: HandleSym, ErrorNode: ErrNode, C, Range: &Range, Type: ReleaseUnownedBugType, |
634 | Msg: "Releasing an unowned handle" ); |
635 | } |
636 | |
637 | void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, |
638 | const SourceRange &Range, |
639 | CheckerContext &C) const { |
640 | ExplodedNode *ErrNode = C.generateErrorNode(State: C.getState()); |
641 | reportBug(Sym: HandleSym, ErrorNode: ErrNode, C, Range: &Range, Type: UseAfterReleaseBugType, |
642 | Msg: "Using a previously released handle" ); |
643 | } |
644 | |
645 | void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, |
646 | CheckerContext &C, |
647 | const SourceRange *Range, |
648 | const BugType &Type, StringRef Msg) const { |
649 | if (!ErrorNode) |
650 | return; |
651 | |
652 | std::unique_ptr<PathSensitiveBugReport> R; |
653 | if (Type.isSuppressOnSink()) { |
654 | const ExplodedNode *AcquireNode = getAcquireSite(N: ErrorNode, Sym, Ctx&: C); |
655 | if (AcquireNode) { |
656 | const Stmt *S = AcquireNode->getStmtForDiagnostics(); |
657 | assert(S && "Statement cannot be null." ); |
658 | PathDiagnosticLocation LocUsedForUniqueing = |
659 | PathDiagnosticLocation::createBegin( |
660 | S, SM: C.getSourceManager(), LAC: AcquireNode->getLocationContext()); |
661 | |
662 | R = std::make_unique<PathSensitiveBugReport>( |
663 | args: Type, args&: Msg, args&: ErrorNode, args&: LocUsedForUniqueing, |
664 | args: AcquireNode->getLocationContext()->getDecl()); |
665 | } |
666 | } |
667 | if (!R) |
668 | R = std::make_unique<PathSensitiveBugReport>(args: Type, args&: Msg, args&: ErrorNode); |
669 | if (Range) |
670 | R->addRange(R: *Range); |
671 | R->markInteresting(sym: Sym); |
672 | C.emitReport(R: std::move(R)); |
673 | } |
674 | |
675 | void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { |
676 | mgr.registerChecker<FuchsiaHandleChecker>(); |
677 | } |
678 | |
679 | bool ento::shouldRegisterFuchsiaHandleChecker(const CheckerManager &mgr) { |
680 | return true; |
681 | } |
682 | |
683 | void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, |
684 | const char *NL, const char *Sep) const { |
685 | |
686 | HStateMapTy StateMap = State->get<HStateMap>(); |
687 | |
688 | if (!StateMap.isEmpty()) { |
689 | Out << Sep << "FuchsiaHandleChecker :" << NL; |
690 | for (const auto &[Sym, HandleState] : StateMap) { |
691 | Sym->dumpToStream(os&: Out); |
692 | Out << " : " ; |
693 | HandleState.dump(OS&: Out); |
694 | Out << NL; |
695 | } |
696 | } |
697 | } |
698 | |