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
27using namespace clang;
28using namespace ento;
29
30namespace {
31class 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
39public:
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
74private:
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.
152REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData,
153 SymbolRef,
154 MacOSKeychainAPIChecker::AllocationState)
155
156static 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
166const 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
178unsigned 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
196static 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.
204static 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.
220void 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
244void 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
394void 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.
440const ExplodedNode *
441MacOSKeychainAPIChecker::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
464std::unique_ptr<PathSensitiveBugReport>
465MacOSKeychainAPIChecker::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.
496ProgramStateRef 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
532void 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
573ProgramStateRef 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
611PathDiagnosticPieceRef
612MacOSKeychainAPIChecker::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
641void 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
657void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
658 mgr.registerChecker<MacOSKeychainAPIChecker>();
659}
660
661bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) {
662 return true;
663}
664