| 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 | |