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 const BugType BT{
this,
"Improper use of SecKeychain API",
42 struct AllocationState {
44 unsigned int AllocatorIdx;
52 return (AllocatorIdx ==
X.AllocatorIdx &&
56 void Profile(llvm::FoldingSetNodeID &ID)
const {
57 ID.AddInteger(AllocatorIdx);
58 ID.AddPointer(Region);
70 bool Assumption)
const;
72 const char *NL,
const char *Sep)
const override;
75 typedef std::pair<SymbolRef, const AllocationState*> AllocationPair;
89 struct ADFunctionInfo {
92 unsigned int DeallocatorIdx;
95 static const unsigned InvalidIdx = 100000;
96 static const unsigned FunctionsToTrackSize = 8;
97 static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize];
99 static const unsigned NoErr = 0;
103 static unsigned getTrackedFunctionIndex(StringRef Name,
bool IsAllocator);
105 void generateDeallocatorMismatchReport(
const AllocationPair &AP,
113 std::unique_ptr<PathSensitiveBugReport>
114 generateAllocatedDataNotReleasedReport(
const AllocationPair &AP,
120 const AllocationPair &AP)
const {
134 SecKeychainBugVisitor(
SymbolRef S) : Sym(S) {}
136 void Profile(llvm::FoldingSetNodeID &ID)
const override {
154 MacOSKeychainAPIChecker::AllocationState)
156static bool isEnclosingFunctionParam(
const Expr *
E) {
158 if (
const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(
E)) {
160 if (isa<ImplicitParamDecl, ParmVarDecl>(VD))
166const MacOSKeychainAPIChecker::ADFunctionInfo
167 MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = {
168 {
"SecKeychainItemCopyContent", 4, 3, ValidAPI},
169 {
"SecKeychainFindGenericPassword", 6, 3, ValidAPI},
170 {
"SecKeychainFindInternetPassword", 13, 3, ValidAPI},
171 {
"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI},
172 {
"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI},
173 {
"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI},
174 {
"free", 0, InvalidIdx, ErrorAPI},
175 {
"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI},
178unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
180 for (
unsigned I = 0; I < FunctionsToTrackSize; ++I) {
181 ADFunctionInfo FI = FunctionsToTrack[I];
185 if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
187 if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
199 return isa<AllocaRegion, BlockDataRegion, TypedRegion>(Arg);
211 SymbolRef sym =
SM.getBinding(State->getStore(), *
X).getAsLocSymbol();
220void MacOSKeychainAPIChecker::
221 generateDeallocatorMismatchReport(
const AllocationPair &AP,
225 State = State->remove<AllocatedData>(AP.first);
231 llvm::raw_svector_ostream os(sbuf);
232 unsigned int PDeallocIdx =
233 FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx;
235 os <<
"Deallocator doesn't match the allocator: '"
236 << FunctionsToTrack[PDeallocIdx].Name <<
"' should be used.";
237 auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
238 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
240 markInteresting(Report.get(), AP);
241 C.emitReport(std::move(Report));
244void MacOSKeychainAPIChecker::checkPreStmt(
const CallExpr *CE,
246 unsigned idx = InvalidIdx;
250 if (!FD || FD->
getKind() != Decl::Function)
253 StringRef funName =
C.getCalleeName(FD);
258 idx = getTrackedFunctionIndex(funName,
true);
259 if (idx != InvalidIdx) {
260 unsigned paramIdx = FunctionsToTrack[idx].Param;
266 if (
const AllocationState *AS = State->get<AllocatedData>(
V)) {
269 State = State->remove<AllocatedData>(
V);
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
280 auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
281 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(
V));
283 Report->markInteresting(AS->Region);
284 C.emitReport(std::move(Report));
290 idx = getTrackedFunctionIndex(funName,
false);
291 if (idx == InvalidIdx)
294 unsigned paramIdx = FunctionsToTrack[idx].Param;
300 SVal ArgSVal =
C.getSVal(ArgExpr);
310 bool RegionArgIsBad =
false;
314 RegionArgIsBad =
true;
318 const AllocationState *AS = State->get<AllocatedData>(ArgSM);
324 if (RegionArgIsBad) {
327 if (isEnclosingFunctionParam(ArgExpr))
333 auto Report = std::make_unique<PathSensitiveBugReport>(
334 BT,
"Trying to free data which has not been allocated.", N);
337 Report->markInteresting(AS->Region);
338 C.emitReport(std::move(Report));
343 if (FunctionsToTrack[idx].Kind == PossibleAPI) {
345 if (funName ==
"CFStringCreateWithBytesNoCopy") {
350 const AllocationPair AP = std::make_pair(ArgSM, AS);
351 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
355 if (
const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) {
356 StringRef DeallocatorName = DE->getFoundDecl()->getName();
357 if (DeallocatorName ==
"kCFAllocatorDefault" ||
358 DeallocatorName ==
"kCFAllocatorSystemDefault" ||
359 DeallocatorName ==
"kCFAllocatorMalloc") {
360 const AllocationPair AP = std::make_pair(ArgSM, AS);
361 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
366 if (DE->getFoundDecl()->getName() ==
"kCFAllocatorNull")
371 State = State->remove<AllocatedData>(ArgSM);
372 C.addTransition(State);
376 llvm_unreachable(
"We know of no other possible APIs.");
381 State = State->remove<AllocatedData>(ArgSM);
384 unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
385 if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) {
386 const AllocationPair AP = std::make_pair(ArgSM, AS);
387 generateDeallocatorMismatchReport(AP, ArgExpr,
C);
391 C.addTransition(State);
394void MacOSKeychainAPIChecker::checkPostStmt(
const CallExpr *CE,
398 if (!FD || FD->
getKind() != Decl::Function)
401 StringRef funName =
C.getCalleeName(FD);
404 unsigned idx = getTrackedFunctionIndex(funName,
true);
405 if (idx == InvalidIdx)
408 const Expr *ArgExpr = CE->
getArg(FunctionsToTrack[idx].Param);
411 if (isEnclosingFunctionParam(ArgExpr) &&
412 C.getLocationContext()->getParent() ==
nullptr)
428 SymbolRef RetStatusSymbol =
C.getSVal(CE).getAsSymbol();
429 C.getSymbolManager().addSymbolDependency(
V, RetStatusSymbol);
432 State = State->set<AllocatedData>(
V, AllocationState(ArgExpr, idx,
435 C.addTransition(State);
441MacOSKeychainAPIChecker::getAllocationNode(
const ExplodedNode *N,
450 if (!N->
getState()->get<AllocatedData>(Sym))
455 if (NContext == LeakContext ||
464std::unique_ptr<PathSensitiveBugReport>
465MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
467 const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx];
469 llvm::raw_svector_ostream os(sbuf);
470 os <<
"Allocated data is not released: missing a call to '"
471 << FunctionsToTrack[FI.DeallocatorIdx].Name <<
"'.";
477 const ExplodedNode *AllocNode = getAllocationNode(N, AP.first,
C);
482 C.getSourceManager(),
485 auto Report = std::make_unique<PathSensitiveBugReport>(
486 BT, os.str(), N, LocUsedForUniqueing,
489 Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
490 markInteresting(Report.get(), AP);
498 bool Assumption)
const {
499 AllocatedDataTy AMap = State->get<AllocatedData>();
503 auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.
getAsSymbol());
507 if (OpCode != BO_EQ && OpCode != BO_NE)
513 if (
auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {
514 const llvm::APInt &RHS = SIE->getRHS();
515 bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr) ||
516 (OpCode == BO_NE && RHS == NoErr);
518 ErrorIsReturned = !ErrorIsReturned;
520 ReturnSymbol = SIE->getLHS();
524 for (
auto [Sym, AllocState] : AMap) {
525 if (ReturnSymbol == AllocState.Region)
526 State = State->remove<AllocatedData>(Sym);
532void MacOSKeychainAPIChecker::checkDeadSymbols(
SymbolReaper &SR,
535 AllocatedDataTy AMap = State->get<AllocatedData>();
540 AllocationPairVec Errors;
541 for (
const auto &[Sym, AllocState] : AMap) {
546 State = State->remove<AllocatedData>(Sym);
552 Errors.push_back(std::make_pair(Sym, &AllocState));
556 C.addTransition(State);
561 ExplodedNode *N =
C.generateNonFatalErrorNode(
C.getState(), &Tag);
566 for (
const auto &
P : Errors)
567 C.emitReport(generateAllocatedDataNotReleasedReport(
P, N,
C));
570 C.addTransition(State, N);
581 for (
auto I : State->get<AllocatedData>()) {
583 if (Escaped.count(Sym))
584 State = State->remove<AllocatedData>(Sym);
602 if (
const auto *SD = dyn_cast<SymbolDerived>(Sym)) {
603 SymbolRef ParentSym = SD->getParentSymbol();
604 if (Escaped.count(ParentSym))
605 State = State->remove<AllocatedData>(Sym);
612MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
615 const AllocationState *AS = N->
getState()->get<AllocatedData>(Sym);
618 const AllocationState *ASPrev =
628 assert(funDecl &&
"We do not support indirect function calls as of now.");
629 StringRef funName = funDecl->
getName();
632 unsigned Idx = getTrackedFunctionIndex(funName,
true);
633 assert(Idx != InvalidIdx &&
"This should be a call to an allocator.");
634 const Expr *ArgExpr = CE->
getArg(FunctionsToTrack[Idx].Param);
637 return std::make_shared<PathDiagnosticEventPiece>(Pos,
638 "Data is allocated here.");
641void MacOSKeychainAPIChecker::printState(raw_ostream &Out,
644 const char *Sep)
const {
646 AllocatedDataTy AMap = State->get<AllocatedData>();
648 if (!AMap.isEmpty()) {
649 Out << Sep <<
"KeychainAPIChecker :" << NL;
650 for (
SymbolRef Sym : llvm::make_first_range(AMap)) {
661bool 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.
const char *const AppleAPIMisuse
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
llvm::DenseSet< SymbolRef > InvalidatedSymbols
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
The JSON file list parser is used to communicate input to InstallAPI.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)