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