MacOSKeychainAPIChecker.cpp revision 79c9c75737cb22fd74d186999eccc10672eef8c0
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 the argument entered as an enclosing function parameter, skip it to 353 // avoid false positives. 354 if (isEnclosingFunctionParam(ArgExpr)) 355 return; 356 357 if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { 358 // If the argument points to something that's not a symbolic region, it 359 // can be: 360 // - unknown (cannot reason about it) 361 // - undefined (already reported by other checker) 362 // - constant (null - should not be tracked, 363 // other constant will generate a compiler warning) 364 // - goto (should be reported by other checker) 365 366 // The call return value symbol should stay alive for as long as the 367 // allocated value symbol, since our diagnostics depend on the value 368 // returned by the call. Ex: Data should only be freed if noErr was 369 // returned during allocation.) 370 SymbolRef RetStatusSymbol = State->getSVal(CE).getAsSymbol(); 371 C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); 372 373 // Track the allocated value in the checker state. 374 State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, 375 RetStatusSymbol)); 376 assert(State); 377 C.addTransition(State); 378 } 379} 380 381void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 382 CheckerContext &C) const { 383 const Expr *retExpr = S->getRetValue(); 384 if (!retExpr) 385 return; 386 387 // Check if the value is escaping through the return. 388 const GRState *state = C.getState(); 389 const MemRegion *V = state->getSVal(retExpr).getAsRegion(); 390 if (!V) 391 return; 392 state = state->remove<AllocatedData>(getSymbolForRegion(C, V)); 393 394 // Proceed from the new state. 395 C.addTransition(state); 396} 397 398// TODO: The report has to mention the expression which contains the 399// allocated content as well as the point at which it has been allocated. 400RangedBugReport *MacOSKeychainAPIChecker:: 401 generateAllocatedDataNotReleasedReport(const AllocationState &AS, 402 ExplodedNode *N) const { 403 const ADFunctionInfo &FI = FunctionsToTrack[AS.AllocatorIdx]; 404 initBugType(); 405 std::string sbuf; 406 llvm::raw_string_ostream os(sbuf); 407 os << "Allocated data is not released: missing a call to '" 408 << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 409 RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N); 410 Report->addRange(AS.Address->getSourceRange()); 411 return Report; 412} 413 414void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, 415 CheckerContext &C) const { 416 const GRState *State = C.getState(); 417 AllocatedSetTy ASet = State->get<AllocatedData>(); 418 if (ASet.isEmpty()) 419 return; 420 421 bool Changed = false; 422 llvm::SmallVector<const AllocationState*, 1> Errors; 423 for (AllocatedSetTy::iterator I = ASet.begin(), E = ASet.end(); I != E; ++I) { 424 if (SR.isLive(I->first)) 425 continue; 426 427 Changed = true; 428 State = State->remove<AllocatedData>(I->first); 429 // If the allocated symbol is null or if the allocation call might have 430 // returned an error, do not report. 431 if (State->getSymVal(I->first) || 432 definitelyReturnedError(I->second.RetValue, State, C.getSValBuilder())) 433 continue; 434 Errors.push_back(&I->second); 435 } 436 if (!Changed) 437 return; 438 439 // Generate the new, cleaned up state. 440 ExplodedNode *N = C.generateNode(State); 441 if (!N) 442 return; 443 444 // Generate the error reports. 445 for (llvm::SmallVector<const AllocationState*, 3>::iterator 446 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 447 C.EmitReport(generateAllocatedDataNotReleasedReport(**I, N)); 448 } 449} 450 451// TODO: Remove this after we ensure that checkDeadSymbols are always called. 452void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, 453 ExprEngine &Eng) const { 454 const GRState *state = B.getState(); 455 AllocatedSetTy AS = state->get<AllocatedData>(); 456 if (AS.isEmpty()) 457 return; 458 459 // Anything which has been allocated but not freed (nor escaped) will be 460 // found here, so report it. 461 bool Changed = false; 462 llvm::SmallVector<const AllocationState*, 1> Errors; 463 for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 464 Changed = true; 465 state = state->remove<AllocatedData>(I->first); 466 // If the allocated symbol is null or if error code was returned at 467 // allocation, do not report. 468 if (state->getSymVal(I.getKey()) || 469 definitelyReturnedError(I->second.RetValue, state, 470 Eng.getSValBuilder())) { 471 continue; 472 } 473 Errors.push_back(&I->second); 474 } 475 476 // If no change, do not generate a new state. 477 if (!Changed) 478 return; 479 480 ExplodedNode *N = B.generateNode(state); 481 if (!N) 482 return; 483 484 // Generate the error reports. 485 for (llvm::SmallVector<const AllocationState*, 3>::iterator 486 I = Errors.begin(), E = Errors.end(); I != E; ++I) { 487 Eng.getBugReporter().EmitReport( 488 generateAllocatedDataNotReleasedReport(**I, N)); 489 } 490 491} 492 493void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 494 mgr.registerChecker<MacOSKeychainAPIChecker>(); 495} 496