MacOSKeychainAPIChecker.cpp revision dd6060ebbd92842fbebd5d383f5ad48b29e7c99c
1//==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// This checker flags misuses of KeyChainAPI. In particular, the password data 10// allocated/returned by SecKeychainItemCopyContent, 11// SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has 12// to be freed using a call to SecKeychainItemFreeContent. 13//===----------------------------------------------------------------------===// 14 15#include "ClangSACheckers.h" 16#include "clang/StaticAnalyzer/Core/Checker.h" 17#include "clang/StaticAnalyzer/Core/CheckerManager.h" 18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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 23using namespace clang; 24using namespace ento; 25 26namespace { 27class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, 28 check::PreStmt<ReturnStmt>, 29 check::PostStmt<CallExpr>, 30 check::EndPath, 31 check::DeadSymbols> { 32 mutable llvm::OwningPtr<BugType> BT; 33 34public: 35 /// AllocationState is a part of the checker specific state together with the 36 /// MemRegion corresponding to the allocated data. 37 struct AllocationState { 38 const Expr *Address; 39 /// The index of the allocator function. 40 unsigned int AllocatorIdx; 41 SymbolRef RetValue; 42 43 AllocationState(const Expr *E, unsigned int Idx, SymbolRef R) : 44 Address(E), 45 AllocatorIdx(Idx), 46 RetValue(R) {} 47 48 bool operator==(const AllocationState &X) const { 49 return Address == X.Address; 50 } 51 void Profile(llvm::FoldingSetNodeID &ID) const { 52 ID.AddPointer(Address); 53 ID.AddInteger(AllocatorIdx); 54 } 55 }; 56 57 void checkPreStmt(const CallExpr *S, CheckerContext &C) const; 58 void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; 59 void checkPostStmt(const CallExpr *S, CheckerContext &C) const; 60 61 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; 62 void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; 63 64private: 65 /// Stores the information about the allocator and deallocator functions - 66 /// these are the functions the checker is tracking. 67 struct ADFunctionInfo { 68 const char* Name; 69 unsigned int Param; 70 unsigned int DeallocatorIdx; 71 /// The flag specifies if the call is valid or is a result of a common user 72 /// error (Ex: free instead of SecKeychainItemFreeContent), which we also 73 /// track for better diagnostics. 74 bool isValid; 75 }; 76 static const unsigned InvalidIdx = 100000; 77 static const unsigned FunctionsToTrackSize = 7; 78 static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize]; 79 /// The value, which represents no error return value for allocator functions. 80 static const unsigned NoErr = 0; 81 82 /// Given the function name, returns the index of the allocator/deallocator 83 /// function. 84 unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator) const; 85 86 inline void initBugType() const { 87 if (!BT) 88 BT.reset(new BugType("Improper use of SecKeychain API", "Mac OS API")); 89 } 90 91 void generateDeallocatorMismatchReport(const AllocationState &AS, 92 const Expr *ArgExpr, 93 CheckerContext &C, 94 SymbolRef ArgSM) const; 95 96 BugReport *generateAllocatedDataNotReleasedReport(const AllocationState &AS, 97 ExplodedNode *N) const; 98 99 /// Check if RetSym evaluates to an error value in the current state. 100 bool definitelyReturnedError(SymbolRef RetSym, 101 const ProgramState *State, 102 SValBuilder &Builder, 103 bool noError = false) const; 104 105 /// Check if RetSym evaluates to a NoErr value in the current state. 106 bool definitelyDidnotReturnError(SymbolRef RetSym, 107 const ProgramState *State, 108 SValBuilder &Builder) const { 109 return definitelyReturnedError(RetSym, State, Builder, true); 110 } 111 112}; 113} 114 115/// ProgramState traits to store the currently allocated (and not yet freed) 116/// symbols. This is a map from the allocated content symbol to the 117/// corresponding AllocationState. 118typedef llvm::ImmutableMap<SymbolRef, 119 MacOSKeychainAPIChecker::AllocationState> AllocatedSetTy; 120 121namespace { struct AllocatedData {}; } 122namespace clang { namespace ento { 123template<> struct ProgramStateTrait<AllocatedData> 124 : public ProgramStatePartialTrait<AllocatedSetTy > { 125 static void *GDMIndex() { static int index = 0; return &index; } 126}; 127}} 128 129static bool isEnclosingFunctionParam(const Expr *E) { 130 E = E->IgnoreParenCasts(); 131 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { 132 const ValueDecl *VD = DRE->getDecl(); 133 if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD)) 134 return true; 135 } 136 return false; 137} 138 139const MacOSKeychainAPIChecker::ADFunctionInfo 140 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = { 141 {"SecKeychainItemCopyContent", 4, 3, true}, // 0 142 {"SecKeychainFindGenericPassword", 6, 3, true}, // 1 143 {"SecKeychainFindInternetPassword", 13, 3, true}, // 2 144 {"SecKeychainItemFreeContent", 1, InvalidIdx, true}, // 3 145 {"SecKeychainItemCopyAttributesAndData", 5, 5, true}, // 4 146 {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, true}, // 5 147 {"free", 0, InvalidIdx, false}, // 6 148}; 149 150unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, 151 bool IsAllocator) const { 152 for (unsigned I = 0; I < FunctionsToTrackSize; ++I) { 153 ADFunctionInfo FI = FunctionsToTrack[I]; 154 if (FI.Name != Name) 155 continue; 156 // Make sure the function is of the right type (allocator vs deallocator). 157 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx)) 158 return InvalidIdx; 159 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx)) 160 return InvalidIdx; 161 162 return I; 163 } 164 // The function is not tracked. 165 return InvalidIdx; 166} 167 168static SymbolRef getSymbolForRegion(CheckerContext &C, 169 const MemRegion *R) { 170 if (!isa<SymbolicRegion>(R)) 171 return 0; 172 return cast<SymbolicRegion>(R)->getSymbol(); 173} 174 175static bool isBadDeallocationArgument(const MemRegion *Arg) { 176 if (isa<AllocaRegion>(Arg) || 177 isa<BlockDataRegion>(Arg) || 178 isa<TypedRegion>(Arg)) { 179 return true; 180 } 181 return false; 182} 183/// Given the address expression, retrieve the value it's pointing to. Assume 184/// that value is itself an address, and return the corresponding symbol. 185static SymbolRef getAsPointeeSymbol(const Expr *Expr, 186 CheckerContext &C) { 187 const ProgramState *State = C.getState(); 188 SVal ArgV = State->getSVal(Expr); 189 190 if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) { 191 StoreManager& SM = C.getStoreManager(); 192 const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion(); 193 if (V) 194 return getSymbolForRegion(C, V); 195 } 196 return 0; 197} 198 199// When checking for error code, we need to consider the following cases: 200// 1) noErr / [0] 201// 2) someErr / [1, inf] 202// 3) unknown 203// If noError, returns true iff (1). 204// If !noError, returns true iff (2). 205bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym, 206 const ProgramState *State, 207 SValBuilder &Builder, 208 bool noError) const { 209 DefinedOrUnknownSVal NoErrVal = Builder.makeIntVal(NoErr, 210 Builder.getSymbolManager().getType(RetSym)); 211 DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal, 212 nonloc::SymbolVal(RetSym)); 213 const ProgramState *ErrState = State->assume(NoErr, noError); 214 if (ErrState == State) { 215 return true; 216 } 217 218 return false; 219} 220 221// Report deallocator mismatch. Remove the region from tracking - reporting a 222// missing free error after this one is redundant. 223void MacOSKeychainAPIChecker:: 224 generateDeallocatorMismatchReport(const AllocationState &AS, 225 const Expr *ArgExpr, 226 CheckerContext &C, 227 SymbolRef ArgSM) const { 228 const ProgramState *State = C.getState(); 229 State = State->remove<AllocatedData>(ArgSM); 230 ExplodedNode *N = C.generateNode(State); 231 232 if (!N) 233 return; 234 initBugType(); 235 llvm::SmallString<80> sbuf; 236 llvm::raw_svector_ostream os(sbuf); 237 unsigned int PDeallocIdx = FunctionsToTrack[AS.AllocatorIdx].DeallocatorIdx; 238 239 os << "Deallocator doesn't match the allocator: '" 240 << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; 241 BugReport *Report = new BugReport(*BT, os.str(), N); 242 Report->addRange(ArgExpr->getSourceRange()); 243 C.EmitReport(Report); 244} 245 246void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 247 CheckerContext &C) const { 248 const ProgramState *State = C.getState(); 249 const Expr *Callee = CE->getCallee(); 250 SVal L = State->getSVal(Callee); 251 unsigned idx = InvalidIdx; 252 253 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 254 if (!funDecl) 255 return; 256 IdentifierInfo *funI = funDecl->getIdentifier(); 257 if (!funI) 258 return; 259 StringRef funName = funI->getName(); 260 261 // If it is a call to an allocator function, it could be a double allocation. 262 idx = getTrackedFunctionIndex(funName, true); 263 if (idx != InvalidIdx) { 264 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 265 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) 266 if (const AllocationState *AS = State->get<AllocatedData>(V)) { 267 if (!definitelyReturnedError(AS->RetValue, State, C.getSValBuilder())) { 268 // Remove the value from the state. The new symbol will be added for 269 // tracking when the second allocator is processed in checkPostStmt(). 270 State = State->remove<AllocatedData>(V); 271 ExplodedNode *N = C.generateNode(State); 272 if (!N) 273 return; 274 initBugType(); 275 llvm::SmallString<128> sbuf; 276 llvm::raw_svector_ostream os(sbuf); 277 unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 278 os << "Allocated data should be released before another call to " 279 << "the allocator: missing a call to '" 280 << FunctionsToTrack[DIdx].Name 281 << "'."; 282 BugReport *Report = new BugReport(*BT, os.str(), N); 283 Report->addRange(ArgExpr->getSourceRange()); 284 C.EmitReport(Report); 285 } 286 } 287 return; 288 } 289 290 // Is it a call to one of deallocator functions? 291 idx = getTrackedFunctionIndex(funName, false); 292 if (idx == InvalidIdx) 293 return; 294 295 // We also track invalid deallocators. Ex: free() for enhanced error messages. 296 bool isValidDeallocator = FunctionsToTrack[idx].isValid; 297 298 // Check the argument to the deallocator. 299 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 300 SVal ArgSVal = State->getSVal(ArgExpr); 301 302 // Undef is reported by another checker. 303 if (ArgSVal.isUndef()) 304 return; 305 306 const MemRegion *Arg = ArgSVal.getAsRegion(); 307 if (!Arg) 308 return; 309 310 SymbolRef ArgSM = getSymbolForRegion(C, Arg); 311 bool RegionArgIsBad = ArgSM ? false : isBadDeallocationArgument(Arg); 312 // If the argument is coming from the heap, globals, or unknown, do not 313 // report it. 314 if (!ArgSM && !RegionArgIsBad) 315 return; 316 317 // If trying to free data which has not been allocated yet, report as a bug. 318 // TODO: We might want a more precise diagnostic for double free 319 // (that would involve tracking all the freed symbols in the checker state). 320 const AllocationState *AS = State->get<AllocatedData>(ArgSM); 321 if ((!AS || RegionArgIsBad) && isValidDeallocator) { 322 // It is possible that this is a false positive - the argument might 323 // have entered as an enclosing function parameter. 324 if (isEnclosingFunctionParam(ArgExpr)) 325 return; 326 327 ExplodedNode *N = C.generateNode(State); 328 if (!N) 329 return; 330 initBugType(); 331 BugReport *Report = new BugReport(*BT, 332 "Trying to free data which has not been allocated.", N); 333 Report->addRange(ArgExpr->getSourceRange()); 334 C.EmitReport(Report); 335 return; 336 } 337 338 // The call is deallocating a value we previously allocated, so remove it 339 // from the next state. 340 State = State->remove<AllocatedData>(ArgSM); 341 342 // Check if the proper deallocator is used. 343 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 344 if (PDeallocIdx != idx || !isValidDeallocator) { 345 generateDeallocatorMismatchReport(*AS, ArgExpr, C, ArgSM); 346 return; 347 } 348 349 // If the return status is undefined or is error, report a bad call to free. 350 if (!definitelyDidnotReturnError(AS->RetValue, State, C.getSValBuilder())) { 351 ExplodedNode *N = C.generateNode(State); 352 if (!N) 353 return; 354 initBugType(); 355 BugReport *Report = new BugReport(*BT, 356 "Call to free data when error was returned during allocation.", N); 357 Report->addRange(ArgExpr->getSourceRange()); 358 C.EmitReport(Report); 359 return; 360 } 361 362 C.addTransition(State); 363} 364 365void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 366 CheckerContext &C) const { 367 const ProgramState *State = C.getState(); 368 const Expr *Callee = CE->getCallee(); 369 SVal L = State->getSVal(Callee); 370 371 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 372 if (!funDecl) 373 return; 374 IdentifierInfo *funI = funDecl->getIdentifier(); 375 if (!funI) 376 return; 377 StringRef funName = funI->getName(); 378 379 // If a value has been allocated, add it to the set for tracking. 380 unsigned idx = getTrackedFunctionIndex(funName, true); 381 if (idx == InvalidIdx) 382 return; 383 384 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 385 // If the argument entered as an enclosing function parameter, skip it to 386 // avoid false positives. 387 if (isEnclosingFunctionParam(ArgExpr)) 388 return; 389 390 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { 391 // If the argument points to something that's not a symbolic region, it 392 // can be: 393 // - unknown (cannot reason about it) 394 // - undefined (already reported by other checker) 395 // - constant (null - should not be tracked, 396 // other constant will generate a compiler warning) 397 // - goto (should be reported by other checker) 398 399 // The call return value symbol should stay alive for as long as the 400 // allocated value symbol, since our diagnostics depend on the value 401 // returned by the call. Ex: Data should only be freed if noErr was 402 // returned during allocation.) 403 SymbolRef RetStatusSymbol = State->getSVal(CE).getAsSymbol(); 404 C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); 405 406 // Track the allocated value in the checker state. 407 State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, 408 RetStatusSymbol)); 409 assert(State); 410 C.addTransition(State); 411 } 412} 413 414void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 415 CheckerContext &C) const { 416 const Expr *retExpr = S->getRetValue(); 417 if (!retExpr) 418 return; 419 420 // Check if the value is escaping through the return. 421 const ProgramState *state = C.getState(); 422 const MemRegion *V = state->getSVal(retExpr).getAsRegion(); 423 if (!V) 424 return; 425 state = state->remove<AllocatedData>(getSymbolForRegion(C, V)); 426 427 // Proceed from the new state. 428 C.addTransition(state); 429} 430 431// TODO: The report has to mention the expression which contains the 432// allocated content as well as the point at which it has been allocated. 433BugReport *MacOSKeychainAPIChecker:: 434 generateAllocatedDataNotReleasedReport(const AllocationState &AS, 435 ExplodedNode *N) const { 436 const ADFunctionInfo &FI = FunctionsToTrack[AS.AllocatorIdx]; 437 initBugType(); 438 llvm::SmallString<70> sbuf; 439 llvm::raw_svector_ostream os(sbuf); 440 os << "Allocated data is not released: missing a call to '" 441 << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 442 BugReport *Report = new BugReport(*BT, os.str(), N); 443 Report->addRange(AS.Address->getSourceRange()); 444 return Report; 445} 446 447void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, 448 CheckerContext &C) const { 449 const ProgramState *State = C.getState(); 450 AllocatedSetTy ASet = State->get<AllocatedData>(); 451 if (ASet.isEmpty()) 452 return; 453 454 bool Changed = false; 455 llvm::SmallVector<const AllocationState*, 1> Errors; 456 for (AllocatedSetTy::iterator I = ASet.begin(), E = ASet.end(); I != E; ++I) { 457 if (SR.isLive(I->first)) 458 continue; 459 460 Changed = true; 461 State = State->remove<AllocatedData>(I->first); 462 // If the allocated symbol is null or if the allocation call might have 463 // returned an error, do not report. 464 if (State->getSymVal(I->first) || 465 definitelyReturnedError(I->second.RetValue, State, C.getSValBuilder())) 466 continue; 467 Errors.push_back(&I->second); 468 } 469 if (!Changed) 470 return; 471 472 // Generate the new, cleaned up state. 473 ExplodedNode *N = C.generateNode(State); 474 if (!N) 475 return; 476 477 // Generate the error reports. 478 for (llvm::SmallVector<const AllocationState*, 3>::iterator 479 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 480 C.EmitReport(generateAllocatedDataNotReleasedReport(**I, N)); 481 } 482} 483 484// TODO: Remove this after we ensure that checkDeadSymbols are always called. 485void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, 486 ExprEngine &Eng) const { 487 const ProgramState *state = B.getState(); 488 AllocatedSetTy AS = state->get<AllocatedData>(); 489 if (AS.isEmpty()) 490 return; 491 492 // Anything which has been allocated but not freed (nor escaped) will be 493 // found here, so report it. 494 bool Changed = false; 495 llvm::SmallVector<const AllocationState*, 1> Errors; 496 for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 497 Changed = true; 498 state = state->remove<AllocatedData>(I->first); 499 // If the allocated symbol is null or if error code was returned at 500 // allocation, do not report. 501 if (state->getSymVal(I.getKey()) || 502 definitelyReturnedError(I->second.RetValue, state, 503 Eng.getSValBuilder())) { 504 continue; 505 } 506 Errors.push_back(&I->second); 507 } 508 509 // If no change, do not generate a new state. 510 if (!Changed) 511 return; 512 513 ExplodedNode *N = B.generateNode(state); 514 if (!N) 515 return; 516 517 // Generate the error reports. 518 for (llvm::SmallVector<const AllocationState*, 3>::iterator 519 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 520 Eng.getBugReporter().EmitReport( 521 generateAllocatedDataNotReleasedReport(**I, N)); 522 } 523 524} 525 526void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 527 mgr.registerChecker<MacOSKeychainAPIChecker>(); 528} 529