MacOSKeychainAPIChecker.cpp revision ca0b57e07cfa029d4a6a061260727625bd833fd4
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 mutable llvm::OwningPtr<BugType> BT; 32 33public: 34 void checkPreStmt(const CallExpr *S, CheckerContext &C) const; 35 void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; 36 void checkPostStmt(const CallExpr *S, CheckerContext &C) const; 37 38 void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; 39 40private: 41 /// Stores the information about the allocator and deallocator functions - 42 /// these are the functions the checker is tracking. 43 struct ADFunctionInfo { 44 const char* Name; 45 unsigned int Param; 46 unsigned int DeallocatorIdx; 47 }; 48 static const unsigned InvalidIdx = 100000; 49 static const unsigned FunctionsToTrackSize = 6; 50 static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize]; 51 52 /// Given the function name, returns the index of the allocator/deallocator 53 /// function. 54 unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator) const; 55 56 inline void initBugType() const { 57 if (!BT) 58 BT.reset(new BugType("Improper use of SecKeychain API", "Mac OS API")); 59 } 60}; 61} 62 63/// AllocationState is a part of the checker specific state together with the 64/// MemRegion corresponding to the allocated data. 65struct AllocationState { 66 const Expr *Address; 67 /// The index of the allocator function. 68 unsigned int AllocatorIdx; 69 70 AllocationState(const Expr *E, unsigned int Idx) : Address(E), 71 AllocatorIdx(Idx) {} 72 bool operator==(const AllocationState &X) const { 73 return Address == X.Address; 74 } 75 void Profile(llvm::FoldingSetNodeID &ID) const { 76 ID.AddPointer(Address); 77 ID.AddInteger(AllocatorIdx); 78 } 79}; 80 81/// GRState traits to store the currently allocated (and not yet freed) symbols. 82typedef llvm::ImmutableMap<const MemRegion*, AllocationState> AllocatedSetTy; 83 84namespace { struct AllocatedData {}; } 85namespace clang { namespace ento { 86template<> struct GRStateTrait<AllocatedData> 87 : public GRStatePartialTrait<AllocatedSetTy > { 88 static void *GDMIndex() { static int index = 0; return &index; } 89}; 90}} 91 92static bool isEnclosingFunctionParam(const Expr *E) { 93 E = E->IgnoreParenCasts(); 94 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { 95 const ValueDecl *VD = DRE->getDecl(); 96 if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD)) 97 return true; 98 } 99 return false; 100} 101 102const MacOSKeychainAPIChecker::ADFunctionInfo 103 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = { 104 {"SecKeychainItemCopyContent", 4, 3}, // 0 105 {"SecKeychainFindGenericPassword", 6, 3}, // 1 106 {"SecKeychainFindInternetPassword", 13, 3}, // 2 107 {"SecKeychainItemFreeContent", 1, InvalidIdx}, // 3 108 {"SecKeychainItemCopyAttributesAndData", 5, 5}, // 4 109 {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx}, // 5 110}; 111 112unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, 113 bool IsAllocator) const { 114 for (unsigned I = 0; I < FunctionsToTrackSize; ++I) { 115 ADFunctionInfo FI = FunctionsToTrack[I]; 116 if (FI.Name != Name) 117 continue; 118 // Make sure the function is of the right type (allocator vs deallocator). 119 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx)) 120 return InvalidIdx; 121 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx)) 122 return InvalidIdx; 123 124 return I; 125 } 126 // The function is not tracked. 127 return InvalidIdx; 128} 129 130/// Given the address expression, retrieve the value it's pointing to. Assume 131/// that value is itself an address, and return the corresponding MemRegion. 132static const MemRegion *getAsPointeeMemoryRegion(const Expr *Expr, 133 CheckerContext &C) { 134 const GRState *State = C.getState(); 135 SVal ArgV = State->getSVal(Expr); 136 if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) { 137 StoreManager& SM = C.getStoreManager(); 138 const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion(); 139 return V; 140 } 141 return 0; 142} 143 144void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 145 CheckerContext &C) const { 146 const GRState *State = C.getState(); 147 const Expr *Callee = CE->getCallee(); 148 SVal L = State->getSVal(Callee); 149 unsigned idx = InvalidIdx; 150 151 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 152 if (!funDecl) 153 return; 154 IdentifierInfo *funI = funDecl->getIdentifier(); 155 if (!funI) 156 return; 157 StringRef funName = funI->getName(); 158 159 // If it is a call to an allocator function, it could be a double allocation. 160 idx = getTrackedFunctionIndex(funName, true); 161 if (idx != InvalidIdx) { 162 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 163 if (const MemRegion *V = getAsPointeeMemoryRegion(ArgExpr, C)) 164 if (const AllocationState *AS = State->get<AllocatedData>(V)) { 165 ExplodedNode *N = C.generateSink(State); 166 if (!N) 167 return; 168 initBugType(); 169 std::string sbuf; 170 llvm::raw_string_ostream os(sbuf); 171 unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 172 os << "Allocated data should be released before another call to " 173 << "the allocator: missing a call to '" 174 << FunctionsToTrack[DIdx].Name 175 << "'."; 176 RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N); 177 Report->addRange(ArgExpr->getSourceRange()); 178 C.EmitReport(Report); 179 } 180 return; 181 } 182 183 // Is it a call to one of deallocator functions? 184 idx = getTrackedFunctionIndex(funName, false); 185 if (idx == InvalidIdx) 186 return; 187 188 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 189 const MemRegion *Arg = State->getSVal(ArgExpr).getAsRegion(); 190 if (!Arg) 191 return; 192 193 // If trying to free data which has not been allocated yet, report as bug. 194 const AllocationState *AS = State->get<AllocatedData>(Arg); 195 if (!AS) { 196 // It is possible that this is a false positive - the argument might 197 // have entered as an enclosing function parameter. 198 if (isEnclosingFunctionParam(ArgExpr)) 199 return; 200 201 ExplodedNode *N = C.generateNode(State); 202 if (!N) 203 return; 204 initBugType(); 205 RangedBugReport *Report = new RangedBugReport(*BT, 206 "Trying to free data which has not been allocated.", N); 207 Report->addRange(ArgExpr->getSourceRange()); 208 C.EmitReport(Report); 209 return; 210 } 211 212 // Check if the proper deallocator is used. 213 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; 214 if (PDeallocIdx != idx) { 215 ExplodedNode *N = C.generateSink(State); 216 if (!N) 217 return; 218 initBugType(); 219 220 std::string sbuf; 221 llvm::raw_string_ostream os(sbuf); 222 os << "Allocator doesn't match the deallocator: '" 223 << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; 224 RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N); 225 Report->addRange(ArgExpr->getSourceRange()); 226 C.EmitReport(Report); 227 return; 228 } 229 230 // If a value has been freed, remove it from the list and continue exploring 231 // from the new state. 232 State = State->remove<AllocatedData>(Arg); 233 C.addTransition(State); 234} 235 236void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 237 CheckerContext &C) const { 238 const GRState *State = C.getState(); 239 const Expr *Callee = CE->getCallee(); 240 SVal L = State->getSVal(Callee); 241 242 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 243 if (!funDecl) 244 return; 245 IdentifierInfo *funI = funDecl->getIdentifier(); 246 if (!funI) 247 return; 248 StringRef funName = funI->getName(); 249 250 // If a value has been allocated, add it to the set for tracking. 251 unsigned idx = getTrackedFunctionIndex(funName, true); 252 if (idx == InvalidIdx) 253 return; 254 255 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); 256 if (const MemRegion *V = getAsPointeeMemoryRegion(ArgExpr, C)) { 257 // If the argument points to something that's not a region, it can be: 258 // - unknown (cannot reason about it) 259 // - undefined (already reported by other checker) 260 // - constant (null - should not be tracked, 261 // other constant will generate a compiler warning) 262 // - goto (should be reported by other checker) 263 264 // We only need to track the value if the function returned noErr(0), so 265 // bind the return value of the function to 0 and proceed from the no error 266 // state. 267 SValBuilder &Builder = C.getSValBuilder(); 268 SVal ZeroVal = Builder.makeIntVal(0, CE->getCallReturnType()); 269 const GRState *NoErr = State->BindExpr(CE, ZeroVal); 270 // Add the symbolic value V, which represents the location of the allocated 271 // data, to the set. 272 NoErr = NoErr->set<AllocatedData>(V, AllocationState(ArgExpr, idx)); 273 assert(NoErr); 274 C.addTransition(NoErr); 275 276 // Generate a transition to explore the state space when there is an error. 277 // In this case, we do not track the allocated data. 278 SVal ReturnedError = Builder.evalBinOpNN(State, BO_NE, 279 cast<NonLoc>(ZeroVal), 280 cast<NonLoc>(State->getSVal(CE)), 281 CE->getCallReturnType()); 282 const GRState *Err = State->assume(cast<NonLoc>(ReturnedError), true); 283 assert(Err); 284 C.addTransition(Err); 285 } 286} 287 288void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 289 CheckerContext &C) const { 290 const Expr *retExpr = S->getRetValue(); 291 if (!retExpr) 292 return; 293 294 // Check if the value is escaping through the return. 295 const GRState *state = C.getState(); 296 const MemRegion *V = state->getSVal(retExpr).getAsRegion(); 297 if (!V) 298 return; 299 state = state->remove<AllocatedData>(V); 300 301 // Proceed from the new state. 302 C.addTransition(state); 303} 304 305void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, 306 ExprEngine &Eng) const { 307 const GRState *state = B.getState(); 308 AllocatedSetTy AS = state->get<AllocatedData>(); 309 ExplodedNode *N = B.generateNode(state); 310 if (!N) 311 return; 312 initBugType(); 313 314 // Anything which has been allocated but not freed (nor escaped) will be 315 // found here, so report it. 316 for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 317 const ADFunctionInfo &FI = FunctionsToTrack[I->second.AllocatorIdx]; 318 319 std::string sbuf; 320 llvm::raw_string_ostream os(sbuf); 321 os << "Allocated data is not released: missing a call to '" 322 << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; 323 RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N); 324 // TODO: The report has to mention the expression which contains the 325 // allocated content as well as the point at which it has been allocated. 326 // Currently, the next line is useless. 327 Report->addRange(I->second.Address->getSourceRange()); 328 Eng.getBugReporter().EmitReport(Report); 329 } 330} 331 332void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 333 mgr.registerChecker<MacOSKeychainAPIChecker>(); 334} 335