xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp (revision 08be9b99e3e68a2603d5d68f56b7f0730570a86e)
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   /// 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 = 4;
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.
65 struct 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.
82 typedef llvm::ImmutableMap<const MemRegion*, AllocationState> AllocatedSetTy;
83 
84 namespace { struct AllocatedData {}; }
85 namespace clang { namespace ento {
86 template<> struct GRStateTrait<AllocatedData>
87     :  public GRStatePartialTrait<AllocatedSetTy > {
88   static void *GDMIndex() { static int index = 0; return &index; }
89 };
90 }}
91 
92 static 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 
102 const 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 };
109 
110 unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
111                                                        bool IsAllocator) const {
112   for (unsigned I = 0; I < FunctionsToTrackSize; ++I) {
113     ADFunctionInfo FI = FunctionsToTrack[I];
114     if (FI.Name != Name)
115       continue;
116     // Make sure the function is of the right type (allocator vs deallocator).
117     if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
118       return InvalidIdx;
119     if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
120       return InvalidIdx;
121 
122     return I;
123   }
124   // The function is not tracked.
125   return InvalidIdx;
126 }
127 
128 void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
129                                            CheckerContext &C) const {
130   const GRState *State = C.getState();
131   const Expr *Callee = CE->getCallee();
132   SVal L = State->getSVal(Callee);
133 
134   const FunctionDecl *funDecl = L.getAsFunctionDecl();
135   if (!funDecl)
136     return;
137   IdentifierInfo *funI = funDecl->getIdentifier();
138   if (!funI)
139     return;
140   StringRef funName = funI->getName();
141 
142   // If a value has been freed, remove from the list.
143   unsigned idx = getTrackedFunctionIndex(funName, false);
144   if (idx == InvalidIdx)
145     return;
146 
147   const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
148   const MemRegion *Arg = State->getSVal(ArgExpr).getAsRegion();
149   if (!Arg)
150     return;
151 
152   // If trying to free data which has not been allocated yet, report as bug.
153   const AllocationState *AS = State->get<AllocatedData>(Arg);
154   if (!AS) {
155     // It is possible that this is a false positive - the argument might
156     // have entered as an enclosing function parameter.
157     if (isEnclosingFunctionParam(ArgExpr))
158       return;
159 
160     ExplodedNode *N = C.generateNode(State);
161     if (!N)
162       return;
163     initBugType();
164     RangedBugReport *Report = new RangedBugReport(*BT,
165         "Trying to free data which has not been allocated.", N);
166     Report->addRange(ArgExpr->getSourceRange());
167     C.EmitReport(Report);
168     return;
169   }
170 
171   // Continue exploring from the new state.
172   assert(FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx == idx &&
173     "Allocator should match the deallocator");
174   State = State->remove<AllocatedData>(Arg);
175   C.addTransition(State);
176 }
177 
178 void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE,
179                                             CheckerContext &C) const {
180   const GRState *State = C.getState();
181   const Expr *Callee = CE->getCallee();
182   SVal L = State->getSVal(Callee);
183   StoreManager& SM = C.getStoreManager();
184 
185   const FunctionDecl *funDecl = L.getAsFunctionDecl();
186   if (!funDecl)
187     return;
188   IdentifierInfo *funI = funDecl->getIdentifier();
189   if (!funI)
190     return;
191   StringRef funName = funI->getName();
192 
193   // If a value has been allocated, add it to the set for tracking.
194   unsigned idx = getTrackedFunctionIndex(funName, true);
195   if (idx == InvalidIdx)
196     return;
197 
198   const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
199   SVal Arg = State->getSVal(ArgExpr);
200   if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&Arg)) {
201     // Add the symbolic value, which represents the location of the allocated
202     // data, to the set.
203     const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion();
204     // If this is not a region, it can be:
205     //  - unknown (cannot reason about it)
206     //  - undefined (already reported by other checker)
207     //  - constant (null - should not be tracked,
208     //              other constant will generate a compiler warning)
209     //  - goto (should be reported by other checker)
210     if (!V)
211       return;
212 
213     State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx));
214 
215     // We only need to track the value if the function returned noErr(0), so
216     // bind the return value of the function to 0.
217     SValBuilder &Builder = C.getSValBuilder();
218     SVal ZeroVal = Builder.makeZeroVal(Builder.getContext().CharTy);
219     State = State->BindExpr(CE, ZeroVal);
220     assert(State);
221 
222     // Proceed from the new state.
223     C.addTransition(State);
224   }
225 }
226 
227 void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S,
228                                            CheckerContext &C) const {
229   const Expr *retExpr = S->getRetValue();
230   if (!retExpr)
231     return;
232 
233   // Check  if the value is escaping through the return.
234   const GRState *state = C.getState();
235   const MemRegion *V = state->getSVal(retExpr).getAsRegion();
236   if (!V)
237     return;
238   state = state->remove<AllocatedData>(V);
239 
240   // Proceed from the new state.
241   C.addTransition(state);
242 }
243 
244 void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B,
245                                            ExprEngine &Eng) const {
246   const GRState *state = B.getState();
247   AllocatedSetTy AS = state->get<AllocatedData>();
248   ExplodedNode *N = B.generateNode(state);
249   if (!N)
250     return;
251   initBugType();
252 
253   // Anything which has been allocated but not freed (nor escaped) will be
254   // found here, so report it.
255   for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) {
256     RangedBugReport *Report = new RangedBugReport(*BT,
257       "Missing a call to SecKeychainItemFreeContent.", N);
258     // TODO: The report has to mention the expression which contains the
259     // allocated content as well as the point at which it has been allocated.
260     // Currently, the next line is useless.
261     Report->addRange(I->second.Address->getSourceRange());
262     Eng.getBugReporter().EmitReport(Report);
263   }
264 }
265 
266 void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
267   mgr.registerChecker<MacOSKeychainAPIChecker>();
268 }
269