1 //==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // This checker flags misuses of KeyChainAPI. In particular, the password data 9 // allocated/returned by SecKeychainItemCopyContent, 10 // SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has 11 // to be freed using a call to SecKeychainItemFreeContent. 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 16 #include "clang/StaticAnalyzer/Core/Checker.h" 17 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 22 #include "llvm/ADT/STLExtras.h" 23 #include "llvm/ADT/SmallString.h" 24 #include "llvm/Support/raw_ostream.h" 25 #include <optional> 26 27 using namespace clang; 28 using namespace ento; 29 30 namespace { 31 class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, 32 check::PostStmt<CallExpr>, 33 check::DeadSymbols, 34 check::PointerEscape, 35 eval::Assume> { 36 const BugType BT{this, "Improper use of SecKeychain API", 37 categories::AppleAPIMisuse}; 38 39 public: 40 /// AllocationState is a part of the checker specific state together with the 41 /// MemRegion corresponding to the allocated data. 42 struct AllocationState { 43 /// The index of the allocator function. 44 unsigned int AllocatorIdx; 45 SymbolRef Region; 46 47 AllocationState(const Expr *E, unsigned int Idx, SymbolRef R) : 48 AllocatorIdx(Idx), 49 Region(R) {} 50 51 bool operator==(const AllocationState &X) const { 52 return (AllocatorIdx == X.AllocatorIdx && 53 Region == X.Region); 54 } 55 56 void Profile(llvm::FoldingSetNodeID &ID) const { 57 ID.AddInteger(AllocatorIdx); 58 ID.AddPointer(Region); 59 } 60 }; 61 62 void checkPreStmt(const CallExpr *S, CheckerContext &C) const; 63 void checkPostStmt(const CallExpr *S, CheckerContext &C) const; 64 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; 65 ProgramStateRef checkPointerEscape(ProgramStateRef State, 66 const InvalidatedSymbols &Escaped, 67 const CallEvent *Call, 68 PointerEscapeKind Kind) const; 69 ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, 70 bool Assumption) const; 71 void printState(raw_ostream &Out, ProgramStateRef State, 72 const char *NL, const char *Sep) const override; 73 74 private: 75 typedef std::pair<SymbolRef, const AllocationState*> AllocationPair; 76 typedef SmallVector<AllocationPair, 2> AllocationPairVec; 77 78 enum APIKind { 79 /// Denotes functions tracked by this checker. 80 ValidAPI = 0, 81 /// The functions commonly/mistakenly used in place of the given API. 82 ErrorAPI = 1, 83 /// The functions which may allocate the data. These are tracked to reduce 84 /// the false alarm rate. 85 PossibleAPI = 2 86 }; 87 /// Stores the information about the allocator and deallocator functions - 88 /// these are the functions the checker is tracking. 89 struct ADFunctionInfo { 90 const char* Name; 91 unsigned int Param; 92 unsigned int DeallocatorIdx; 93 APIKind Kind; 94 }; 95 static const unsigned InvalidIdx = 100000; 96 static const unsigned FunctionsToTrackSize = 8; 97 static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize]; 98 /// The value, which represents no error return value for allocator functions. 99 static const unsigned NoErr = 0; 100 101 /// Given the function name, returns the index of the allocator/deallocator 102 /// function. 103 static unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator); 104 105 void generateDeallocatorMismatchReport(const AllocationPair &AP, 106 const Expr *ArgExpr, 107 CheckerContext &C) const; 108 109 /// Find the allocation site for Sym on the path leading to the node N. 110 const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym, 111 CheckerContext &C) const; 112 113 std::unique_ptr<PathSensitiveBugReport> 114 generateAllocatedDataNotReleasedReport(const AllocationPair &AP, 115 ExplodedNode *N, 116 CheckerContext &C) const; 117 118 /// Mark an AllocationPair interesting for diagnostic reporting. 119 void markInteresting(PathSensitiveBugReport *R, 120 const AllocationPair &AP) const { 121 R->markInteresting(AP.first); 122 R->markInteresting(AP.second->Region); 123 } 124 125 /// The bug visitor which allows us to print extra diagnostics along the 126 /// BugReport path. For example, showing the allocation site of the leaked 127 /// region. 128 class SecKeychainBugVisitor : public BugReporterVisitor { 129 protected: 130 // The allocated region symbol tracked by the main analysis. 131 SymbolRef Sym; 132 133 public: 134 SecKeychainBugVisitor(SymbolRef S) : Sym(S) {} 135 136 void Profile(llvm::FoldingSetNodeID &ID) const override { 137 static int X = 0; 138 ID.AddPointer(&X); 139 ID.AddPointer(Sym); 140 } 141 142 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, 143 BugReporterContext &BRC, 144 PathSensitiveBugReport &BR) override; 145 }; 146 }; 147 } 148 149 /// ProgramState traits to store the currently allocated (and not yet freed) 150 /// symbols. This is a map from the allocated content symbol to the 151 /// corresponding AllocationState. 152 REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData, 153 SymbolRef, 154 MacOSKeychainAPIChecker::AllocationState) 155 156 static bool isEnclosingFunctionParam(const Expr *E) { 157 E = E->IgnoreParenCasts(); 158 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { 159 const ValueDecl *VD = DRE->getDecl(); 160 if (isa<ImplicitParamDecl, ParmVarDecl>(VD)) 161 return true; 162 } 163 return false; 164 } 165 166 const MacOSKeychainAPIChecker::ADFunctionInfo 167 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = { 168 {"SecKeychainItemCopyContent", 4, 3, ValidAPI}, // 0 169 {"SecKeychainFindGenericPassword", 6, 3, ValidAPI}, // 1 170 {"SecKeychainFindInternetPassword", 13, 3, ValidAPI}, // 2 171 {"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI}, // 3 172 {"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI}, // 4 173 {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI}, // 5 174 {"free", 0, InvalidIdx, ErrorAPI}, // 6 175 {"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI}, // 7 176 }; 177 178 unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, 179 bool IsAllocator) { 180 for (unsigned I = 0; I < FunctionsToTrackSize; ++I) { 181 ADFunctionInfo FI = FunctionsToTrack[I]; 182 if (FI.Name != Name) 183 continue; 184 // Make sure the function is of the right type (allocator vs deallocator). 185 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx)) 186 return InvalidIdx; 187 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx)) 188 return InvalidIdx; 189 190 return I; 191 } 192 // The function is not tracked. 193 return InvalidIdx; 194 } 195 196 static bool isBadDeallocationArgument(const MemRegion *Arg) { 197 if (!Arg) 198 return false; 199 return isa<AllocaRegion, BlockDataRegion, TypedRegion>(Arg); 200 } 201 202 /// Given the address expression, retrieve the value it's pointing to. Assume 203 /// that value is itself an address, and return the corresponding symbol. 204 static SymbolRef getAsPointeeSymbol(const Expr *Expr, 205 CheckerContext &C) { 206 ProgramStateRef State = C.getState(); 207 SVal ArgV = C.getSVal(Expr); 208 209 if (std::optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) { 210 StoreManager& SM = C.getStoreManager(); 211 SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol(); 212 if (sym) 213 return sym; 214 } 215 return nullptr; 216 } 217 218 // Report deallocator mismatch. Remove the region from tracking - reporting a 219 // missing free error after this one is redundant. 220 void MacOSKeychainAPIChecker:: 221 generateDeallocatorMismatchReport(const AllocationPair &AP, 222 const Expr *ArgExpr, 223 CheckerContext &C) const { 224 ProgramStateRef State = C.getState(); 225 State = State->remove<AllocatedData>(AP.first); 226 ExplodedNode *N = C.generateNonFatalErrorNode(State); 227 228 if (!N) 229 return; 230 SmallString<80> sbuf; 231 llvm::raw_svector_ostream os(sbuf); 232 unsigned int PDeallocIdx = 233 FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx; 234 235 os << "Deallocator doesn't match the allocator: '" 236 << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; 237 auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); 238 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); 239 Report->addRange(ArgExpr->getSourceRange()); 240 markInteresting(Report.get(), AP); 241 C.emitReport(std::move(Report)); 242 } 243 244 void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 245 CheckerContext &C) const { 246 unsigned idx = InvalidIdx; 247 ProgramStateRef State = C.getState(); 248 249 const FunctionDecl *FD = C.getCalleeDecl(CE); 250 if (!FD || FD->getKind() != Decl::Function) 251 return; 252 253 StringRef funName = C.getCalleeName(FD); 254 if (funName.empty()) 255 return; 256 257 // If it is a call to an allocator function, it could be a double allocation. 258 idx = getTrackedFunctionIndex(funName, true); 259 if (idx != InvalidIdx) { 260 unsigned paramIdx = FunctionsToTrack[idx].Param; 261 if (CE->getNumArgs() <= paramIdx) 262 return; 263 264 const Expr *ArgExpr = CE->getArg(paramIdx); 265 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) 266 if (const AllocationState *AS = State->get<AllocatedData>(V)) { 267 // Remove the value from the state. The new symbol will be added for 268 // tracking when the second allocator is processed in checkPostStmt(). 269 State = State->remove<AllocatedData>(V); 270 ExplodedNode *N = C.generateNonFatalErrorNode(State); 271 if (!N) 272 return; 273 SmallString<128> sbuf; 274 llvm::raw_svector_ostream os(sbuf); 275 unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 276 os << "Allocated data should be released before another call to " 277 << "the allocator: missing a call to '" 278 << FunctionsToTrack[DIdx].Name 279 << "'."; 280 auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); 281 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(V)); 282 Report->addRange(ArgExpr->getSourceRange()); 283 Report->markInteresting(AS->Region); 284 C.emitReport(std::move(Report)); 285 } 286 return; 287 } 288 289 // Is it a call to one of deallocator functions? 290 idx = getTrackedFunctionIndex(funName, false); 291 if (idx == InvalidIdx) 292 return; 293 294 unsigned paramIdx = FunctionsToTrack[idx].Param; 295 if (CE->getNumArgs() <= paramIdx) 296 return; 297 298 // Check the argument to the deallocator. 299 const Expr *ArgExpr = CE->getArg(paramIdx); 300 SVal ArgSVal = C.getSVal(ArgExpr); 301 302 // Undef is reported by another checker. 303 if (ArgSVal.isUndef()) 304 return; 305 306 SymbolRef ArgSM = ArgSVal.getAsLocSymbol(); 307 308 // If the argument is coming from the heap, globals, or unknown, do not 309 // report it. 310 bool RegionArgIsBad = false; 311 if (!ArgSM) { 312 if (!isBadDeallocationArgument(ArgSVal.getAsRegion())) 313 return; 314 RegionArgIsBad = true; 315 } 316 317 // Is the argument to the call being tracked? 318 const AllocationState *AS = State->get<AllocatedData>(ArgSM); 319 if (!AS) 320 return; 321 322 // TODO: We might want to report double free here. 323 // (that would involve tracking all the freed symbols in the checker state). 324 if (RegionArgIsBad) { 325 // It is possible that this is a false positive - the argument might 326 // have entered as an enclosing function parameter. 327 if (isEnclosingFunctionParam(ArgExpr)) 328 return; 329 330 ExplodedNode *N = C.generateNonFatalErrorNode(State); 331 if (!N) 332 return; 333 auto Report = std::make_unique<PathSensitiveBugReport>( 334 BT, "Trying to free data which has not been allocated.", N); 335 Report->addRange(ArgExpr->getSourceRange()); 336 if (AS) 337 Report->markInteresting(AS->Region); 338 C.emitReport(std::move(Report)); 339 return; 340 } 341 342 // Process functions which might deallocate. 343 if (FunctionsToTrack[idx].Kind == PossibleAPI) { 344 345 if (funName == "CFStringCreateWithBytesNoCopy") { 346 const Expr *DeallocatorExpr = CE->getArg(5)->IgnoreParenCasts(); 347 // NULL ~ default deallocator, so warn. 348 if (DeallocatorExpr->isNullPointerConstant(C.getASTContext(), 349 Expr::NPC_ValueDependentIsNotNull)) { 350 const AllocationPair AP = std::make_pair(ArgSM, AS); 351 generateDeallocatorMismatchReport(AP, ArgExpr, C); 352 return; 353 } 354 // One of the default allocators, so warn. 355 if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) { 356 StringRef DeallocatorName = DE->getFoundDecl()->getName(); 357 if (DeallocatorName == "kCFAllocatorDefault" || 358 DeallocatorName == "kCFAllocatorSystemDefault" || 359 DeallocatorName == "kCFAllocatorMalloc") { 360 const AllocationPair AP = std::make_pair(ArgSM, AS); 361 generateDeallocatorMismatchReport(AP, ArgExpr, C); 362 return; 363 } 364 // If kCFAllocatorNull, which does not deallocate, we still have to 365 // find the deallocator. 366 if (DE->getFoundDecl()->getName() == "kCFAllocatorNull") 367 return; 368 } 369 // In all other cases, assume the user supplied a correct deallocator 370 // that will free memory so stop tracking. 371 State = State->remove<AllocatedData>(ArgSM); 372 C.addTransition(State); 373 return; 374 } 375 376 llvm_unreachable("We know of no other possible APIs."); 377 } 378 379 // The call is deallocating a value we previously allocated, so remove it 380 // from the next state. 381 State = State->remove<AllocatedData>(ArgSM); 382 383 // Check if the proper deallocator is used. 384 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 385 if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) { 386 const AllocationPair AP = std::make_pair(ArgSM, AS); 387 generateDeallocatorMismatchReport(AP, ArgExpr, C); 388 return; 389 } 390 391 C.addTransition(State); 392 } 393 394 void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 395 CheckerContext &C) const { 396 ProgramStateRef State = C.getState(); 397 const FunctionDecl *FD = C.getCalleeDecl(CE); 398 if (!FD || FD->getKind() != Decl::Function) 399 return; 400 401 StringRef funName = C.getCalleeName(FD); 402 403 // If a value has been allocated, add it to the set for tracking. 404 unsigned idx = getTrackedFunctionIndex(funName, true); 405 if (idx == InvalidIdx) 406 return; 407 408 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 409 // If the argument entered as an enclosing function parameter, skip it to 410 // avoid false positives. 411 if (isEnclosingFunctionParam(ArgExpr) && 412 C.getLocationContext()->getParent() == nullptr) 413 return; 414 415 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { 416 // If the argument points to something that's not a symbolic region, it 417 // can be: 418 // - unknown (cannot reason about it) 419 // - undefined (already reported by other checker) 420 // - constant (null - should not be tracked, 421 // other constant will generate a compiler warning) 422 // - goto (should be reported by other checker) 423 424 // The call return value symbol should stay alive for as long as the 425 // allocated value symbol, since our diagnostics depend on the value 426 // returned by the call. Ex: Data should only be freed if noErr was 427 // returned during allocation.) 428 SymbolRef RetStatusSymbol = C.getSVal(CE).getAsSymbol(); 429 C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); 430 431 // Track the allocated value in the checker state. 432 State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, 433 RetStatusSymbol)); 434 assert(State); 435 C.addTransition(State); 436 } 437 } 438 439 // TODO: This logic is the same as in Malloc checker. 440 const ExplodedNode * 441 MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N, 442 SymbolRef Sym, 443 CheckerContext &C) const { 444 const LocationContext *LeakContext = N->getLocationContext(); 445 // Walk the ExplodedGraph backwards and find the first node that referred to 446 // the tracked symbol. 447 const ExplodedNode *AllocNode = N; 448 449 while (N) { 450 if (!N->getState()->get<AllocatedData>(Sym)) 451 break; 452 // Allocation node, is the last node in the current or parent context in 453 // which the symbol was tracked. 454 const LocationContext *NContext = N->getLocationContext(); 455 if (NContext == LeakContext || 456 NContext->isParentOf(LeakContext)) 457 AllocNode = N; 458 N = N->pred_empty() ? nullptr : *(N->pred_begin()); 459 } 460 461 return AllocNode; 462 } 463 464 std::unique_ptr<PathSensitiveBugReport> 465 MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( 466 const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const { 467 const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; 468 SmallString<70> sbuf; 469 llvm::raw_svector_ostream os(sbuf); 470 os << "Allocated data is not released: missing a call to '" 471 << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 472 473 // Most bug reports are cached at the location where they occurred. 474 // With leaks, we want to unique them by the location where they were 475 // allocated, and only report a single path. 476 PathDiagnosticLocation LocUsedForUniqueing; 477 const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C); 478 const Stmt *AllocStmt = AllocNode->getStmtForDiagnostics(); 479 480 if (AllocStmt) 481 LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, 482 C.getSourceManager(), 483 AllocNode->getLocationContext()); 484 485 auto Report = std::make_unique<PathSensitiveBugReport>( 486 BT, os.str(), N, LocUsedForUniqueing, 487 AllocNode->getLocationContext()->getDecl()); 488 489 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); 490 markInteresting(Report.get(), AP); 491 return Report; 492 } 493 494 /// If the return symbol is assumed to be error, remove the allocated info 495 /// from consideration. 496 ProgramStateRef MacOSKeychainAPIChecker::evalAssume(ProgramStateRef State, 497 SVal Cond, 498 bool Assumption) const { 499 AllocatedDataTy AMap = State->get<AllocatedData>(); 500 if (AMap.isEmpty()) 501 return State; 502 503 auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymbol()); 504 if (!CondBSE) 505 return State; 506 BinaryOperator::Opcode OpCode = CondBSE->getOpcode(); 507 if (OpCode != BO_EQ && OpCode != BO_NE) 508 return State; 509 510 // Match for a restricted set of patterns for cmparison of error codes. 511 // Note, the comparisons of type '0 == st' are transformed into SymIntExpr. 512 SymbolRef ReturnSymbol = nullptr; 513 if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) { 514 const llvm::APInt &RHS = SIE->getRHS(); 515 bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr) || 516 (OpCode == BO_NE && RHS == NoErr); 517 if (!Assumption) 518 ErrorIsReturned = !ErrorIsReturned; 519 if (ErrorIsReturned) 520 ReturnSymbol = SIE->getLHS(); 521 } 522 523 if (ReturnSymbol) 524 for (auto [Sym, AllocState] : AMap) { 525 if (ReturnSymbol == AllocState.Region) 526 State = State->remove<AllocatedData>(Sym); 527 } 528 529 return State; 530 } 531 532 void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, 533 CheckerContext &C) const { 534 ProgramStateRef State = C.getState(); 535 AllocatedDataTy AMap = State->get<AllocatedData>(); 536 if (AMap.isEmpty()) 537 return; 538 539 bool Changed = false; 540 AllocationPairVec Errors; 541 for (const auto &[Sym, AllocState] : AMap) { 542 if (!SR.isDead(Sym)) 543 continue; 544 545 Changed = true; 546 State = State->remove<AllocatedData>(Sym); 547 // If the allocated symbol is null do not report. 548 ConstraintManager &CMgr = State->getConstraintManager(); 549 ConditionTruthVal AllocFailed = CMgr.isNull(State, Sym); 550 if (AllocFailed.isConstrainedTrue()) 551 continue; 552 Errors.push_back(std::make_pair(Sym, &AllocState)); 553 } 554 if (!Changed) { 555 // Generate the new, cleaned up state. 556 C.addTransition(State); 557 return; 558 } 559 560 static CheckerProgramPointTag Tag(this, "DeadSymbolsLeak"); 561 ExplodedNode *N = C.generateNonFatalErrorNode(C.getState(), &Tag); 562 if (!N) 563 return; 564 565 // Generate the error reports. 566 for (const auto &P : Errors) 567 C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C)); 568 569 // Generate the new, cleaned up state. 570 C.addTransition(State, N); 571 } 572 573 ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( 574 ProgramStateRef State, const InvalidatedSymbols &Escaped, 575 const CallEvent *Call, PointerEscapeKind Kind) const { 576 // FIXME: This branch doesn't make any sense at all, but it is an overfitted 577 // replacement for a previous overfitted code that was making even less sense. 578 if (!Call || Call->getDecl()) 579 return State; 580 581 for (auto I : State->get<AllocatedData>()) { 582 SymbolRef Sym = I.first; 583 if (Escaped.count(Sym)) 584 State = State->remove<AllocatedData>(Sym); 585 586 // This checker is special. Most checkers in fact only track symbols of 587 // SymbolConjured type, eg. symbols returned from functions such as 588 // malloc(). This checker tracks symbols returned as out-parameters. 589 // 590 // When a function is evaluated conservatively, the out-parameter's pointee 591 // base region gets invalidated with a SymbolConjured. If the base region is 592 // larger than the region we're interested in, the value we're interested in 593 // would be SymbolDerived based on that SymbolConjured. However, such 594 // SymbolDerived will never be listed in the Escaped set when the base 595 // region is invalidated because ExprEngine doesn't know which symbols 596 // were derived from a given symbol, while there can be infinitely many 597 // valid symbols derived from any given symbol. 598 // 599 // Hence the extra boilerplate: remove the derived symbol when its parent 600 // symbol escapes. 601 // 602 if (const auto *SD = dyn_cast<SymbolDerived>(Sym)) { 603 SymbolRef ParentSym = SD->getParentSymbol(); 604 if (Escaped.count(ParentSym)) 605 State = State->remove<AllocatedData>(Sym); 606 } 607 } 608 return State; 609 } 610 611 PathDiagnosticPieceRef 612 MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( 613 const ExplodedNode *N, BugReporterContext &BRC, 614 PathSensitiveBugReport &BR) { 615 const AllocationState *AS = N->getState()->get<AllocatedData>(Sym); 616 if (!AS) 617 return nullptr; 618 const AllocationState *ASPrev = 619 N->getFirstPred()->getState()->get<AllocatedData>(Sym); 620 if (ASPrev) 621 return nullptr; 622 623 // (!ASPrev && AS) ~ We started tracking symbol in node N, it must be the 624 // allocation site. 625 const CallExpr *CE = 626 cast<CallExpr>(N->getLocation().castAs<StmtPoint>().getStmt()); 627 const FunctionDecl *funDecl = CE->getDirectCallee(); 628 assert(funDecl && "We do not support indirect function calls as of now."); 629 StringRef funName = funDecl->getName(); 630 631 // Get the expression of the corresponding argument. 632 unsigned Idx = getTrackedFunctionIndex(funName, true); 633 assert(Idx != InvalidIdx && "This should be a call to an allocator."); 634 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[Idx].Param); 635 PathDiagnosticLocation Pos(ArgExpr, BRC.getSourceManager(), 636 N->getLocationContext()); 637 return std::make_shared<PathDiagnosticEventPiece>(Pos, 638 "Data is allocated here."); 639 } 640 641 void MacOSKeychainAPIChecker::printState(raw_ostream &Out, 642 ProgramStateRef State, 643 const char *NL, 644 const char *Sep) const { 645 646 AllocatedDataTy AMap = State->get<AllocatedData>(); 647 648 if (!AMap.isEmpty()) { 649 Out << Sep << "KeychainAPIChecker :" << NL; 650 for (SymbolRef Sym : llvm::make_first_range(AMap)) { 651 Sym->dumpToStream(Out); 652 } 653 } 654 } 655 656 657 void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 658 mgr.registerChecker<MacOSKeychainAPIChecker>(); 659 } 660 661 bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) { 662 return true; 663 } 664