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