MacOSKeychainAPIChecker.cpp revision 239462
1226586Sdim//==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==// 2226586Sdim// 3226586Sdim// The LLVM Compiler Infrastructure 4226586Sdim// 5226586Sdim// This file is distributed under the University of Illinois Open Source 6226586Sdim// License. See LICENSE.TXT for details. 7226586Sdim// 8226586Sdim//===----------------------------------------------------------------------===// 9226586Sdim// This checker flags misuses of KeyChainAPI. In particular, the password data 10226586Sdim// allocated/returned by SecKeychainItemCopyContent, 11226586Sdim// SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has 12226586Sdim// to be freed using a call to SecKeychainItemFreeContent. 13226586Sdim//===----------------------------------------------------------------------===// 14226586Sdim 15226586Sdim#include "ClangSACheckers.h" 16226586Sdim#include "clang/StaticAnalyzer/Core/Checker.h" 17226586Sdim#include "clang/StaticAnalyzer/Core/CheckerManager.h" 18226586Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19226586Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 20226586Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 21226586Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 22234353Sdim#include "llvm/ADT/SmallString.h" 23226586Sdim 24226586Sdimusing namespace clang; 25226586Sdimusing namespace ento; 26226586Sdim 27226586Sdimnamespace { 28226586Sdimclass MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, 29226586Sdim check::PreStmt<ReturnStmt>, 30226586Sdim check::PostStmt<CallExpr>, 31226586Sdim check::EndPath, 32226586Sdim check::DeadSymbols> { 33234353Sdim mutable OwningPtr<BugType> BT; 34226586Sdim 35226586Sdimpublic: 36226586Sdim /// AllocationState is a part of the checker specific state together with the 37226586Sdim /// MemRegion corresponding to the allocated data. 38226586Sdim struct AllocationState { 39226586Sdim /// The index of the allocator function. 40226586Sdim unsigned int AllocatorIdx; 41226586Sdim SymbolRef Region; 42226586Sdim 43226586Sdim AllocationState(const Expr *E, unsigned int Idx, SymbolRef R) : 44226586Sdim AllocatorIdx(Idx), 45226586Sdim Region(R) {} 46226586Sdim 47226586Sdim bool operator==(const AllocationState &X) const { 48226586Sdim return (AllocatorIdx == X.AllocatorIdx && 49226586Sdim Region == X.Region); 50226586Sdim } 51226586Sdim 52226586Sdim void Profile(llvm::FoldingSetNodeID &ID) const { 53226586Sdim ID.AddInteger(AllocatorIdx); 54226586Sdim ID.AddPointer(Region); 55226586Sdim } 56226586Sdim }; 57226586Sdim 58226586Sdim void checkPreStmt(const CallExpr *S, CheckerContext &C) const; 59226586Sdim void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; 60226586Sdim void checkPostStmt(const CallExpr *S, CheckerContext &C) const; 61226586Sdim void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; 62234353Sdim void checkEndPath(CheckerContext &C) const; 63226586Sdim 64226586Sdimprivate: 65226586Sdim typedef std::pair<SymbolRef, const AllocationState*> AllocationPair; 66226586Sdim typedef llvm::SmallVector<AllocationPair, 2> AllocationPairVec; 67226586Sdim 68226586Sdim enum APIKind { 69226586Sdim /// Denotes functions tracked by this checker. 70226586Sdim ValidAPI = 0, 71226586Sdim /// The functions commonly/mistakenly used in place of the given API. 72226586Sdim ErrorAPI = 1, 73226586Sdim /// The functions which may allocate the data. These are tracked to reduce 74226586Sdim /// the false alarm rate. 75226586Sdim PossibleAPI = 2 76226586Sdim }; 77226586Sdim /// Stores the information about the allocator and deallocator functions - 78226586Sdim /// these are the functions the checker is tracking. 79226586Sdim struct ADFunctionInfo { 80226586Sdim const char* Name; 81226586Sdim unsigned int Param; 82226586Sdim unsigned int DeallocatorIdx; 83226586Sdim APIKind Kind; 84226586Sdim }; 85226586Sdim static const unsigned InvalidIdx = 100000; 86226586Sdim static const unsigned FunctionsToTrackSize = 8; 87226586Sdim static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize]; 88226586Sdim /// The value, which represents no error return value for allocator functions. 89226586Sdim static const unsigned NoErr = 0; 90226586Sdim 91226586Sdim /// Given the function name, returns the index of the allocator/deallocator 92226586Sdim /// function. 93226586Sdim static unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator); 94226586Sdim 95226586Sdim inline void initBugType() const { 96226586Sdim if (!BT) 97226586Sdim BT.reset(new BugType("Improper use of SecKeychain API", "Mac OS API")); 98226586Sdim } 99226586Sdim 100226586Sdim void generateDeallocatorMismatchReport(const AllocationPair &AP, 101226586Sdim const Expr *ArgExpr, 102226586Sdim CheckerContext &C) const; 103226586Sdim 104234353Sdim /// Find the allocation site for Sym on the path leading to the node N. 105234353Sdim const Stmt *getAllocationSite(const ExplodedNode *N, SymbolRef Sym, 106234353Sdim CheckerContext &C) const; 107234353Sdim 108226586Sdim BugReport *generateAllocatedDataNotReleasedReport(const AllocationPair &AP, 109234353Sdim ExplodedNode *N, 110234353Sdim CheckerContext &C) const; 111226586Sdim 112226586Sdim /// Check if RetSym evaluates to an error value in the current state. 113226586Sdim bool definitelyReturnedError(SymbolRef RetSym, 114234353Sdim ProgramStateRef State, 115226586Sdim SValBuilder &Builder, 116226586Sdim bool noError = false) const; 117226586Sdim 118226586Sdim /// Check if RetSym evaluates to a NoErr value in the current state. 119226586Sdim bool definitelyDidnotReturnError(SymbolRef RetSym, 120234353Sdim ProgramStateRef State, 121226586Sdim SValBuilder &Builder) const { 122226586Sdim return definitelyReturnedError(RetSym, State, Builder, true); 123226586Sdim } 124234353Sdim 125234353Sdim /// Mark an AllocationPair interesting for diagnostic reporting. 126234353Sdim void markInteresting(BugReport *R, const AllocationPair &AP) const { 127234353Sdim R->markInteresting(AP.first); 128234353Sdim R->markInteresting(AP.second->Region); 129234353Sdim } 130226586Sdim 131226586Sdim /// The bug visitor which allows us to print extra diagnostics along the 132226586Sdim /// BugReport path. For example, showing the allocation site of the leaked 133226586Sdim /// region. 134234353Sdim class SecKeychainBugVisitor 135234353Sdim : public BugReporterVisitorImpl<SecKeychainBugVisitor> { 136226586Sdim protected: 137226586Sdim // The allocated region symbol tracked by the main analysis. 138226586Sdim SymbolRef Sym; 139226586Sdim 140226586Sdim public: 141226586Sdim SecKeychainBugVisitor(SymbolRef S) : Sym(S) {} 142226586Sdim virtual ~SecKeychainBugVisitor() {} 143226586Sdim 144226586Sdim void Profile(llvm::FoldingSetNodeID &ID) const { 145226586Sdim static int X = 0; 146226586Sdim ID.AddPointer(&X); 147226586Sdim ID.AddPointer(Sym); 148226586Sdim } 149226586Sdim 150226586Sdim PathDiagnosticPiece *VisitNode(const ExplodedNode *N, 151226586Sdim const ExplodedNode *PrevN, 152226586Sdim BugReporterContext &BRC, 153226586Sdim BugReport &BR); 154226586Sdim }; 155226586Sdim}; 156226586Sdim} 157226586Sdim 158226586Sdim/// ProgramState traits to store the currently allocated (and not yet freed) 159226586Sdim/// symbols. This is a map from the allocated content symbol to the 160226586Sdim/// corresponding AllocationState. 161226586Sdimtypedef llvm::ImmutableMap<SymbolRef, 162226586Sdim MacOSKeychainAPIChecker::AllocationState> AllocatedSetTy; 163226586Sdim 164226586Sdimnamespace { struct AllocatedData {}; } 165226586Sdimnamespace clang { namespace ento { 166226586Sdimtemplate<> struct ProgramStateTrait<AllocatedData> 167226586Sdim : public ProgramStatePartialTrait<AllocatedSetTy > { 168226586Sdim static void *GDMIndex() { static int index = 0; return &index; } 169226586Sdim}; 170226586Sdim}} 171226586Sdim 172226586Sdimstatic bool isEnclosingFunctionParam(const Expr *E) { 173226586Sdim E = E->IgnoreParenCasts(); 174226586Sdim if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { 175226586Sdim const ValueDecl *VD = DRE->getDecl(); 176226586Sdim if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD)) 177226586Sdim return true; 178226586Sdim } 179226586Sdim return false; 180226586Sdim} 181226586Sdim 182226586Sdimconst MacOSKeychainAPIChecker::ADFunctionInfo 183226586Sdim MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = { 184226586Sdim {"SecKeychainItemCopyContent", 4, 3, ValidAPI}, // 0 185226586Sdim {"SecKeychainFindGenericPassword", 6, 3, ValidAPI}, // 1 186226586Sdim {"SecKeychainFindInternetPassword", 13, 3, ValidAPI}, // 2 187226586Sdim {"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI}, // 3 188226586Sdim {"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI}, // 4 189226586Sdim {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI}, // 5 190226586Sdim {"free", 0, InvalidIdx, ErrorAPI}, // 6 191226586Sdim {"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI}, // 7 192226586Sdim}; 193226586Sdim 194226586Sdimunsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, 195226586Sdim bool IsAllocator) { 196226586Sdim for (unsigned I = 0; I < FunctionsToTrackSize; ++I) { 197226586Sdim ADFunctionInfo FI = FunctionsToTrack[I]; 198226586Sdim if (FI.Name != Name) 199226586Sdim continue; 200226586Sdim // Make sure the function is of the right type (allocator vs deallocator). 201226586Sdim if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx)) 202226586Sdim return InvalidIdx; 203226586Sdim if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx)) 204226586Sdim return InvalidIdx; 205226586Sdim 206226586Sdim return I; 207226586Sdim } 208226586Sdim // The function is not tracked. 209226586Sdim return InvalidIdx; 210226586Sdim} 211226586Sdim 212226586Sdimstatic bool isBadDeallocationArgument(const MemRegion *Arg) { 213234353Sdim if (!Arg) 214234353Sdim return false; 215226586Sdim if (isa<AllocaRegion>(Arg) || 216226586Sdim isa<BlockDataRegion>(Arg) || 217226586Sdim isa<TypedRegion>(Arg)) { 218226586Sdim return true; 219226586Sdim } 220226586Sdim return false; 221226586Sdim} 222234353Sdim 223226586Sdim/// Given the address expression, retrieve the value it's pointing to. Assume 224226586Sdim/// that value is itself an address, and return the corresponding symbol. 225226586Sdimstatic SymbolRef getAsPointeeSymbol(const Expr *Expr, 226226586Sdim CheckerContext &C) { 227234353Sdim ProgramStateRef State = C.getState(); 228234353Sdim SVal ArgV = State->getSVal(Expr, C.getLocationContext()); 229226586Sdim 230226586Sdim if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) { 231226586Sdim StoreManager& SM = C.getStoreManager(); 232234353Sdim SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol(); 233234353Sdim if (sym) 234234353Sdim return sym; 235226586Sdim } 236226586Sdim return 0; 237226586Sdim} 238226586Sdim 239226586Sdim// When checking for error code, we need to consider the following cases: 240226586Sdim// 1) noErr / [0] 241226586Sdim// 2) someErr / [1, inf] 242226586Sdim// 3) unknown 243226586Sdim// If noError, returns true iff (1). 244226586Sdim// If !noError, returns true iff (2). 245226586Sdimbool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym, 246234353Sdim ProgramStateRef State, 247226586Sdim SValBuilder &Builder, 248226586Sdim bool noError) const { 249226586Sdim DefinedOrUnknownSVal NoErrVal = Builder.makeIntVal(NoErr, 250226586Sdim Builder.getSymbolManager().getType(RetSym)); 251226586Sdim DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal, 252226586Sdim nonloc::SymbolVal(RetSym)); 253234353Sdim ProgramStateRef ErrState = State->assume(NoErr, noError); 254226586Sdim if (ErrState == State) { 255226586Sdim return true; 256226586Sdim } 257226586Sdim 258226586Sdim return false; 259226586Sdim} 260226586Sdim 261226586Sdim// Report deallocator mismatch. Remove the region from tracking - reporting a 262226586Sdim// missing free error after this one is redundant. 263226586Sdimvoid MacOSKeychainAPIChecker:: 264226586Sdim generateDeallocatorMismatchReport(const AllocationPair &AP, 265226586Sdim const Expr *ArgExpr, 266226586Sdim CheckerContext &C) const { 267234353Sdim ProgramStateRef State = C.getState(); 268226586Sdim State = State->remove<AllocatedData>(AP.first); 269234353Sdim ExplodedNode *N = C.addTransition(State); 270226586Sdim 271226586Sdim if (!N) 272226586Sdim return; 273226586Sdim initBugType(); 274234353Sdim SmallString<80> sbuf; 275226586Sdim llvm::raw_svector_ostream os(sbuf); 276226586Sdim unsigned int PDeallocIdx = 277226586Sdim FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx; 278226586Sdim 279226586Sdim os << "Deallocator doesn't match the allocator: '" 280226586Sdim << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; 281226586Sdim BugReport *Report = new BugReport(*BT, os.str(), N); 282226586Sdim Report->addVisitor(new SecKeychainBugVisitor(AP.first)); 283226586Sdim Report->addRange(ArgExpr->getSourceRange()); 284234353Sdim markInteresting(Report, AP); 285226586Sdim C.EmitReport(Report); 286226586Sdim} 287226586Sdim 288226586Sdimvoid MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 289226586Sdim CheckerContext &C) const { 290226586Sdim unsigned idx = InvalidIdx; 291234353Sdim ProgramStateRef State = C.getState(); 292226586Sdim 293239462Sdim const FunctionDecl *FD = C.getCalleeDecl(CE); 294239462Sdim if (!FD || FD->getKind() != Decl::Function) 295239462Sdim return; 296239462Sdim 297239462Sdim StringRef funName = C.getCalleeName(FD); 298234353Sdim if (funName.empty()) 299226586Sdim return; 300226586Sdim 301226586Sdim // If it is a call to an allocator function, it could be a double allocation. 302226586Sdim idx = getTrackedFunctionIndex(funName, true); 303226586Sdim if (idx != InvalidIdx) { 304226586Sdim const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 305226586Sdim if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) 306226586Sdim if (const AllocationState *AS = State->get<AllocatedData>(V)) { 307226586Sdim if (!definitelyReturnedError(AS->Region, State, C.getSValBuilder())) { 308226586Sdim // Remove the value from the state. The new symbol will be added for 309226586Sdim // tracking when the second allocator is processed in checkPostStmt(). 310226586Sdim State = State->remove<AllocatedData>(V); 311234353Sdim ExplodedNode *N = C.addTransition(State); 312226586Sdim if (!N) 313226586Sdim return; 314226586Sdim initBugType(); 315234353Sdim SmallString<128> sbuf; 316226586Sdim llvm::raw_svector_ostream os(sbuf); 317226586Sdim unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 318226586Sdim os << "Allocated data should be released before another call to " 319226586Sdim << "the allocator: missing a call to '" 320226586Sdim << FunctionsToTrack[DIdx].Name 321226586Sdim << "'."; 322226586Sdim BugReport *Report = new BugReport(*BT, os.str(), N); 323226586Sdim Report->addVisitor(new SecKeychainBugVisitor(V)); 324226586Sdim Report->addRange(ArgExpr->getSourceRange()); 325234353Sdim Report->markInteresting(AS->Region); 326226586Sdim C.EmitReport(Report); 327226586Sdim } 328226586Sdim } 329226586Sdim return; 330226586Sdim } 331226586Sdim 332226586Sdim // Is it a call to one of deallocator functions? 333226586Sdim idx = getTrackedFunctionIndex(funName, false); 334226586Sdim if (idx == InvalidIdx) 335226586Sdim return; 336226586Sdim 337226586Sdim // Check the argument to the deallocator. 338226586Sdim const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 339234353Sdim SVal ArgSVal = State->getSVal(ArgExpr, C.getLocationContext()); 340226586Sdim 341226586Sdim // Undef is reported by another checker. 342226586Sdim if (ArgSVal.isUndef()) 343226586Sdim return; 344226586Sdim 345234353Sdim SymbolRef ArgSM = ArgSVal.getAsLocSymbol(); 346226586Sdim 347226586Sdim // If the argument is coming from the heap, globals, or unknown, do not 348226586Sdim // report it. 349234353Sdim bool RegionArgIsBad = false; 350234353Sdim if (!ArgSM) { 351234353Sdim if (!isBadDeallocationArgument(ArgSVal.getAsRegion())) 352234353Sdim return; 353234353Sdim RegionArgIsBad = true; 354234353Sdim } 355226586Sdim 356226586Sdim // Is the argument to the call being tracked? 357226586Sdim const AllocationState *AS = State->get<AllocatedData>(ArgSM); 358226586Sdim if (!AS && FunctionsToTrack[idx].Kind != ValidAPI) { 359226586Sdim return; 360226586Sdim } 361226586Sdim // If trying to free data which has not been allocated yet, report as a bug. 362226586Sdim // TODO: We might want a more precise diagnostic for double free 363226586Sdim // (that would involve tracking all the freed symbols in the checker state). 364226586Sdim if (!AS || RegionArgIsBad) { 365226586Sdim // It is possible that this is a false positive - the argument might 366226586Sdim // have entered as an enclosing function parameter. 367226586Sdim if (isEnclosingFunctionParam(ArgExpr)) 368226586Sdim return; 369226586Sdim 370234353Sdim ExplodedNode *N = C.addTransition(State); 371226586Sdim if (!N) 372226586Sdim return; 373226586Sdim initBugType(); 374226586Sdim BugReport *Report = new BugReport(*BT, 375226586Sdim "Trying to free data which has not been allocated.", N); 376226586Sdim Report->addRange(ArgExpr->getSourceRange()); 377234353Sdim if (AS) 378234353Sdim Report->markInteresting(AS->Region); 379226586Sdim C.EmitReport(Report); 380226586Sdim return; 381226586Sdim } 382226586Sdim 383226586Sdim // Process functions which might deallocate. 384226586Sdim if (FunctionsToTrack[idx].Kind == PossibleAPI) { 385226586Sdim 386226586Sdim if (funName == "CFStringCreateWithBytesNoCopy") { 387226586Sdim const Expr *DeallocatorExpr = CE->getArg(5)->IgnoreParenCasts(); 388226586Sdim // NULL ~ default deallocator, so warn. 389226586Sdim if (DeallocatorExpr->isNullPointerConstant(C.getASTContext(), 390226586Sdim Expr::NPC_ValueDependentIsNotNull)) { 391226586Sdim const AllocationPair AP = std::make_pair(ArgSM, AS); 392226586Sdim generateDeallocatorMismatchReport(AP, ArgExpr, C); 393226586Sdim return; 394226586Sdim } 395226586Sdim // One of the default allocators, so warn. 396226586Sdim if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) { 397226586Sdim StringRef DeallocatorName = DE->getFoundDecl()->getName(); 398226586Sdim if (DeallocatorName == "kCFAllocatorDefault" || 399226586Sdim DeallocatorName == "kCFAllocatorSystemDefault" || 400226586Sdim DeallocatorName == "kCFAllocatorMalloc") { 401226586Sdim const AllocationPair AP = std::make_pair(ArgSM, AS); 402226586Sdim generateDeallocatorMismatchReport(AP, ArgExpr, C); 403226586Sdim return; 404226586Sdim } 405226586Sdim // If kCFAllocatorNull, which does not deallocate, we still have to 406226586Sdim // find the deallocator. Otherwise, assume that the user had written a 407226586Sdim // custom deallocator which does the right thing. 408226586Sdim if (DE->getFoundDecl()->getName() != "kCFAllocatorNull") { 409226586Sdim State = State->remove<AllocatedData>(ArgSM); 410226586Sdim C.addTransition(State); 411226586Sdim return; 412226586Sdim } 413226586Sdim } 414226586Sdim } 415226586Sdim return; 416226586Sdim } 417226586Sdim 418226586Sdim // The call is deallocating a value we previously allocated, so remove it 419226586Sdim // from the next state. 420226586Sdim State = State->remove<AllocatedData>(ArgSM); 421226586Sdim 422226586Sdim // Check if the proper deallocator is used. 423226586Sdim unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 424226586Sdim if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) { 425226586Sdim const AllocationPair AP = std::make_pair(ArgSM, AS); 426226586Sdim generateDeallocatorMismatchReport(AP, ArgExpr, C); 427226586Sdim return; 428226586Sdim } 429226586Sdim 430234353Sdim // If the buffer can be null and the return status can be an error, 431234353Sdim // report a bad call to free. 432234353Sdim if (State->assume(cast<DefinedSVal>(ArgSVal), false) && 433234353Sdim !definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) { 434234353Sdim ExplodedNode *N = C.addTransition(State); 435226586Sdim if (!N) 436226586Sdim return; 437226586Sdim initBugType(); 438226586Sdim BugReport *Report = new BugReport(*BT, 439234353Sdim "Only call free if a valid (non-NULL) buffer was returned.", N); 440226586Sdim Report->addVisitor(new SecKeychainBugVisitor(ArgSM)); 441226586Sdim Report->addRange(ArgExpr->getSourceRange()); 442234353Sdim Report->markInteresting(AS->Region); 443226586Sdim C.EmitReport(Report); 444226586Sdim return; 445226586Sdim } 446226586Sdim 447226586Sdim C.addTransition(State); 448226586Sdim} 449226586Sdim 450226586Sdimvoid MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 451226586Sdim CheckerContext &C) const { 452234353Sdim ProgramStateRef State = C.getState(); 453239462Sdim const FunctionDecl *FD = C.getCalleeDecl(CE); 454239462Sdim if (!FD || FD->getKind() != Decl::Function) 455239462Sdim return; 456226586Sdim 457239462Sdim StringRef funName = C.getCalleeName(FD); 458239462Sdim 459226586Sdim // If a value has been allocated, add it to the set for tracking. 460226586Sdim unsigned idx = getTrackedFunctionIndex(funName, true); 461226586Sdim if (idx == InvalidIdx) 462226586Sdim return; 463226586Sdim 464226586Sdim const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 465226586Sdim // If the argument entered as an enclosing function parameter, skip it to 466226586Sdim // avoid false positives. 467234353Sdim if (isEnclosingFunctionParam(ArgExpr) && 468234353Sdim C.getLocationContext()->getParent() == 0) 469226586Sdim return; 470226586Sdim 471226586Sdim if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { 472226586Sdim // If the argument points to something that's not a symbolic region, it 473226586Sdim // can be: 474226586Sdim // - unknown (cannot reason about it) 475226586Sdim // - undefined (already reported by other checker) 476226586Sdim // - constant (null - should not be tracked, 477226586Sdim // other constant will generate a compiler warning) 478226586Sdim // - goto (should be reported by other checker) 479226586Sdim 480226586Sdim // The call return value symbol should stay alive for as long as the 481226586Sdim // allocated value symbol, since our diagnostics depend on the value 482226586Sdim // returned by the call. Ex: Data should only be freed if noErr was 483226586Sdim // returned during allocation.) 484234353Sdim SymbolRef RetStatusSymbol = 485234353Sdim State->getSVal(CE, C.getLocationContext()).getAsSymbol(); 486226586Sdim C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); 487226586Sdim 488226586Sdim // Track the allocated value in the checker state. 489226586Sdim State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, 490226586Sdim RetStatusSymbol)); 491226586Sdim assert(State); 492226586Sdim C.addTransition(State); 493226586Sdim } 494226586Sdim} 495226586Sdim 496226586Sdimvoid MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 497226586Sdim CheckerContext &C) const { 498226586Sdim const Expr *retExpr = S->getRetValue(); 499226586Sdim if (!retExpr) 500226586Sdim return; 501226586Sdim 502234353Sdim // If inside inlined call, skip it. 503234353Sdim const LocationContext *LC = C.getLocationContext(); 504234353Sdim if (LC->getParent() != 0) 505234353Sdim return; 506234353Sdim 507226586Sdim // Check if the value is escaping through the return. 508234353Sdim ProgramStateRef state = C.getState(); 509234353Sdim SymbolRef sym = state->getSVal(retExpr, LC).getAsLocSymbol(); 510234353Sdim if (!sym) 511226586Sdim return; 512234353Sdim state = state->remove<AllocatedData>(sym); 513226586Sdim 514226586Sdim // Proceed from the new state. 515226586Sdim C.addTransition(state); 516226586Sdim} 517226586Sdim 518234353Sdim// TODO: This logic is the same as in Malloc checker. 519234353Sdimconst Stmt * 520234353SdimMacOSKeychainAPIChecker::getAllocationSite(const ExplodedNode *N, 521234353Sdim SymbolRef Sym, 522234353Sdim CheckerContext &C) const { 523234353Sdim const LocationContext *LeakContext = N->getLocationContext(); 524234353Sdim // Walk the ExplodedGraph backwards and find the first node that referred to 525234353Sdim // the tracked symbol. 526234353Sdim const ExplodedNode *AllocNode = N; 527234353Sdim 528234353Sdim while (N) { 529234353Sdim if (!N->getState()->get<AllocatedData>(Sym)) 530234353Sdim break; 531234353Sdim // Allocation node, is the last node in the current context in which the 532234353Sdim // symbol was tracked. 533234353Sdim if (N->getLocationContext() == LeakContext) 534234353Sdim AllocNode = N; 535234353Sdim N = N->pred_empty() ? NULL : *(N->pred_begin()); 536234353Sdim } 537234353Sdim 538234353Sdim ProgramPoint P = AllocNode->getLocation(); 539239462Sdim if (CallExitEnd *Exit = dyn_cast<CallExitEnd>(&P)) 540239462Sdim return Exit->getCalleeContext()->getCallSite(); 541239462Sdim if (clang::PostStmt *PS = dyn_cast<clang::PostStmt>(&P)) 542239462Sdim return PS->getStmt(); 543239462Sdim return 0; 544234353Sdim} 545234353Sdim 546226586SdimBugReport *MacOSKeychainAPIChecker:: 547226586Sdim generateAllocatedDataNotReleasedReport(const AllocationPair &AP, 548234353Sdim ExplodedNode *N, 549234353Sdim CheckerContext &C) const { 550226586Sdim const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; 551226586Sdim initBugType(); 552234353Sdim SmallString<70> sbuf; 553226586Sdim llvm::raw_svector_ostream os(sbuf); 554226586Sdim os << "Allocated data is not released: missing a call to '" 555226586Sdim << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 556234353Sdim 557234353Sdim // Most bug reports are cached at the location where they occurred. 558234353Sdim // With leaks, we want to unique them by the location where they were 559234353Sdim // allocated, and only report a single path. 560234353Sdim PathDiagnosticLocation LocUsedForUniqueing; 561234353Sdim if (const Stmt *AllocStmt = getAllocationSite(N, AP.first, C)) 562234353Sdim LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, 563234353Sdim C.getSourceManager(), N->getLocationContext()); 564234353Sdim 565234353Sdim BugReport *Report = new BugReport(*BT, os.str(), N, LocUsedForUniqueing); 566226586Sdim Report->addVisitor(new SecKeychainBugVisitor(AP.first)); 567234353Sdim markInteresting(Report, AP); 568226586Sdim return Report; 569226586Sdim} 570226586Sdim 571226586Sdimvoid MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, 572226586Sdim CheckerContext &C) const { 573234353Sdim ProgramStateRef State = C.getState(); 574226586Sdim AllocatedSetTy ASet = State->get<AllocatedData>(); 575226586Sdim if (ASet.isEmpty()) 576226586Sdim return; 577226586Sdim 578226586Sdim bool Changed = false; 579226586Sdim AllocationPairVec Errors; 580226586Sdim for (AllocatedSetTy::iterator I = ASet.begin(), E = ASet.end(); I != E; ++I) { 581226586Sdim if (SR.isLive(I->first)) 582226586Sdim continue; 583226586Sdim 584226586Sdim Changed = true; 585226586Sdim State = State->remove<AllocatedData>(I->first); 586226586Sdim // If the allocated symbol is null or if the allocation call might have 587226586Sdim // returned an error, do not report. 588226586Sdim if (State->getSymVal(I->first) || 589226586Sdim definitelyReturnedError(I->second.Region, State, C.getSValBuilder())) 590226586Sdim continue; 591226586Sdim Errors.push_back(std::make_pair(I->first, &I->second)); 592226586Sdim } 593234353Sdim if (!Changed) { 594234353Sdim // Generate the new, cleaned up state. 595234353Sdim C.addTransition(State); 596226586Sdim return; 597234353Sdim } 598226586Sdim 599234353Sdim static SimpleProgramPointTag Tag("MacOSKeychainAPIChecker : DeadSymbolsLeak"); 600234353Sdim ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); 601226586Sdim 602226586Sdim // Generate the error reports. 603226586Sdim for (AllocationPairVec::iterator I = Errors.begin(), E = Errors.end(); 604226586Sdim I != E; ++I) { 605234353Sdim C.EmitReport(generateAllocatedDataNotReleasedReport(*I, N, C)); 606226586Sdim } 607234353Sdim 608234353Sdim // Generate the new, cleaned up state. 609234353Sdim C.addTransition(State, N); 610226586Sdim} 611226586Sdim 612226586Sdim// TODO: Remove this after we ensure that checkDeadSymbols are always called. 613234353Sdimvoid MacOSKeychainAPIChecker::checkEndPath(CheckerContext &C) const { 614234353Sdim ProgramStateRef state = C.getState(); 615234353Sdim 616234353Sdim // If inside inlined call, skip it. 617234353Sdim if (C.getLocationContext()->getParent() != 0) 618234353Sdim return; 619234353Sdim 620226586Sdim AllocatedSetTy AS = state->get<AllocatedData>(); 621226586Sdim if (AS.isEmpty()) 622226586Sdim return; 623226586Sdim 624226586Sdim // Anything which has been allocated but not freed (nor escaped) will be 625226586Sdim // found here, so report it. 626226586Sdim bool Changed = false; 627226586Sdim AllocationPairVec Errors; 628226586Sdim for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 629226586Sdim Changed = true; 630226586Sdim state = state->remove<AllocatedData>(I->first); 631226586Sdim // If the allocated symbol is null or if error code was returned at 632226586Sdim // allocation, do not report. 633226586Sdim if (state->getSymVal(I.getKey()) || 634226586Sdim definitelyReturnedError(I->second.Region, state, 635234353Sdim C.getSValBuilder())) { 636226586Sdim continue; 637226586Sdim } 638226586Sdim Errors.push_back(std::make_pair(I->first, &I->second)); 639226586Sdim } 640226586Sdim 641226586Sdim // If no change, do not generate a new state. 642234353Sdim if (!Changed) { 643234353Sdim C.addTransition(state); 644226586Sdim return; 645234353Sdim } 646226586Sdim 647234353Sdim static SimpleProgramPointTag Tag("MacOSKeychainAPIChecker : EndPathLeak"); 648234353Sdim ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); 649226586Sdim 650226586Sdim // Generate the error reports. 651226586Sdim for (AllocationPairVec::iterator I = Errors.begin(), E = Errors.end(); 652226586Sdim I != E; ++I) { 653234353Sdim C.EmitReport(generateAllocatedDataNotReleasedReport(*I, N, C)); 654226586Sdim } 655234353Sdim 656234353Sdim C.addTransition(state, N); 657226586Sdim} 658226586Sdim 659226586Sdim 660226586SdimPathDiagnosticPiece *MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( 661226586Sdim const ExplodedNode *N, 662226586Sdim const ExplodedNode *PrevN, 663226586Sdim BugReporterContext &BRC, 664226586Sdim BugReport &BR) { 665226586Sdim const AllocationState *AS = N->getState()->get<AllocatedData>(Sym); 666226586Sdim if (!AS) 667226586Sdim return 0; 668226586Sdim const AllocationState *ASPrev = PrevN->getState()->get<AllocatedData>(Sym); 669226586Sdim if (ASPrev) 670226586Sdim return 0; 671226586Sdim 672226586Sdim // (!ASPrev && AS) ~ We started tracking symbol in node N, it must be the 673226586Sdim // allocation site. 674226586Sdim const CallExpr *CE = cast<CallExpr>(cast<StmtPoint>(N->getLocation()) 675226586Sdim .getStmt()); 676226586Sdim const FunctionDecl *funDecl = CE->getDirectCallee(); 677226586Sdim assert(funDecl && "We do not support indirect function calls as of now."); 678226586Sdim StringRef funName = funDecl->getName(); 679226586Sdim 680226586Sdim // Get the expression of the corresponding argument. 681226586Sdim unsigned Idx = getTrackedFunctionIndex(funName, true); 682226586Sdim assert(Idx != InvalidIdx && "This should be a call to an allocator."); 683226586Sdim const Expr *ArgExpr = CE->getArg(FunctionsToTrack[Idx].Param); 684226586Sdim PathDiagnosticLocation Pos(ArgExpr, BRC.getSourceManager(), 685226586Sdim N->getLocationContext()); 686226586Sdim return new PathDiagnosticEventPiece(Pos, "Data is allocated here."); 687226586Sdim} 688226586Sdim 689226586Sdimvoid ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 690226586Sdim mgr.registerChecker<MacOSKeychainAPIChecker>(); 691226586Sdim} 692