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