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