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