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
26using namespace clang;
27using namespace ento;
28
29namespace {
30class 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
38public:
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
73private:
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.
151REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData,
152 SymbolRef,
153 MacOSKeychainAPIChecker::AllocationState)
154
155static 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
165const 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
177unsigned 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
195static 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.
203static 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.
219void 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
243void 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
393void 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.
439const ExplodedNode *
440MacOSKeychainAPIChecker::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
463std::unique_ptr<PathSensitiveBugReport>
464MacOSKeychainAPIChecker::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.
495ProgramStateRef 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
531void 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
571ProgramStateRef 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
609PathDiagnosticPieceRef
610MacOSKeychainAPIChecker::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
639void 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
655void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
656 mgr.registerChecker<MacOSKeychainAPIChecker>();
657}
658
659bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) {
660 return true;
661}
662