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