clang: lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
22#include "llvm/ADT/STLExtras.h"
23#include "llvm/Support/raw_ostream.h"
24#include
25
26using namespace clang;
27using namespace ento;
28
29namespace {
30class MacOSKeychainAPIChecker : public Checker<check::PreStmt,
31 check::PostStmt,
32 check::DeadSymbols,
33 check::PointerEscape,
34 eval::Assume> {
35 const BugType BT{this, "Improper use of SecKeychain API",
37
38public:
39
40
41 struct AllocationState {
42
43 unsigned int AllocatorIdx;
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(AllocatorIdx);
57 ID.AddPointer(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;
66 const CallEvent *Call,
69 bool Assumption) const;
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
79 ValidAPI = 0,
80
81 ErrorAPI = 1,
82
83
84 PossibleAPI = 2
85 };
86
87
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
98 static const unsigned NoErr = 0;
99
100
101
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
109 const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym,
110 CheckerContext &C) const;
111
112 std::unique_ptr
113 generateAllocatedDataNotReleasedReport(const AllocationPair &AP,
114 ExplodedNode *N,
115 CheckerContext &C) const;
116
117
118 void markInteresting(PathSensitiveBugReport *R,
119 const AllocationPair &AP) const {
122 }
123
124
125
126
127 class SecKeychainBugVisitor : public BugReporterVisitor {
128 protected:
129
131
132 public:
133 SecKeychainBugVisitor(SymbolRef S) : Sym(S) {}
134
135 void Profile(llvm::FoldingSetNodeID &ID) const override {
136 static int X = 0;
138 ID.AddPointer(Sym);
139 }
140
142 BugReporterContext &BRC,
143 PathSensitiveBugReport &BR) override;
144 };
145};
146}
147
148
149
150
153 MacOSKeychainAPIChecker::AllocationState)
154
155static bool isEnclosingFunctionParam(const Expr *E) {
157 if (const DeclRefExpr *DRE = dyn_cast(E)) {
158 const ValueDecl *VD = DRE->getDecl();
160 return true;
161 }
162 return false;
163}
164
165const MacOSKeychainAPIChecker::ADFunctionInfo
166 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = {
167 {"SecKeychainItemCopyContent", 4, 3, ValidAPI},
168 {"SecKeychainFindGenericPassword", 6, 3, ValidAPI},
169 {"SecKeychainFindInternetPassword", 13, 3, ValidAPI},
170 {"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI},
171 {"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI},
172 {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI},
173 {"free", 0, InvalidIdx, ErrorAPI},
174 {"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI},
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
184 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
185 return InvalidIdx;
186 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
187 return InvalidIdx;
188
189 return I;
190 }
191
192 return InvalidIdx;
193}
194
196 if (!Arg)
197 return false;
199}
200
201
202
207
210 SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol();
211 if (sym)
212 return sym;
213 }
214 return nullptr;
215}
216
217
218
219void MacOSKeychainAPIChecker::
220 generateDeallocatorMismatchReport(const AllocationPair &AP,
221 const Expr *ArgExpr,
222 CheckerContext &C) const {
224 State = State->remove(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(BT, os.str(), N);
237 Report->addVisitor(std::make_unique(AP.first));
239 markInteresting(Report.get(), AP);
240 C.emitReport(std::move(Report));
241}
242
243void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
244 CheckerContext &C) const {
245 unsigned idx = InvalidIdx;
247
248 const FunctionDecl *FD = C.getCalleeDecl(CE);
249 if (!FD || FD->getKind() != Decl::Function)
250 return;
251
252 StringRef funName = C.getCalleeName(FD);
253 if (funName.empty())
254 return;
255
256
257 idx = getTrackedFunctionIndex(funName, true);
258 if (idx != InvalidIdx) {
259 unsigned paramIdx = FunctionsToTrack[idx].Param;
261 return;
262
263 const Expr *ArgExpr = CE->getArg(paramIdx);
265 if (const AllocationState *AS = State->get(V)) {
266
267
268 State = State->remove(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(BT, os.str(), N);
280 Report->addVisitor(std::make_unique(V));
282 Report->markInteresting(AS->Region);
283 C.emitReport(std::move(Report));
284 }
285 return;
286 }
287
288
289 idx = getTrackedFunctionIndex(funName, false);
290 if (idx == InvalidIdx)
291 return;
292
293 unsigned paramIdx = FunctionsToTrack[idx].Param;
295 return;
296
297
298 const Expr *ArgExpr = CE->getArg(paramIdx);
299 SVal ArgSVal = C.getSVal(ArgExpr);
300
301
303 return;
304
306
307
308
309 bool RegionArgIsBad = false;
310 if (!ArgSM) {
312 return;
313 RegionArgIsBad = true;
314 }
315
316
317 const AllocationState *AS = State->get(ArgSM);
318 if (!AS)
319 return;
320
321
322
323 if (RegionArgIsBad) {
324
325
326 if (isEnclosingFunctionParam(ArgExpr))
327 return;
328
329 ExplodedNode *N = C.generateNonFatalErrorNode(State);
330 if (!N)
331 return;
332 auto Report = std::make_unique(
333 BT, "Trying to free data which has not been allocated.", N);
335 if (AS)
336 Report->markInteresting(AS->Region);
337 C.emitReport(std::move(Report));
338 return;
339 }
340
341
342 if (FunctionsToTrack[idx].Kind == PossibleAPI) {
343
344 if (funName == "CFStringCreateWithBytesNoCopy") {
346
349 const AllocationPair AP = std::make_pair(ArgSM, AS);
350 generateDeallocatorMismatchReport(AP, ArgExpr, C);
351 return;
352 }
353
354 if (const DeclRefExpr *DE = dyn_cast(DeallocatorExpr)) {
355 StringRef DeallocatorName = DE->getFoundDecl()->getName();
356 if (DeallocatorName == "kCFAllocatorDefault" ||
357 DeallocatorName == "kCFAllocatorSystemDefault" ||
358 DeallocatorName == "kCFAllocatorMalloc") {
359 const AllocationPair AP = std::make_pair(ArgSM, AS);
360 generateDeallocatorMismatchReport(AP, ArgExpr, C);
361 return;
362 }
363
364
365 if (DE->getFoundDecl()->getName() == "kCFAllocatorNull")
366 return;
367 }
368
369
370 State = State->remove(ArgSM);
371 C.addTransition(State);
372 return;
373 }
374
375 llvm_unreachable("We know of no other possible APIs.");
376 }
377
378
379
380 State = State->remove(ArgSM);
381
382
383 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
384 if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) {
385 const AllocationPair AP = std::make_pair(ArgSM, 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 {
396 const FunctionDecl *FD = C.getCalleeDecl(CE);
397 if (!FD || FD->getKind() != Decl::Function)
398 return;
399
400 StringRef funName = C.getCalleeName(FD);
401
402
403 unsigned idx = getTrackedFunctionIndex(funName, true);
404 if (idx == InvalidIdx)
405 return;
406
407 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
408
409
410 if (isEnclosingFunctionParam(ArgExpr) &&
411 C.getLocationContext()->getParent() == nullptr)
412 return;
413
415
416
417
418
419
420
421
422
423
424
425
426
427 SymbolRef RetStatusSymbol = C.getSVal(CE).getAsSymbol();
428 C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol);
429
430
431 State = State->set(V, AllocationState(ArgExpr, idx,
432 RetStatusSymbol));
433 assert(State);
434 C.addTransition(State);
435 }
436}
437
438
439const ExplodedNode *
440MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N,
442 CheckerContext &C) const {
444
445
446 const ExplodedNode *AllocNode = N;
447
448 while (N) {
449 if (!N->getState()->get(Sym))
450 break;
451
452
454 if (NContext == LeakContext ||
456 AllocNode = N;
458 }
459
460 return AllocNode;
461}
462
463std::unique_ptr
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
473
474
475 PathDiagnosticLocation LocUsedForUniqueing;
476 const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C);
478
479 if (AllocStmt)
481 C.getSourceManager(),
483
484 auto Report = std::make_unique(
485 BT, os.str(), N, LocUsedForUniqueing,
487
488 Report->addVisitor(std::make_unique(AP.first));
489 markInteresting(Report.get(), AP);
491}
492
493
494
497 bool Assumption) const {
498 AllocatedDataTy AMap = State->get();
499 if (AMap.isEmpty())
500 return State;
501
502 auto *CondBSE = dyn_cast_or_null(Cond.getAsSymbol());
503 if (!CondBSE)
504 return State;
506 if (OpCode != BO_EQ && OpCode != BO_NE)
507 return State;
508
509
510
511 SymbolRef ReturnSymbol = nullptr;
512 if (auto *SIE = dyn_cast(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(Sym);
526 }
527
528 return State;
529}
530
531void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR,
532 CheckerContext &C) const {
534 AllocatedDataTy AMap = State->get();
535 if (AMap.isEmpty())
536 return;
537
539 AllocationPairVec Errors;
540 for (const auto &[Sym, AllocState] : AMap) {
542 continue;
543
545 State = State->remove(Sym);
546
547 ConstraintManager &CMgr = State->getConstraintManager();
548 ConditionTruthVal AllocFailed = CMgr.isNull(State, Sym);
550 continue;
551 Errors.push_back(std::make_pair(Sym, &AllocState));
552 }
553 if (!Changed) {
554
555 C.addTransition(State);
556 return;
557 }
558
559 ExplodedNode *N = C.generateNonFatalErrorNode(C.getState());
560 if (!N)
561 return;
562
563
564 for (const auto &P : Errors)
565 C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C));
566
567
568 C.addTransition(State, N);
569}
570
571ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape(
574
575
576 if ( || Call->getDecl())
577 return State;
578
579 for (auto I : State->get()) {
581 if (Escaped.count(Sym))
582 State = State->remove(Sym);
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600 if (const auto *SD = dyn_cast(Sym)) {
601 SymbolRef ParentSym = SD->getParentSymbol();
602 if (Escaped.count(ParentSym))
603 State = State->remove(Sym);
604 }
605 }
606 return State;
607}
608
610MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
611 const ExplodedNode *N, BugReporterContext &BRC,
612 PathSensitiveBugReport &BR) {
613 const AllocationState *AS = N->getState()->get(Sym);
614 if (!AS)
615 return nullptr;
616 const AllocationState *ASPrev =
618 if (ASPrev)
619 return nullptr;
620
621
622
623 const CallExpr *CE =
626 assert(funDecl && "We do not support indirect function calls as of now.");
627 StringRef funName = funDecl->getName();
628
629
630 unsigned Idx = getTrackedFunctionIndex(funName, true);
631 assert(Idx != InvalidIdx && "This should be a call to an allocator.");
632 const Expr *ArgExpr = CE->getArg(FunctionsToTrack[Idx].Param);
635 return std::make_shared(Pos,
636 "Data is allocated here.");
637}
638
639void MacOSKeychainAPIChecker::printState(raw_ostream &Out,
641 const char *NL,
642 const char *Sep) const {
643
644 AllocatedDataTy AMap = State->get();
645
646 if (!AMap.isEmpty()) {
647 Out << Sep << "KeychainAPIChecker :" << NL;
648 for (SymbolRef Sym : llvm::make_first_range(AMap)) {
650 }
651 }
652}
653
654
655void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
657}
658
659bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) {
660 return true;
661}
static bool isBadDeallocationArgument(const MemRegion *Arg)
Definition MacOSKeychainAPIChecker.cpp:195
static SymbolRef getAsPointeeSymbol(const Expr *Expr, CheckerContext &C)
Given the address expression, retrieve the value it's pointing to.
Definition MacOSKeychainAPIChecker.cpp:203
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
BinaryOperatorKind Opcode
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
FunctionDecl * getDirectCallee()
If the callee is a FunctionDecl, return it. Otherwise return null.
unsigned getNumArgs() const
getNumArgs - Return the number of actual arguments to this call.
A reference to a declared variable, function, enum, etc.
This represents one expression.
Expr * IgnoreParenCasts() LLVM_READONLY
Skip past any parentheses and casts which might surround this expression until reaching a fixed point...
@ NPC_ValueDependentIsNotNull
Specifies that a value-dependent expression should be considered to never be a null pointer constant.
NullPointerConstantKind isNullPointerConstant(ASTContext &Ctx, NullPointerConstantValueDependence NPC) const
isNullPointerConstant - C99 6.3.2.3p3 - Test if this reduces down to a Null pointer constant.
bool isParentOf(const LocationContext *LC) const
const Decl * getDecl() const
StringRef getName() const
Get the name of identifier for this declaration as a StringRef.
T castAs() const
Convert to the specified ProgramPoint type, asserting that this ProgramPoint is of the desired type.
SourceRange getSourceRange() const LLVM_READONLY
SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...
Represent the declaration of a variable (in which case it is an lvalue) a function (in which case it ...
const SourceManager & getSourceManager() const
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
bool isConstrainedTrue() const
Return true if the constraint is perfectly constrained to 'true'.
const ProgramStateRef & getState() const
pred_iterator pred_begin()
const Stmt * getStmtForDiagnostics() const
If the node's program point corresponds to a statement, retrieve that statement.
ProgramPoint getLocation() const
getLocation - Returns the edge associated with the given node.
const LocationContext * getLocationContext() const
ExplodedNode * getFirstPred()
MemRegion - The root abstract class for all memory regions.
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
void markInteresting(SymbolRef sym, bugreporter::TrackingKind TKind=bugreporter::TrackingKind::Thorough)
Marks a symbol as interesting.
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
SymbolRef getAsLocSymbol(bool IncludeBaseRegions=false) const
If this SVal is a location and wraps a symbol, return that SymbolRef.
const MemRegion * getAsRegion() const
virtual void dumpToStream(raw_ostream &os) const
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
const char *const AppleAPIMisuse
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
llvm::DenseSet< SymbolRef > InvalidatedSymbols
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const SymExpr * SymbolRef
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
nullptr
This class represents a compute construct, representing a 'Kind' of ‘parallel’, 'serial',...
U cast(CodeGen::Address addr)