22#include "llvm/ADT/STLExtras.h"
23#include "llvm/ADT/SmallString.h"
24#include "llvm/Support/raw_ostream.h"
31class MacOSKeychainAPIChecker :
public Checker<check::PreStmt<CallExpr>,
32 check::PostStmt<CallExpr>,
36 mutable std::unique_ptr<BugType> BT;
41 struct AllocationState {
43 unsigned int AllocatorIdx;
46 AllocationState(
const Expr *E,
unsigned int Idx,
SymbolRef R) :
51 return (AllocatorIdx ==
X.AllocatorIdx &&
55 void Profile(llvm::FoldingSetNodeID &ID)
const {
56 ID.AddInteger(AllocatorIdx);
57 ID.AddPointer(Region);
69 bool Assumption)
const;
71 const char *NL,
const char *Sep)
const override;
74 typedef std::pair<SymbolRef, const AllocationState*> AllocationPair;
88 struct ADFunctionInfo {
91 unsigned int DeallocatorIdx;
94 static const unsigned InvalidIdx = 100000;
95 static const unsigned FunctionsToTrackSize = 8;
96 static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize];
98 static const unsigned NoErr = 0;
102 static unsigned getTrackedFunctionIndex(StringRef Name,
bool IsAllocator);
104 inline void initBugType()
const {
106 BT.reset(
new BugType(
this,
"Improper use of SecKeychain API",
107 "API Misuse (Apple)"));
110 void generateDeallocatorMismatchReport(
const AllocationPair &AP,
118 std::unique_ptr<PathSensitiveBugReport>
119 generateAllocatedDataNotReleasedReport(
const AllocationPair &AP,
125 const AllocationPair &AP)
const {
139 SecKeychainBugVisitor(
SymbolRef S) : Sym(S) {}
141 void Profile(llvm::FoldingSetNodeID &ID)
const override {
159 MacOSKeychainAPIChecker::AllocationState)
161static bool isEnclosingFunctionParam(
const Expr *E) {
163 if (
const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) {
165 if (isa<ImplicitParamDecl, ParmVarDecl>(VD))
171const MacOSKeychainAPIChecker::ADFunctionInfo
172 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = {
173 {
"SecKeychainItemCopyContent", 4, 3, ValidAPI},
174 {
"SecKeychainFindGenericPassword", 6, 3, ValidAPI},
175 {
"SecKeychainFindInternetPassword", 13, 3, ValidAPI},
176 {
"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI},
177 {
"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI},
178 {
"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI},
179 {
"free", 0, InvalidIdx, ErrorAPI},
180 {
"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI},
183unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
185 for (
unsigned I = 0; I < FunctionsToTrackSize; ++I) {
186 ADFunctionInfo FI = FunctionsToTrack[I];
190 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
192 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
204 return isa<AllocaRegion, BlockDataRegion, TypedRegion>(Arg);
216 SymbolRef sym =
SM.getBinding(State->getStore(), *
X).getAsLocSymbol();
225void MacOSKeychainAPIChecker::
226 generateDeallocatorMismatchReport(
const AllocationPair &AP,
230 State = State->remove<AllocatedData>(AP.first);
237 llvm::raw_svector_ostream os(sbuf);
238 unsigned int PDeallocIdx =
239 FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx;
241 os <<
"Deallocator doesn't match the allocator: '"
242 << FunctionsToTrack[PDeallocIdx].Name <<
"' should be used.";
243 auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
244 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
246 markInteresting(Report.get(), AP);
247 C.emitReport(std::move(Report));
250void MacOSKeychainAPIChecker::checkPreStmt(
const CallExpr *CE,
252 unsigned idx = InvalidIdx;
256 if (!FD || FD->
getKind() != Decl::Function)
259 StringRef funName =
C.getCalleeName(FD);
264 idx = getTrackedFunctionIndex(funName,
true);
265 if (idx != InvalidIdx) {
266 unsigned paramIdx = FunctionsToTrack[idx].Param;
272 if (
const AllocationState *AS = State->get<AllocatedData>(
V)) {
275 State = State->remove<AllocatedData>(
V);
281 llvm::raw_svector_ostream os(sbuf);
282 unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
283 os <<
"Allocated data should be released before another call to "
284 <<
"the allocator: missing a call to '"
285 << FunctionsToTrack[DIdx].Name
288 std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
289 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(
V));
291 Report->markInteresting(AS->Region);
292 C.emitReport(std::move(Report));
298 idx = getTrackedFunctionIndex(funName,
false);
299 if (idx == InvalidIdx)
302 unsigned paramIdx = FunctionsToTrack[idx].Param;
308 SVal ArgSVal =
C.getSVal(ArgExpr);
318 bool RegionArgIsBad =
false;
322 RegionArgIsBad =
true;
326 const AllocationState *AS = State->get<AllocatedData>(ArgSM);
332 if (RegionArgIsBad) {
335 if (isEnclosingFunctionParam(ArgExpr))
342 auto Report = std::make_unique<PathSensitiveBugReport>(
343 *BT,
"Trying to free data which has not been allocated.", N);
346 Report->markInteresting(AS->Region);
347 C.emitReport(std::move(Report));
352 if (FunctionsToTrack[idx].Kind == PossibleAPI) {
354 if (funName ==
"CFStringCreateWithBytesNoCopy") {
359 const AllocationPair AP = std::make_pair(ArgSM, AS);
360 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
364 if (
const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) {
365 StringRef DeallocatorName = DE->getFoundDecl()->getName();
366 if (DeallocatorName ==
"kCFAllocatorDefault" ||
367 DeallocatorName ==
"kCFAllocatorSystemDefault" ||
368 DeallocatorName ==
"kCFAllocatorMalloc") {
369 const AllocationPair AP = std::make_pair(ArgSM, AS);
370 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
375 if (DE->getFoundDecl()->getName() ==
"kCFAllocatorNull")
380 State = State->remove<AllocatedData>(ArgSM);
381 C.addTransition(State);
385 llvm_unreachable(
"We know of no other possible APIs.");
390 State = State->remove<AllocatedData>(ArgSM);
393 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
394 if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) {
395 const AllocationPair AP = std::make_pair(ArgSM, AS);
396 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
400 C.addTransition(State);
403void MacOSKeychainAPIChecker::checkPostStmt(
const CallExpr *CE,
407 if (!FD || FD->
getKind() != Decl::Function)
410 StringRef funName =
C.getCalleeName(FD);
413 unsigned idx = getTrackedFunctionIndex(funName,
true);
414 if (idx == InvalidIdx)
417 const Expr *ArgExpr = CE->
getArg(FunctionsToTrack[idx].Param);
420 if (isEnclosingFunctionParam(ArgExpr) &&
421 C.getLocationContext()->getParent() ==
nullptr)
437 SymbolRef RetStatusSymbol =
C.getSVal(CE).getAsSymbol();
438 C.getSymbolManager().addSymbolDependency(
V, RetStatusSymbol);
441 State = State->set<AllocatedData>(
V, AllocationState(ArgExpr, idx,
444 C.addTransition(State);
450MacOSKeychainAPIChecker::getAllocationNode(
const ExplodedNode *N,
459 if (!N->
getState()->get<AllocatedData>(Sym))
464 if (NContext == LeakContext ||
473std::unique_ptr<PathSensitiveBugReport>
474MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
476 const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx];
479 llvm::raw_svector_ostream os(sbuf);
480 os <<
"Allocated data is not released: missing a call to '"
481 << FunctionsToTrack[FI.DeallocatorIdx].Name <<
"'.";
487 const ExplodedNode *AllocNode = getAllocationNode(N, AP.first,
C);
492 C.getSourceManager(),
495 auto Report = std::make_unique<PathSensitiveBugReport>(
496 *BT, os.str(), N, LocUsedForUniqueing,
499 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
500 markInteresting(Report.get(), AP);
508 bool Assumption)
const {
509 AllocatedDataTy AMap = State->get<AllocatedData>();
513 auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.
getAsSymbol());
517 if (OpCode != BO_EQ && OpCode != BO_NE)
523 if (
auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {
524 const llvm::APInt &RHS = SIE->getRHS();
525 bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr) ||
526 (OpCode == BO_NE && RHS == NoErr);
528 ErrorIsReturned = !ErrorIsReturned;
530 ReturnSymbol = SIE->getLHS();
534 for (
auto [Sym, AllocState] : AMap) {
535 if (ReturnSymbol == AllocState.Region)
536 State = State->remove<AllocatedData>(Sym);
542void MacOSKeychainAPIChecker::checkDeadSymbols(
SymbolReaper &SR,
545 AllocatedDataTy AMap = State->get<AllocatedData>();
550 AllocationPairVec Errors;
551 for (
const auto &[Sym, AllocState] : AMap) {
556 State = State->remove<AllocatedData>(Sym);
562 Errors.push_back(std::make_pair(Sym, &AllocState));
566 C.addTransition(State);
571 ExplodedNode *N =
C.generateNonFatalErrorNode(
C.getState(), &Tag);
576 for (
const auto &
P : Errors)
577 C.emitReport(generateAllocatedDataNotReleasedReport(
P, N,
C));
580 C.addTransition(State, N);
591 for (
auto I : State->get<AllocatedData>()) {
593 if (Escaped.count(Sym))
594 State = State->remove<AllocatedData>(Sym);
612 if (
const auto *SD = dyn_cast<SymbolDerived>(Sym)) {
613 SymbolRef ParentSym = SD->getParentSymbol();
614 if (Escaped.count(ParentSym))
615 State = State->remove<AllocatedData>(Sym);
622MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
625 const AllocationState *AS = N->
getState()->get<AllocatedData>(Sym);
628 const AllocationState *ASPrev =
638 assert(funDecl &&
"We do not support indirect function calls as of now.");
639 StringRef funName = funDecl->
getName();
642 unsigned Idx = getTrackedFunctionIndex(funName,
true);
643 assert(Idx != InvalidIdx &&
"This should be a call to an allocator.");
644 const Expr *ArgExpr = CE->
getArg(FunctionsToTrack[Idx].Param);
647 return std::make_shared<PathDiagnosticEventPiece>(Pos,
648 "Data is allocated here.");
651void MacOSKeychainAPIChecker::printState(raw_ostream &Out,
654 const char *Sep)
const {
656 AllocatedDataTy AMap = State->get<AllocatedData>();
658 if (!AMap.isEmpty()) {
659 Out << Sep <<
"KeychainAPIChecker :" << NL;
660 for (
SymbolRef Sym : llvm::make_first_range(AMap)) {
671bool ento::shouldRegisterMacOSKeychainAPIChecker(
const CheckerManager &mgr) {
static bool isBadDeallocationArgument(const MemRegion *Arg)
static SymbolRef getAsPointeeSymbol(const Expr *Expr, CheckerContext &C)
Given the address expression, retrieve the value it's pointing to.
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
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.
Represents a function declaration or definition.
It wraps the AnalysisDeclContext to represent both the call stack with the help of StackFrameContext ...
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.
const Stmt * getStmt() const
Stmt - This represents one statement.
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
BugReporterVisitors are used to add custom diagnostics along a path.
Represents an abstract call to a function or method along a particular path.
virtual void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const
See CheckerManager::runCheckersForPrintState.
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
Tag that can use a checker name as a message provider (see SimpleProgramPointTag).
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.
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
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
A class responsible for cleaning up unused symbols.
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)