xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp (revision 4aa34a532569b10476195e2ed4edb55cee128d21)
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