MacOSKeychainAPIChecker.cpp revision 31e1028afca8745ff6cfd64ecacdc05e513039ec
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 // Implicit casts (ex: void* -> char*) can turn Symbolic region into element 172 // region, if that is the case, get the underlining region. 173 if (const ElementRegion *ER = dyn_cast<ElementRegion>(R)) 174 R = ER->getAsArrayOffset().getRegion(); 175 else 176 return 0; 177 } 178 return cast<SymbolicRegion>(R)->getSymbol(); 179} 180 181static bool isBadDeallocationArgument(const MemRegion *Arg) { 182 if (isa<AllocaRegion>(Arg) || 183 isa<BlockDataRegion>(Arg) || 184 isa<TypedRegion>(Arg)) { 185 return true; 186 } 187 return false; 188} 189/// Given the address expression, retrieve the value it's pointing to. Assume 190/// that value is itself an address, and return the corresponding symbol. 191static SymbolRef getAsPointeeSymbol(const Expr *Expr, 192 CheckerContext &C) { 193 const ProgramState *State = C.getState(); 194 SVal ArgV = State->getSVal(Expr); 195 196 if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) { 197 StoreManager& SM = C.getStoreManager(); 198 const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion(); 199 if (V) 200 return getSymbolForRegion(C, V); 201 } 202 return 0; 203} 204 205// When checking for error code, we need to consider the following cases: 206// 1) noErr / [0] 207// 2) someErr / [1, inf] 208// 3) unknown 209// If noError, returns true iff (1). 210// If !noError, returns true iff (2). 211bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym, 212 const ProgramState *State, 213 SValBuilder &Builder, 214 bool noError) const { 215 DefinedOrUnknownSVal NoErrVal = Builder.makeIntVal(NoErr, 216 Builder.getSymbolManager().getType(RetSym)); 217 DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal, 218 nonloc::SymbolVal(RetSym)); 219 const ProgramState *ErrState = State->assume(NoErr, noError); 220 if (ErrState == State) { 221 return true; 222 } 223 224 return false; 225} 226 227// Report deallocator mismatch. Remove the region from tracking - reporting a 228// missing free error after this one is redundant. 229void MacOSKeychainAPIChecker:: 230 generateDeallocatorMismatchReport(const AllocationState &AS, 231 const Expr *ArgExpr, 232 CheckerContext &C, 233 SymbolRef ArgSM) const { 234 const ProgramState *State = C.getState(); 235 State = State->remove<AllocatedData>(ArgSM); 236 ExplodedNode *N = C.generateNode(State); 237 238 if (!N) 239 return; 240 initBugType(); 241 llvm::SmallString<80> sbuf; 242 llvm::raw_svector_ostream os(sbuf); 243 unsigned int PDeallocIdx = FunctionsToTrack[AS.AllocatorIdx].DeallocatorIdx; 244 245 os << "Deallocator doesn't match the allocator: '" 246 << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; 247 BugReport *Report = new BugReport(*BT, os.str(), N); 248 Report->addRange(ArgExpr->getSourceRange()); 249 C.EmitReport(Report); 250} 251 252void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 253 CheckerContext &C) const { 254 const ProgramState *State = C.getState(); 255 const Expr *Callee = CE->getCallee(); 256 SVal L = State->getSVal(Callee); 257 unsigned idx = InvalidIdx; 258 259 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 260 if (!funDecl) 261 return; 262 IdentifierInfo *funI = funDecl->getIdentifier(); 263 if (!funI) 264 return; 265 StringRef funName = funI->getName(); 266 267 // If it is a call to an allocator function, it could be a double allocation. 268 idx = getTrackedFunctionIndex(funName, true); 269 if (idx != InvalidIdx) { 270 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 271 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) 272 if (const AllocationState *AS = State->get<AllocatedData>(V)) { 273 if (!definitelyReturnedError(AS->RetValue, State, C.getSValBuilder())) { 274 // Remove the value from the state. The new symbol will be added for 275 // tracking when the second allocator is processed in checkPostStmt(). 276 State = State->remove<AllocatedData>(V); 277 ExplodedNode *N = C.generateNode(State); 278 if (!N) 279 return; 280 initBugType(); 281 llvm::SmallString<128> sbuf; 282 llvm::raw_svector_ostream os(sbuf); 283 unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 284 os << "Allocated data should be released before another call to " 285 << "the allocator: missing a call to '" 286 << FunctionsToTrack[DIdx].Name 287 << "'."; 288 BugReport *Report = new BugReport(*BT, os.str(), N); 289 Report->addRange(ArgExpr->getSourceRange()); 290 C.EmitReport(Report); 291 } 292 } 293 return; 294 } 295 296 // Is it a call to one of deallocator functions? 297 idx = getTrackedFunctionIndex(funName, false); 298 if (idx == InvalidIdx) 299 return; 300 301 // We also track invalid deallocators. Ex: free() for enhanced error messages. 302 bool isValidDeallocator = FunctionsToTrack[idx].isValid; 303 304 // Check the argument to the deallocator. 305 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 306 SVal ArgSVal = State->getSVal(ArgExpr); 307 308 // Undef is reported by another checker. 309 if (ArgSVal.isUndef()) 310 return; 311 312 const MemRegion *Arg = ArgSVal.getAsRegion(); 313 if (!Arg) 314 return; 315 316 SymbolRef ArgSM = getSymbolForRegion(C, Arg); 317 bool RegionArgIsBad = ArgSM ? false : isBadDeallocationArgument(Arg); 318 // If the argument is coming from the heap, globals, or unknown, do not 319 // report it. 320 if (!ArgSM && !RegionArgIsBad) 321 return; 322 323 // If trying to free data which has not been allocated yet, report as a bug. 324 // TODO: We might want a more precise diagnostic for double free 325 // (that would involve tracking all the freed symbols in the checker state). 326 const AllocationState *AS = State->get<AllocatedData>(ArgSM); 327 if ((!AS || RegionArgIsBad) && isValidDeallocator) { 328 // It is possible that this is a false positive - the argument might 329 // have entered as an enclosing function parameter. 330 if (isEnclosingFunctionParam(ArgExpr)) 331 return; 332 333 ExplodedNode *N = C.generateNode(State); 334 if (!N) 335 return; 336 initBugType(); 337 BugReport *Report = new BugReport(*BT, 338 "Trying to free data which has not been allocated.", N); 339 Report->addRange(ArgExpr->getSourceRange()); 340 C.EmitReport(Report); 341 return; 342 } 343 344 // The call is deallocating a value we previously allocated, so remove it 345 // from the next state. 346 State = State->remove<AllocatedData>(ArgSM); 347 348 // Check if the proper deallocator is used. 349 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 350 if (PDeallocIdx != idx || !isValidDeallocator) { 351 generateDeallocatorMismatchReport(*AS, ArgExpr, C, ArgSM); 352 return; 353 } 354 355 // If the return status is undefined or is error, report a bad call to free. 356 if (!definitelyDidnotReturnError(AS->RetValue, State, C.getSValBuilder())) { 357 ExplodedNode *N = C.generateNode(State); 358 if (!N) 359 return; 360 initBugType(); 361 BugReport *Report = new BugReport(*BT, 362 "Call to free data when error was returned during allocation.", N); 363 Report->addRange(ArgExpr->getSourceRange()); 364 C.EmitReport(Report); 365 return; 366 } 367 368 C.addTransition(State); 369} 370 371void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 372 CheckerContext &C) const { 373 const ProgramState *State = C.getState(); 374 const Expr *Callee = CE->getCallee(); 375 SVal L = State->getSVal(Callee); 376 377 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 378 if (!funDecl) 379 return; 380 IdentifierInfo *funI = funDecl->getIdentifier(); 381 if (!funI) 382 return; 383 StringRef funName = funI->getName(); 384 385 // If a value has been allocated, add it to the set for tracking. 386 unsigned idx = getTrackedFunctionIndex(funName, true); 387 if (idx == InvalidIdx) 388 return; 389 390 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 391 // If the argument entered as an enclosing function parameter, skip it to 392 // avoid false positives. 393 if (isEnclosingFunctionParam(ArgExpr)) 394 return; 395 396 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { 397 // If the argument points to something that's not a symbolic region, it 398 // can be: 399 // - unknown (cannot reason about it) 400 // - undefined (already reported by other checker) 401 // - constant (null - should not be tracked, 402 // other constant will generate a compiler warning) 403 // - goto (should be reported by other checker) 404 405 // The call return value symbol should stay alive for as long as the 406 // allocated value symbol, since our diagnostics depend on the value 407 // returned by the call. Ex: Data should only be freed if noErr was 408 // returned during allocation.) 409 SymbolRef RetStatusSymbol = State->getSVal(CE).getAsSymbol(); 410 C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); 411 412 // Track the allocated value in the checker state. 413 State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, 414 RetStatusSymbol)); 415 assert(State); 416 C.addTransition(State); 417 } 418} 419 420void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 421 CheckerContext &C) const { 422 const Expr *retExpr = S->getRetValue(); 423 if (!retExpr) 424 return; 425 426 // Check if the value is escaping through the return. 427 const ProgramState *state = C.getState(); 428 const MemRegion *V = state->getSVal(retExpr).getAsRegion(); 429 if (!V) 430 return; 431 state = state->remove<AllocatedData>(getSymbolForRegion(C, V)); 432 433 // Proceed from the new state. 434 C.addTransition(state); 435} 436 437// TODO: The report has to mention the expression which contains the 438// allocated content as well as the point at which it has been allocated. 439BugReport *MacOSKeychainAPIChecker:: 440 generateAllocatedDataNotReleasedReport(const AllocationState &AS, 441 ExplodedNode *N) const { 442 const ADFunctionInfo &FI = FunctionsToTrack[AS.AllocatorIdx]; 443 initBugType(); 444 llvm::SmallString<70> sbuf; 445 llvm::raw_svector_ostream os(sbuf); 446 os << "Allocated data is not released: missing a call to '" 447 << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 448 BugReport *Report = new BugReport(*BT, os.str(), N); 449 Report->addRange(AS.Address->getSourceRange()); 450 return Report; 451} 452 453void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, 454 CheckerContext &C) const { 455 const ProgramState *State = C.getState(); 456 AllocatedSetTy ASet = State->get<AllocatedData>(); 457 if (ASet.isEmpty()) 458 return; 459 460 bool Changed = false; 461 llvm::SmallVector<const AllocationState*, 1> Errors; 462 for (AllocatedSetTy::iterator I = ASet.begin(), E = ASet.end(); I != E; ++I) { 463 if (SR.isLive(I->first)) 464 continue; 465 466 Changed = true; 467 State = State->remove<AllocatedData>(I->first); 468 // If the allocated symbol is null or if the allocation call might have 469 // returned an error, do not report. 470 if (State->getSymVal(I->first) || 471 definitelyReturnedError(I->second.RetValue, State, C.getSValBuilder())) 472 continue; 473 Errors.push_back(&I->second); 474 } 475 if (!Changed) 476 return; 477 478 // Generate the new, cleaned up state. 479 ExplodedNode *N = C.generateNode(State); 480 if (!N) 481 return; 482 483 // Generate the error reports. 484 for (llvm::SmallVector<const AllocationState*, 3>::iterator 485 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 486 C.EmitReport(generateAllocatedDataNotReleasedReport(**I, N)); 487 } 488} 489 490// TODO: Remove this after we ensure that checkDeadSymbols are always called. 491void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, 492 ExprEngine &Eng) const { 493 const ProgramState *state = B.getState(); 494 AllocatedSetTy AS = state->get<AllocatedData>(); 495 if (AS.isEmpty()) 496 return; 497 498 // Anything which has been allocated but not freed (nor escaped) will be 499 // found here, so report it. 500 bool Changed = false; 501 llvm::SmallVector<const AllocationState*, 1> Errors; 502 for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 503 Changed = true; 504 state = state->remove<AllocatedData>(I->first); 505 // If the allocated symbol is null or if error code was returned at 506 // allocation, do not report. 507 if (state->getSymVal(I.getKey()) || 508 definitelyReturnedError(I->second.RetValue, state, 509 Eng.getSValBuilder())) { 510 continue; 511 } 512 Errors.push_back(&I->second); 513 } 514 515 // If no change, do not generate a new state. 516 if (!Changed) 517 return; 518 519 ExplodedNode *N = B.generateNode(state); 520 if (!N) 521 return; 522 523 // Generate the error reports. 524 for (llvm::SmallVector<const AllocationState*, 3>::iterator 525 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 526 Eng.getBugReporter().EmitReport( 527 generateAllocatedDataNotReleasedReport(**I, N)); 528 } 529 530} 531 532void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 533 mgr.registerChecker<MacOSKeychainAPIChecker>(); 534} 535