/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==// |
2 | | // |
3 | | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | | // See https://llvm.org/LICENSE.txt for license information. |
5 | | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | // This checker flags misuses of KeyChainAPI. In particular, the password data |
9 | | // allocated/returned by SecKeychainItemCopyContent, |
10 | | // SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has |
11 | | // to be freed using a call to SecKeychainItemFreeContent. |
12 | | //===----------------------------------------------------------------------===// |
13 | | |
14 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
15 | | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
16 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
17 | | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
18 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
19 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
20 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
21 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" |
22 | | #include "llvm/ADT/STLExtras.h" |
23 | | #include "llvm/ADT/SmallString.h" |
24 | | #include "llvm/Support/raw_ostream.h" |
25 | | #include <optional> |
26 | | |
27 | | using namespace clang; |
28 | | using namespace ento; |
29 | | |
30 | | namespace { |
31 | | class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, |
32 | | check::PostStmt<CallExpr>, |
33 | | check::DeadSymbols, |
34 | | check::PointerEscape, |
35 | | eval::Assume> { |
36 | | mutable std::unique_ptr<BugType> BT; |
37 | | |
38 | | public: |
39 | | /// AllocationState is a part of the checker specific state together with the |
40 | | /// MemRegion corresponding to the allocated data. |
41 | | struct AllocationState { |
42 | | /// The index of the allocator function. |
43 | | unsigned int AllocatorIdx; |
44 | | SymbolRef Region; |
45 | | |
46 | | AllocationState(const Expr *E, unsigned int Idx, SymbolRef R) : |
47 | 28 | AllocatorIdx(Idx), |
48 | 28 | Region(R) {} |
49 | | |
50 | 2 | bool operator==(const AllocationState &X) const { |
51 | 2 | return (AllocatorIdx == X.AllocatorIdx && |
52 | 2 | Region == X.Region); |
53 | 2 | } |
54 | | |
55 | 31 | void Profile(llvm::FoldingSetNodeID &ID) const { |
56 | 31 | ID.AddInteger(AllocatorIdx); |
57 | 31 | ID.AddPointer(Region); |
58 | 31 | } |
59 | | }; |
60 | | |
61 | | void checkPreStmt(const CallExpr *S, CheckerContext &C) const; |
62 | | void checkPostStmt(const CallExpr *S, CheckerContext &C) const; |
63 | | void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; |
64 | | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
65 | | const InvalidatedSymbols &Escaped, |
66 | | const CallEvent *Call, |
67 | | PointerEscapeKind Kind) const; |
68 | | ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, |
69 | | bool Assumption) const; |
70 | | void printState(raw_ostream &Out, ProgramStateRef State, |
71 | | const char *NL, const char *Sep) const override; |
72 | | |
73 | | private: |
74 | | typedef std::pair<SymbolRef, const AllocationState*> AllocationPair; |
75 | | typedef SmallVector<AllocationPair, 2> AllocationPairVec; |
76 | | |
77 | | enum APIKind { |
78 | | /// Denotes functions tracked by this checker. |
79 | | ValidAPI = 0, |
80 | | /// The functions commonly/mistakenly used in place of the given API. |
81 | | ErrorAPI = 1, |
82 | | /// The functions which may allocate the data. These are tracked to reduce |
83 | | /// the false alarm rate. |
84 | | PossibleAPI = 2 |
85 | | }; |
86 | | /// Stores the information about the allocator and deallocator functions - |
87 | | /// these are the functions the checker is tracking. |
88 | | struct ADFunctionInfo { |
89 | | const char* Name; |
90 | | unsigned int Param; |
91 | | unsigned int DeallocatorIdx; |
92 | | APIKind Kind; |
93 | | }; |
94 | | static const unsigned InvalidIdx = 100000; |
95 | | static const unsigned FunctionsToTrackSize = 8; |
96 | | static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize]; |
97 | | /// The value, which represents no error return value for allocator functions. |
98 | | static const unsigned NoErr = 0; |
99 | | |
100 | | /// Given the function name, returns the index of the allocator/deallocator |
101 | | /// function. |
102 | | static unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator); |
103 | | |
104 | 14 | inline void initBugType() const { |
105 | 14 | if (!BT) |
106 | 2 | BT.reset(new BugType(this, "Improper use of SecKeychain API", |
107 | 2 | "API Misuse (Apple)")); |
108 | 14 | } |
109 | | |
110 | | void generateDeallocatorMismatchReport(const AllocationPair &AP, |
111 | | const Expr *ArgExpr, |
112 | | CheckerContext &C) const; |
113 | | |
114 | | /// Find the allocation site for Sym on the path leading to the node N. |
115 | | const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym, |
116 | | CheckerContext &C) const; |
117 | | |
118 | | std::unique_ptr<PathSensitiveBugReport> |
119 | | generateAllocatedDataNotReleasedReport(const AllocationPair &AP, |
120 | | ExplodedNode *N, |
121 | | CheckerContext &C) const; |
122 | | |
123 | | /// Mark an AllocationPair interesting for diagnostic reporting. |
124 | | void markInteresting(PathSensitiveBugReport *R, |
125 | 13 | const AllocationPair &AP) const { |
126 | 13 | R->markInteresting(AP.first); |
127 | 13 | R->markInteresting(AP.second->Region); |
128 | 13 | } |
129 | | |
130 | | /// The bug visitor which allows us to print extra diagnostics along the |
131 | | /// BugReport path. For example, showing the allocation site of the leaked |
132 | | /// region. |
133 | | class SecKeychainBugVisitor : public BugReporterVisitor { |
134 | | protected: |
135 | | // The allocated region symbol tracked by the main analysis. |
136 | | SymbolRef Sym; |
137 | | |
138 | | public: |
139 | 14 | SecKeychainBugVisitor(SymbolRef S) : Sym(S) {} |
140 | | |
141 | 14 | void Profile(llvm::FoldingSetNodeID &ID) const override { |
142 | 14 | static int X = 0; |
143 | 14 | ID.AddPointer(&X); |
144 | 14 | ID.AddPointer(Sym); |
145 | 14 | } |
146 | | |
147 | | PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, |
148 | | BugReporterContext &BRC, |
149 | | PathSensitiveBugReport &BR) override; |
150 | | }; |
151 | | }; |
152 | | } |
153 | | |
154 | | /// ProgramState traits to store the currently allocated (and not yet freed) |
155 | | /// symbols. This is a map from the allocated content symbol to the |
156 | | /// corresponding AllocationState. |
157 | | REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData, |
158 | | SymbolRef, |
159 | | MacOSKeychainAPIChecker::AllocationState) |
160 | | |
161 | 31 | static bool isEnclosingFunctionParam(const Expr *E) { |
162 | 31 | E = E->IgnoreParenCasts(); |
163 | 31 | if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { |
164 | 5 | const ValueDecl *VD = DRE->getDecl(); |
165 | 5 | if (isa<ImplicitParamDecl, ParmVarDecl>(VD)) |
166 | 4 | return true; |
167 | 5 | } |
168 | 27 | return false; |
169 | 31 | } |
170 | | |
171 | | const MacOSKeychainAPIChecker::ADFunctionInfo |
172 | | MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = { |
173 | | {"SecKeychainItemCopyContent", 4, 3, ValidAPI}, // 0 |
174 | | {"SecKeychainFindGenericPassword", 6, 3, ValidAPI}, // 1 |
175 | | {"SecKeychainFindInternetPassword", 13, 3, ValidAPI}, // 2 |
176 | | {"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI}, // 3 |
177 | | {"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI}, // 4 |
178 | | {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI}, // 5 |
179 | | {"free", 0, InvalidIdx, ErrorAPI}, // 6 |
180 | | {"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI}, // 7 |
181 | | }; |
182 | | |
183 | | unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, |
184 | 555 | bool IsAllocator) { |
185 | 4.16k | for (unsigned I = 0; I < FunctionsToTrackSize; ++I3.60k ) { |
186 | 3.75k | ADFunctionInfo FI = FunctionsToTrack[I]; |
187 | 3.75k | if (FI.Name != Name) |
188 | 3.60k | continue; |
189 | | // Make sure the function is of the right type (allocator vs deallocator). |
190 | 150 | if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx)125 ) |
191 | 50 | return InvalidIdx; |
192 | 100 | if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx)25 ) |
193 | 0 | return InvalidIdx; |
194 | | |
195 | 100 | return I; |
196 | 100 | } |
197 | | // The function is not tracked. |
198 | 405 | return InvalidIdx; |
199 | 555 | } |
200 | | |
201 | 0 | static bool isBadDeallocationArgument(const MemRegion *Arg) { |
202 | 0 | if (!Arg) |
203 | 0 | return false; |
204 | 0 | return isa<AllocaRegion, BlockDataRegion, TypedRegion>(Arg); |
205 | 0 | } |
206 | | |
207 | | /// Given the address expression, retrieve the value it's pointing to. Assume |
208 | | /// that value is itself an address, and return the corresponding symbol. |
209 | | static SymbolRef getAsPointeeSymbol(const Expr *Expr, |
210 | 61 | CheckerContext &C) { |
211 | 61 | ProgramStateRef State = C.getState(); |
212 | 61 | SVal ArgV = C.getSVal(Expr); |
213 | | |
214 | 61 | if (std::optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) { |
215 | 57 | StoreManager& SM = C.getStoreManager(); |
216 | 57 | SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol(); |
217 | 57 | if (sym) |
218 | 31 | return sym; |
219 | 57 | } |
220 | 30 | return nullptr; |
221 | 61 | } |
222 | | |
223 | | // Report deallocator mismatch. Remove the region from tracking - reporting a |
224 | | // missing free error after this one is redundant. |
225 | | void MacOSKeychainAPIChecker:: |
226 | | generateDeallocatorMismatchReport(const AllocationPair &AP, |
227 | | const Expr *ArgExpr, |
228 | 4 | CheckerContext &C) const { |
229 | 4 | ProgramStateRef State = C.getState(); |
230 | 4 | State = State->remove<AllocatedData>(AP.first); |
231 | 4 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
232 | | |
233 | 4 | if (!N) |
234 | 0 | return; |
235 | 4 | initBugType(); |
236 | 4 | SmallString<80> sbuf; |
237 | 4 | llvm::raw_svector_ostream os(sbuf); |
238 | 4 | unsigned int PDeallocIdx = |
239 | 4 | FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx; |
240 | | |
241 | 4 | os << "Deallocator doesn't match the allocator: '" |
242 | 4 | << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; |
243 | 4 | auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); |
244 | 4 | Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); |
245 | 4 | Report->addRange(ArgExpr->getSourceRange()); |
246 | 4 | markInteresting(Report.get(), AP); |
247 | 4 | C.emitReport(std::move(Report)); |
248 | 4 | } |
249 | | |
250 | | void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, |
251 | 370 | CheckerContext &C) const { |
252 | 370 | unsigned idx = InvalidIdx; |
253 | 370 | ProgramStateRef State = C.getState(); |
254 | | |
255 | 370 | const FunctionDecl *FD = C.getCalleeDecl(CE); |
256 | 370 | if (!FD || FD->getKind() != Decl::Function365 ) |
257 | 179 | return; |
258 | | |
259 | 191 | StringRef funName = C.getCalleeName(FD); |
260 | 191 | if (funName.empty()) |
261 | 0 | return; |
262 | | |
263 | | // If it is a call to an allocator function, it could be a double allocation. |
264 | 191 | idx = getTrackedFunctionIndex(funName, true); |
265 | 191 | if (idx != InvalidIdx) { |
266 | 31 | unsigned paramIdx = FunctionsToTrack[idx].Param; |
267 | 31 | if (CE->getNumArgs() <= paramIdx) |
268 | 0 | return; |
269 | | |
270 | 31 | const Expr *ArgExpr = CE->getArg(paramIdx); |
271 | 31 | if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) |
272 | 3 | if (const AllocationState *AS = State->get<AllocatedData>(V)) { |
273 | | // Remove the value from the state. The new symbol will be added for |
274 | | // tracking when the second allocator is processed in checkPostStmt(). |
275 | 1 | State = State->remove<AllocatedData>(V); |
276 | 1 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
277 | 1 | if (!N) |
278 | 0 | return; |
279 | 1 | initBugType(); |
280 | 1 | SmallString<128> sbuf; |
281 | 1 | llvm::raw_svector_ostream os(sbuf); |
282 | 1 | unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; |
283 | 1 | os << "Allocated data should be released before another call to " |
284 | 1 | << "the allocator: missing a call to '" |
285 | 1 | << FunctionsToTrack[DIdx].Name |
286 | 1 | << "'."; |
287 | 1 | auto Report = |
288 | 1 | std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); |
289 | 1 | Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(V)); |
290 | 1 | Report->addRange(ArgExpr->getSourceRange()); |
291 | 1 | Report->markInteresting(AS->Region); |
292 | 1 | C.emitReport(std::move(Report)); |
293 | 1 | } |
294 | 31 | return; |
295 | 31 | } |
296 | | |
297 | | // Is it a call to one of deallocator functions? |
298 | 160 | idx = getTrackedFunctionIndex(funName, false); |
299 | 160 | if (idx == InvalidIdx) |
300 | 135 | return; |
301 | | |
302 | 25 | unsigned paramIdx = FunctionsToTrack[idx].Param; |
303 | 25 | if (CE->getNumArgs() <= paramIdx) |
304 | 1 | return; |
305 | | |
306 | | // Check the argument to the deallocator. |
307 | 24 | const Expr *ArgExpr = CE->getArg(paramIdx); |
308 | 24 | SVal ArgSVal = C.getSVal(ArgExpr); |
309 | | |
310 | | // Undef is reported by another checker. |
311 | 24 | if (ArgSVal.isUndef()) |
312 | 1 | return; |
313 | | |
314 | 23 | SymbolRef ArgSM = ArgSVal.getAsLocSymbol(); |
315 | | |
316 | | // If the argument is coming from the heap, globals, or unknown, do not |
317 | | // report it. |
318 | 23 | bool RegionArgIsBad = false; |
319 | 23 | if (!ArgSM) { |
320 | 0 | if (!isBadDeallocationArgument(ArgSVal.getAsRegion())) |
321 | 0 | return; |
322 | 0 | RegionArgIsBad = true; |
323 | 0 | } |
324 | | |
325 | | // Is the argument to the call being tracked? |
326 | 23 | const AllocationState *AS = State->get<AllocatedData>(ArgSM); |
327 | 23 | if (!AS) |
328 | 4 | return; |
329 | | |
330 | | // TODO: We might want to report double free here. |
331 | | // (that would involve tracking all the freed symbols in the checker state). |
332 | 19 | if (RegionArgIsBad) { |
333 | | // It is possible that this is a false positive - the argument might |
334 | | // have entered as an enclosing function parameter. |
335 | 0 | if (isEnclosingFunctionParam(ArgExpr)) |
336 | 0 | return; |
337 | | |
338 | 0 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
339 | 0 | if (!N) |
340 | 0 | return; |
341 | 0 | initBugType(); |
342 | 0 | auto Report = std::make_unique<PathSensitiveBugReport>( |
343 | 0 | *BT, "Trying to free data which has not been allocated.", N); |
344 | 0 | Report->addRange(ArgExpr->getSourceRange()); |
345 | 0 | if (AS) |
346 | 0 | Report->markInteresting(AS->Region); |
347 | 0 | C.emitReport(std::move(Report)); |
348 | 0 | return; |
349 | 0 | } |
350 | | |
351 | | // Process functions which might deallocate. |
352 | 19 | if (FunctionsToTrack[idx].Kind == PossibleAPI) { |
353 | | |
354 | 5 | if (funName == "CFStringCreateWithBytesNoCopy") { |
355 | 5 | const Expr *DeallocatorExpr = CE->getArg(5)->IgnoreParenCasts(); |
356 | | // NULL ~ default deallocator, so warn. |
357 | 5 | if (DeallocatorExpr->isNullPointerConstant(C.getASTContext(), |
358 | 5 | Expr::NPC_ValueDependentIsNotNull)) { |
359 | 1 | const AllocationPair AP = std::make_pair(ArgSM, AS); |
360 | 1 | generateDeallocatorMismatchReport(AP, ArgExpr, C); |
361 | 1 | return; |
362 | 1 | } |
363 | | // One of the default allocators, so warn. |
364 | 4 | if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) { |
365 | 3 | StringRef DeallocatorName = DE->getFoundDecl()->getName(); |
366 | 3 | if (DeallocatorName == "kCFAllocatorDefault" || |
367 | 3 | DeallocatorName == "kCFAllocatorSystemDefault"2 || |
368 | 3 | DeallocatorName == "kCFAllocatorMalloc"2 ) { |
369 | 1 | const AllocationPair AP = std::make_pair(ArgSM, AS); |
370 | 1 | generateDeallocatorMismatchReport(AP, ArgExpr, C); |
371 | 1 | return; |
372 | 1 | } |
373 | | // If kCFAllocatorNull, which does not deallocate, we still have to |
374 | | // find the deallocator. |
375 | 2 | if (DE->getFoundDecl()->getName() == "kCFAllocatorNull") |
376 | 1 | return; |
377 | 2 | } |
378 | | // In all other cases, assume the user supplied a correct deallocator |
379 | | // that will free memory so stop tracking. |
380 | 2 | State = State->remove<AllocatedData>(ArgSM); |
381 | 2 | C.addTransition(State); |
382 | 2 | return; |
383 | 4 | } |
384 | | |
385 | 0 | llvm_unreachable("We know of no other possible APIs."); |
386 | 0 | } |
387 | | |
388 | | // The call is deallocating a value we previously allocated, so remove it |
389 | | // from the next state. |
390 | 14 | State = State->remove<AllocatedData>(ArgSM); |
391 | | |
392 | | // Check if the proper deallocator is used. |
393 | 14 | unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; |
394 | 14 | if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)12 ) { |
395 | 2 | const AllocationPair AP = std::make_pair(ArgSM, AS); |
396 | 2 | generateDeallocatorMismatchReport(AP, ArgExpr, C); |
397 | 2 | return; |
398 | 2 | } |
399 | | |
400 | 12 | C.addTransition(State); |
401 | 12 | } |
402 | | |
403 | | void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, |
404 | 373 | CheckerContext &C) const { |
405 | 373 | ProgramStateRef State = C.getState(); |
406 | 373 | const FunctionDecl *FD = C.getCalleeDecl(CE); |
407 | 373 | if (!FD || FD->getKind() != Decl::Function368 ) |
408 | 182 | return; |
409 | | |
410 | 191 | StringRef funName = C.getCalleeName(FD); |
411 | | |
412 | | // If a value has been allocated, add it to the set for tracking. |
413 | 191 | unsigned idx = getTrackedFunctionIndex(funName, true); |
414 | 191 | if (idx == InvalidIdx) |
415 | 160 | return; |
416 | | |
417 | 31 | const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); |
418 | | // If the argument entered as an enclosing function parameter, skip it to |
419 | | // avoid false positives. |
420 | 31 | if (isEnclosingFunctionParam(ArgExpr) && |
421 | 31 | C.getLocationContext()->getParent() == nullptr4 ) |
422 | 1 | return; |
423 | | |
424 | 30 | if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { |
425 | | // If the argument points to something that's not a symbolic region, it |
426 | | // can be: |
427 | | // - unknown (cannot reason about it) |
428 | | // - undefined (already reported by other checker) |
429 | | // - constant (null - should not be tracked, |
430 | | // other constant will generate a compiler warning) |
431 | | // - goto (should be reported by other checker) |
432 | | |
433 | | // The call return value symbol should stay alive for as long as the |
434 | | // allocated value symbol, since our diagnostics depend on the value |
435 | | // returned by the call. Ex: Data should only be freed if noErr was |
436 | | // returned during allocation.) |
437 | 28 | SymbolRef RetStatusSymbol = C.getSVal(CE).getAsSymbol(); |
438 | 28 | C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); |
439 | | |
440 | | // Track the allocated value in the checker state. |
441 | 28 | State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx, |
442 | 28 | RetStatusSymbol)); |
443 | 28 | assert(State); |
444 | 28 | C.addTransition(State); |
445 | 28 | } |
446 | 30 | } |
447 | | |
448 | | // TODO: This logic is the same as in Malloc checker. |
449 | | const ExplodedNode * |
450 | | MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N, |
451 | | SymbolRef Sym, |
452 | 9 | CheckerContext &C) const { |
453 | 9 | const LocationContext *LeakContext = N->getLocationContext(); |
454 | | // Walk the ExplodedGraph backwards and find the first node that referred to |
455 | | // the tracked symbol. |
456 | 9 | const ExplodedNode *AllocNode = N; |
457 | | |
458 | 303 | while (N) { |
459 | 303 | if (!N->getState()->get<AllocatedData>(Sym)) |
460 | 9 | break; |
461 | | // Allocation node, is the last node in the current or parent context in |
462 | | // which the symbol was tracked. |
463 | 294 | const LocationContext *NContext = N->getLocationContext(); |
464 | 294 | if (NContext == LeakContext || |
465 | 294 | NContext->isParentOf(LeakContext)74 ) |
466 | 220 | AllocNode = N; |
467 | 294 | N = N->pred_empty() ? nullptr0 : *(N->pred_begin()); |
468 | 294 | } |
469 | | |
470 | 9 | return AllocNode; |
471 | 9 | } |
472 | | |
473 | | std::unique_ptr<PathSensitiveBugReport> |
474 | | MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( |
475 | 9 | const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const { |
476 | 9 | const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; |
477 | 9 | initBugType(); |
478 | 9 | SmallString<70> sbuf; |
479 | 9 | llvm::raw_svector_ostream os(sbuf); |
480 | 9 | os << "Allocated data is not released: missing a call to '" |
481 | 9 | << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; |
482 | | |
483 | | // Most bug reports are cached at the location where they occurred. |
484 | | // With leaks, we want to unique them by the location where they were |
485 | | // allocated, and only report a single path. |
486 | 9 | PathDiagnosticLocation LocUsedForUniqueing; |
487 | 9 | const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C); |
488 | 9 | const Stmt *AllocStmt = AllocNode->getStmtForDiagnostics(); |
489 | | |
490 | 9 | if (AllocStmt) |
491 | 9 | LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, |
492 | 9 | C.getSourceManager(), |
493 | 9 | AllocNode->getLocationContext()); |
494 | | |
495 | 9 | auto Report = std::make_unique<PathSensitiveBugReport>( |
496 | 9 | *BT, os.str(), N, LocUsedForUniqueing, |
497 | 9 | AllocNode->getLocationContext()->getDecl()); |
498 | | |
499 | 9 | Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); |
500 | 9 | markInteresting(Report.get(), AP); |
501 | 9 | return Report; |
502 | 9 | } |
503 | | |
504 | | /// If the return symbol is assumed to be error, remove the allocated info |
505 | | /// from consideration. |
506 | | ProgramStateRef MacOSKeychainAPIChecker::evalAssume(ProgramStateRef State, |
507 | | SVal Cond, |
508 | 1.64k | bool Assumption) const { |
509 | 1.64k | AllocatedDataTy AMap = State->get<AllocatedData>(); |
510 | 1.64k | if (AMap.isEmpty()) |
511 | 1.56k | return State; |
512 | | |
513 | 77 | auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymbol()); |
514 | 77 | if (!CondBSE) |
515 | 27 | return State; |
516 | 50 | BinaryOperator::Opcode OpCode = CondBSE->getOpcode(); |
517 | 50 | if (OpCode != BO_EQ && OpCode != BO_NE6 ) |
518 | 0 | return State; |
519 | | |
520 | | // Match for a restricted set of patterns for cmparison of error codes. |
521 | | // Note, the comparisons of type '0 == st' are transformed into SymIntExpr. |
522 | 50 | SymbolRef ReturnSymbol = nullptr; |
523 | 50 | if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) { |
524 | 50 | const llvm::APInt &RHS = SIE->getRHS(); |
525 | 50 | bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr44 ) || |
526 | 50 | (44 OpCode == BO_NE44 && RHS == NoErr6 ); |
527 | 50 | if (!Assumption) |
528 | 25 | ErrorIsReturned = !ErrorIsReturned; |
529 | 50 | if (ErrorIsReturned) |
530 | 25 | ReturnSymbol = SIE->getLHS(); |
531 | 50 | } |
532 | | |
533 | 50 | if (ReturnSymbol) |
534 | 26 | for (auto [Sym, AllocState] : AMap)25 { |
535 | 26 | if (ReturnSymbol == AllocState.Region) |
536 | 20 | State = State->remove<AllocatedData>(Sym); |
537 | 26 | } |
538 | | |
539 | 50 | return State; |
540 | 50 | } |
541 | | |
542 | | void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, |
543 | 1.85k | CheckerContext &C) const { |
544 | 1.85k | ProgramStateRef State = C.getState(); |
545 | 1.85k | AllocatedDataTy AMap = State->get<AllocatedData>(); |
546 | 1.85k | if (AMap.isEmpty()) |
547 | 1.73k | return; |
548 | | |
549 | 116 | bool Changed = false; |
550 | 116 | AllocationPairVec Errors; |
551 | 119 | for (const auto &[Sym, AllocState] : AMap) { |
552 | 119 | if (!SR.isDead(Sym)) |
553 | 106 | continue; |
554 | | |
555 | 13 | Changed = true; |
556 | 13 | State = State->remove<AllocatedData>(Sym); |
557 | | // If the allocated symbol is null do not report. |
558 | 13 | ConstraintManager &CMgr = State->getConstraintManager(); |
559 | 13 | ConditionTruthVal AllocFailed = CMgr.isNull(State, Sym); |
560 | 13 | if (AllocFailed.isConstrainedTrue()) |
561 | 4 | continue; |
562 | 9 | Errors.push_back(std::make_pair(Sym, &AllocState)); |
563 | 9 | } |
564 | 116 | if (!Changed) { |
565 | | // Generate the new, cleaned up state. |
566 | 103 | C.addTransition(State); |
567 | 103 | return; |
568 | 103 | } |
569 | | |
570 | 13 | static CheckerProgramPointTag Tag(this, "DeadSymbolsLeak"); |
571 | 13 | ExplodedNode *N = C.generateNonFatalErrorNode(C.getState(), &Tag); |
572 | 13 | if (!N) |
573 | 0 | return; |
574 | | |
575 | | // Generate the error reports. |
576 | 13 | for (const auto &P : Errors) |
577 | 9 | C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C)); |
578 | | |
579 | | // Generate the new, cleaned up state. |
580 | 13 | C.addTransition(State, N); |
581 | 13 | } |
582 | | |
583 | | ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( |
584 | | ProgramStateRef State, const InvalidatedSymbols &Escaped, |
585 | 303 | const CallEvent *Call, PointerEscapeKind Kind) const { |
586 | | // FIXME: This branch doesn't make any sense at all, but it is an overfitted |
587 | | // replacement for a previous overfitted code that was making even less sense. |
588 | 303 | if (!Call || Call->getDecl()269 ) |
589 | 298 | return State; |
590 | | |
591 | 5 | for (auto I : State->get<AllocatedData>()) { |
592 | 2 | SymbolRef Sym = I.first; |
593 | 2 | if (Escaped.count(Sym)) |
594 | 0 | State = State->remove<AllocatedData>(Sym); |
595 | | |
596 | | // This checker is special. Most checkers in fact only track symbols of |
597 | | // SymbolConjured type, eg. symbols returned from functions such as |
598 | | // malloc(). This checker tracks symbols returned as out-parameters. |
599 | | // |
600 | | // When a function is evaluated conservatively, the out-parameter's pointee |
601 | | // base region gets invalidated with a SymbolConjured. If the base region is |
602 | | // larger than the region we're interested in, the value we're interested in |
603 | | // would be SymbolDerived based on that SymbolConjured. However, such |
604 | | // SymbolDerived will never be listed in the Escaped set when the base |
605 | | // region is invalidated because ExprEngine doesn't know which symbols |
606 | | // were derived from a given symbol, while there can be infinitely many |
607 | | // valid symbols derived from any given symbol. |
608 | | // |
609 | | // Hence the extra boilerplate: remove the derived symbol when its parent |
610 | | // symbol escapes. |
611 | | // |
612 | 2 | if (const auto *SD = dyn_cast<SymbolDerived>(Sym)) { |
613 | 2 | SymbolRef ParentSym = SD->getParentSymbol(); |
614 | 2 | if (Escaped.count(ParentSym)) |
615 | 2 | State = State->remove<AllocatedData>(Sym); |
616 | 2 | } |
617 | 2 | } |
618 | 5 | return State; |
619 | 303 | } |
620 | | |
621 | | PathDiagnosticPieceRef |
622 | | MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( |
623 | | const ExplodedNode *N, BugReporterContext &BRC, |
624 | 1.06k | PathSensitiveBugReport &BR) { |
625 | 1.06k | const AllocationState *AS = N->getState()->get<AllocatedData>(Sym); |
626 | 1.06k | if (!AS) |
627 | 657 | return nullptr; |
628 | 412 | const AllocationState *ASPrev = |
629 | 412 | N->getFirstPred()->getState()->get<AllocatedData>(Sym); |
630 | 412 | if (ASPrev) |
631 | 399 | return nullptr; |
632 | | |
633 | | // (!ASPrev && AS) ~ We started tracking symbol in node N, it must be the |
634 | | // allocation site. |
635 | 13 | const CallExpr *CE = |
636 | 13 | cast<CallExpr>(N->getLocation().castAs<StmtPoint>().getStmt()); |
637 | 13 | const FunctionDecl *funDecl = CE->getDirectCallee(); |
638 | 13 | assert(funDecl && "We do not support indirect function calls as of now."); |
639 | 13 | StringRef funName = funDecl->getName(); |
640 | | |
641 | | // Get the expression of the corresponding argument. |
642 | 13 | unsigned Idx = getTrackedFunctionIndex(funName, true); |
643 | 13 | assert(Idx != InvalidIdx && "This should be a call to an allocator."); |
644 | 13 | const Expr *ArgExpr = CE->getArg(FunctionsToTrack[Idx].Param); |
645 | 13 | PathDiagnosticLocation Pos(ArgExpr, BRC.getSourceManager(), |
646 | 13 | N->getLocationContext()); |
647 | 13 | return std::make_shared<PathDiagnosticEventPiece>(Pos, |
648 | 13 | "Data is allocated here."); |
649 | 13 | } |
650 | | |
651 | | void MacOSKeychainAPIChecker::printState(raw_ostream &Out, |
652 | | ProgramStateRef State, |
653 | | const char *NL, |
654 | 0 | const char *Sep) const { |
655 | |
|
656 | 0 | AllocatedDataTy AMap = State->get<AllocatedData>(); |
657 | |
|
658 | 0 | if (!AMap.isEmpty()) { |
659 | 0 | Out << Sep << "KeychainAPIChecker :" << NL; |
660 | 0 | for (SymbolRef Sym : llvm::make_first_range(AMap)) { |
661 | 0 | Sym->dumpToStream(Out); |
662 | 0 | } |
663 | 0 | } |
664 | 0 | } |
665 | | |
666 | | |
667 | 48 | void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { |
668 | 48 | mgr.registerChecker<MacOSKeychainAPIChecker>(); |
669 | 48 | } |
670 | | |
671 | 96 | bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) { |
672 | 96 | return true; |
673 | 96 | } |