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 23 using namespace clang; 24 using namespace ento; 25 26 namespace { 27 class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, 28 check::PreStmt<ReturnStmt>, 29 check::PostStmt<CallExpr>, 30 check::EndPath > { 31 mutable llvm::OwningPtr<BugType> BT; 32 33 public: 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 40 private: 41 static const unsigned InvalidParamVal = 100000; 42 43 /// Given the function name, returns the index of the parameter which will 44 /// be allocated as a result of the call. 45 unsigned getAllocatingFunctionParam(StringRef Name) const { 46 if (Name == "SecKeychainItemCopyContent") 47 return 4; 48 if (Name == "SecKeychainFindGenericPassword") 49 return 6; 50 if (Name == "SecKeychainFindInternetPassword") 51 return 13; 52 return InvalidParamVal; 53 } 54 55 /// Given the function name, returns the index of the parameter which will 56 /// be freed by the function. 57 unsigned getDeallocatingFunctionParam(StringRef Name) const { 58 if (Name == "SecKeychainItemFreeContent") 59 return 1; 60 return InvalidParamVal; 61 } 62 63 inline void initBugType() const { 64 if (!BT) 65 BT.reset(new BugType("Improper use of SecKeychain API", "Mac OS API")); 66 } 67 }; 68 } 69 70 struct AllocationInfo { 71 const Expr *Address; 72 73 AllocationInfo(const Expr *E) : Address(E) {} 74 bool operator==(const AllocationInfo &X) const { 75 return Address == X.Address; 76 } 77 void Profile(llvm::FoldingSetNodeID &ID) const { 78 ID.AddPointer(Address); 79 } 80 }; 81 82 // GRState traits to store the currently allocated (and not yet freed) symbols. 83 typedef llvm::ImmutableMap<const MemRegion*, AllocationInfo> AllocatedSetTy; 84 85 namespace { struct AllocatedData {}; } 86 namespace clang { namespace ento { 87 template<> struct GRStateTrait<AllocatedData> 88 : public GRStatePartialTrait<AllocatedSetTy > { 89 static void *GDMIndex() { static int index = 0; return &index; } 90 }; 91 }} 92 93 static bool isEnclosingFunctionParam(const Expr *E) { 94 E = E->IgnoreParenCasts(); 95 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { 96 const ValueDecl *VD = DRE->getDecl(); 97 if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD)) 98 return true; 99 } 100 return false; 101 } 102 103 void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, 104 CheckerContext &C) const { 105 const GRState *State = C.getState(); 106 const Expr *Callee = CE->getCallee(); 107 SVal L = State->getSVal(Callee); 108 109 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 110 if (!funDecl) 111 return; 112 IdentifierInfo *funI = funDecl->getIdentifier(); 113 if (!funI) 114 return; 115 StringRef funName = funI->getName(); 116 117 // If a value has been freed, remove from the list. 118 unsigned idx = getDeallocatingFunctionParam(funName); 119 if (idx == InvalidParamVal) 120 return; 121 122 const Expr *ArgExpr = CE->getArg(idx); 123 const MemRegion *Arg = State->getSVal(ArgExpr).getAsRegion(); 124 if (!Arg) 125 return; 126 127 // If trying to free data which has not been allocated yet, report as bug. 128 if (State->get<AllocatedData>(Arg) == 0) { 129 // It is possible that this is a false positive - the argument might 130 // have entered as an enclosing function parameter. 131 if (isEnclosingFunctionParam(ArgExpr)) 132 return; 133 134 ExplodedNode *N = C.generateNode(State); 135 if (!N) 136 return; 137 initBugType(); 138 RangedBugReport *Report = new RangedBugReport(*BT, 139 "Trying to free data which has not been allocated.", N); 140 Report->addRange(ArgExpr->getSourceRange()); 141 C.EmitReport(Report); 142 } 143 144 // Continue exploring from the new state. 145 State = State->remove<AllocatedData>(Arg); 146 C.addTransition(State); 147 } 148 149 void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, 150 CheckerContext &C) const { 151 const GRState *State = C.getState(); 152 const Expr *Callee = CE->getCallee(); 153 SVal L = State->getSVal(Callee); 154 StoreManager& SM = C.getStoreManager(); 155 156 const FunctionDecl *funDecl = L.getAsFunctionDecl(); 157 if (!funDecl) 158 return; 159 IdentifierInfo *funI = funDecl->getIdentifier(); 160 if (!funI) 161 return; 162 StringRef funName = funI->getName(); 163 164 // If a value has been allocated, add it to the set for tracking. 165 unsigned idx = getAllocatingFunctionParam(funName); 166 if (idx == InvalidParamVal) 167 return; 168 169 SVal Arg = State->getSVal(CE->getArg(idx)); 170 if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&Arg)) { 171 // Add the symbolic value, which represents the location of the allocated 172 // data, to the set. 173 const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion(); 174 // If this is not a region, it can be: 175 // - unknown (cannot reason about it) 176 // - undefined (already reported by other checker) 177 // - constant (null - should not be tracked, other - report a warning?) 178 // - goto (should be reported by other checker) 179 if (!V) 180 return; 181 182 State = State->set<AllocatedData>(V, AllocationInfo(CE->getArg(idx))); 183 184 // We only need to track the value if the function returned noErr(0), so 185 // bind the return value of the function to 0. 186 SValBuilder &Builder = C.getSValBuilder(); 187 SVal ZeroVal = Builder.makeZeroVal(Builder.getContext().CharTy); 188 State = State->BindExpr(CE, ZeroVal); 189 assert(State); 190 191 // Proceed from the new state. 192 C.addTransition(State); 193 } 194 } 195 196 void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, 197 CheckerContext &C) const { 198 const Expr *retExpr = S->getRetValue(); 199 if (!retExpr) 200 return; 201 202 // Check if the value is escaping through the return. 203 const GRState *state = C.getState(); 204 const MemRegion *V = state->getSVal(retExpr).getAsRegion(); 205 if (!V) 206 return; 207 state = state->remove<AllocatedData>(V); 208 209 // Proceed from the new state. 210 C.addTransition(state); 211 } 212 213 void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, 214 ExprEngine &Eng) const { 215 const GRState *state = B.getState(); 216 AllocatedSetTy AS = state->get<AllocatedData>(); 217 ExplodedNode *N = B.generateNode(state); 218 if (!N) 219 return; 220 initBugType(); 221 222 // Anything which has been allocated but not freed (nor escaped) will be 223 // found here, so report it. 224 for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) { 225 RangedBugReport *Report = new RangedBugReport(*BT, 226 "Missing a call to SecKeychainItemFreeContent.", N); 227 // TODO: The report has to mention the expression which contains the 228 // allocated content as well as the point at which it has been allocated. 229 // Currently, the next line is useless. 230 Report->addRange(I->second.Address->getSourceRange()); 231 Eng.getBugReporter().EmitReport(Report); 232 } 233 } 234 235 void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { 236 mgr.registerChecker<MacOSKeychainAPIChecker>(); 237 } 238