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