17330f729Sjoerg //==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg // This checker flags misuses of KeyChainAPI. In particular, the password data
97330f729Sjoerg // allocated/returned by SecKeychainItemCopyContent,
107330f729Sjoerg // SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has
117330f729Sjoerg // to be freed using a call to SecKeychainItemFreeContent.
127330f729Sjoerg //===----------------------------------------------------------------------===//
137330f729Sjoerg
147330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
157330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
167330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
177330f729Sjoerg #include "clang/StaticAnalyzer/Core/CheckerManager.h"
187330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
197330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
227330f729Sjoerg #include "llvm/ADT/SmallString.h"
237330f729Sjoerg #include "llvm/Support/raw_ostream.h"
247330f729Sjoerg
257330f729Sjoerg using namespace clang;
267330f729Sjoerg using namespace ento;
277330f729Sjoerg
287330f729Sjoerg namespace {
297330f729Sjoerg class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
307330f729Sjoerg check::PostStmt<CallExpr>,
317330f729Sjoerg check::DeadSymbols,
327330f729Sjoerg check::PointerEscape,
337330f729Sjoerg eval::Assume> {
347330f729Sjoerg mutable std::unique_ptr<BugType> BT;
357330f729Sjoerg
367330f729Sjoerg public:
377330f729Sjoerg /// AllocationState is a part of the checker specific state together with the
387330f729Sjoerg /// MemRegion corresponding to the allocated data.
397330f729Sjoerg struct AllocationState {
407330f729Sjoerg /// The index of the allocator function.
417330f729Sjoerg unsigned int AllocatorIdx;
427330f729Sjoerg SymbolRef Region;
437330f729Sjoerg
AllocationState__anonf1e97ce70111::MacOSKeychainAPIChecker::AllocationState447330f729Sjoerg AllocationState(const Expr *E, unsigned int Idx, SymbolRef R) :
457330f729Sjoerg AllocatorIdx(Idx),
467330f729Sjoerg Region(R) {}
477330f729Sjoerg
operator ==__anonf1e97ce70111::MacOSKeychainAPIChecker::AllocationState487330f729Sjoerg bool operator==(const AllocationState &X) const {
497330f729Sjoerg return (AllocatorIdx == X.AllocatorIdx &&
507330f729Sjoerg Region == X.Region);
517330f729Sjoerg }
527330f729Sjoerg
Profile__anonf1e97ce70111::MacOSKeychainAPIChecker::AllocationState537330f729Sjoerg void Profile(llvm::FoldingSetNodeID &ID) const {
547330f729Sjoerg ID.AddInteger(AllocatorIdx);
557330f729Sjoerg ID.AddPointer(Region);
567330f729Sjoerg }
577330f729Sjoerg };
587330f729Sjoerg
597330f729Sjoerg void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
607330f729Sjoerg void checkPostStmt(const CallExpr *S, CheckerContext &C) const;
617330f729Sjoerg void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
627330f729Sjoerg ProgramStateRef checkPointerEscape(ProgramStateRef State,
637330f729Sjoerg const InvalidatedSymbols &Escaped,
647330f729Sjoerg const CallEvent *Call,
657330f729Sjoerg PointerEscapeKind Kind) const;
667330f729Sjoerg ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
677330f729Sjoerg bool Assumption) const;
687330f729Sjoerg void printState(raw_ostream &Out, ProgramStateRef State,
69*e038c9c4Sjoerg const char *NL, const char *Sep) const override;
707330f729Sjoerg
717330f729Sjoerg private:
727330f729Sjoerg typedef std::pair<SymbolRef, const AllocationState*> AllocationPair;
737330f729Sjoerg typedef SmallVector<AllocationPair, 2> AllocationPairVec;
747330f729Sjoerg
757330f729Sjoerg enum APIKind {
767330f729Sjoerg /// Denotes functions tracked by this checker.
777330f729Sjoerg ValidAPI = 0,
787330f729Sjoerg /// The functions commonly/mistakenly used in place of the given API.
797330f729Sjoerg ErrorAPI = 1,
807330f729Sjoerg /// The functions which may allocate the data. These are tracked to reduce
817330f729Sjoerg /// the false alarm rate.
827330f729Sjoerg PossibleAPI = 2
837330f729Sjoerg };
847330f729Sjoerg /// Stores the information about the allocator and deallocator functions -
857330f729Sjoerg /// these are the functions the checker is tracking.
867330f729Sjoerg struct ADFunctionInfo {
877330f729Sjoerg const char* Name;
887330f729Sjoerg unsigned int Param;
897330f729Sjoerg unsigned int DeallocatorIdx;
907330f729Sjoerg APIKind Kind;
917330f729Sjoerg };
927330f729Sjoerg static const unsigned InvalidIdx = 100000;
937330f729Sjoerg static const unsigned FunctionsToTrackSize = 8;
947330f729Sjoerg static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize];
957330f729Sjoerg /// The value, which represents no error return value for allocator functions.
967330f729Sjoerg static const unsigned NoErr = 0;
977330f729Sjoerg
987330f729Sjoerg /// Given the function name, returns the index of the allocator/deallocator
997330f729Sjoerg /// function.
1007330f729Sjoerg static unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator);
1017330f729Sjoerg
initBugType() const1027330f729Sjoerg inline void initBugType() const {
1037330f729Sjoerg if (!BT)
1047330f729Sjoerg BT.reset(new BugType(this, "Improper use of SecKeychain API",
1057330f729Sjoerg "API Misuse (Apple)"));
1067330f729Sjoerg }
1077330f729Sjoerg
1087330f729Sjoerg void generateDeallocatorMismatchReport(const AllocationPair &AP,
1097330f729Sjoerg const Expr *ArgExpr,
1107330f729Sjoerg CheckerContext &C) const;
1117330f729Sjoerg
1127330f729Sjoerg /// Find the allocation site for Sym on the path leading to the node N.
1137330f729Sjoerg const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym,
1147330f729Sjoerg CheckerContext &C) const;
1157330f729Sjoerg
1167330f729Sjoerg std::unique_ptr<PathSensitiveBugReport>
1177330f729Sjoerg generateAllocatedDataNotReleasedReport(const AllocationPair &AP,
1187330f729Sjoerg ExplodedNode *N,
1197330f729Sjoerg CheckerContext &C) const;
1207330f729Sjoerg
1217330f729Sjoerg /// Mark an AllocationPair interesting for diagnostic reporting.
markInteresting(PathSensitiveBugReport * R,const AllocationPair & AP) const1227330f729Sjoerg void markInteresting(PathSensitiveBugReport *R,
1237330f729Sjoerg const AllocationPair &AP) const {
1247330f729Sjoerg R->markInteresting(AP.first);
1257330f729Sjoerg R->markInteresting(AP.second->Region);
1267330f729Sjoerg }
1277330f729Sjoerg
1287330f729Sjoerg /// The bug visitor which allows us to print extra diagnostics along the
1297330f729Sjoerg /// BugReport path. For example, showing the allocation site of the leaked
1307330f729Sjoerg /// region.
1317330f729Sjoerg class SecKeychainBugVisitor : public BugReporterVisitor {
1327330f729Sjoerg protected:
1337330f729Sjoerg // The allocated region symbol tracked by the main analysis.
1347330f729Sjoerg SymbolRef Sym;
1357330f729Sjoerg
1367330f729Sjoerg public:
SecKeychainBugVisitor(SymbolRef S)1377330f729Sjoerg SecKeychainBugVisitor(SymbolRef S) : Sym(S) {}
1387330f729Sjoerg
Profile(llvm::FoldingSetNodeID & ID) const1397330f729Sjoerg void Profile(llvm::FoldingSetNodeID &ID) const override {
1407330f729Sjoerg static int X = 0;
1417330f729Sjoerg ID.AddPointer(&X);
1427330f729Sjoerg ID.AddPointer(Sym);
1437330f729Sjoerg }
1447330f729Sjoerg
1457330f729Sjoerg PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
1467330f729Sjoerg BugReporterContext &BRC,
1477330f729Sjoerg PathSensitiveBugReport &BR) override;
1487330f729Sjoerg };
1497330f729Sjoerg };
1507330f729Sjoerg }
1517330f729Sjoerg
1527330f729Sjoerg /// ProgramState traits to store the currently allocated (and not yet freed)
1537330f729Sjoerg /// symbols. This is a map from the allocated content symbol to the
1547330f729Sjoerg /// corresponding AllocationState.
REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData,SymbolRef,MacOSKeychainAPIChecker::AllocationState)1557330f729Sjoerg REGISTER_MAP_WITH_PROGRAMSTATE(AllocatedData,
1567330f729Sjoerg SymbolRef,
1577330f729Sjoerg MacOSKeychainAPIChecker::AllocationState)
1587330f729Sjoerg
1597330f729Sjoerg static bool isEnclosingFunctionParam(const Expr *E) {
1607330f729Sjoerg E = E->IgnoreParenCasts();
1617330f729Sjoerg if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) {
1627330f729Sjoerg const ValueDecl *VD = DRE->getDecl();
1637330f729Sjoerg if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD))
1647330f729Sjoerg return true;
1657330f729Sjoerg }
1667330f729Sjoerg return false;
1677330f729Sjoerg }
1687330f729Sjoerg
1697330f729Sjoerg const MacOSKeychainAPIChecker::ADFunctionInfo
1707330f729Sjoerg MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = {
1717330f729Sjoerg {"SecKeychainItemCopyContent", 4, 3, ValidAPI}, // 0
1727330f729Sjoerg {"SecKeychainFindGenericPassword", 6, 3, ValidAPI}, // 1
1737330f729Sjoerg {"SecKeychainFindInternetPassword", 13, 3, ValidAPI}, // 2
1747330f729Sjoerg {"SecKeychainItemFreeContent", 1, InvalidIdx, ValidAPI}, // 3
1757330f729Sjoerg {"SecKeychainItemCopyAttributesAndData", 5, 5, ValidAPI}, // 4
1767330f729Sjoerg {"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx, ValidAPI}, // 5
1777330f729Sjoerg {"free", 0, InvalidIdx, ErrorAPI}, // 6
1787330f729Sjoerg {"CFStringCreateWithBytesNoCopy", 1, InvalidIdx, PossibleAPI}, // 7
1797330f729Sjoerg };
1807330f729Sjoerg
getTrackedFunctionIndex(StringRef Name,bool IsAllocator)1817330f729Sjoerg unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
1827330f729Sjoerg bool IsAllocator) {
1837330f729Sjoerg for (unsigned I = 0; I < FunctionsToTrackSize; ++I) {
1847330f729Sjoerg ADFunctionInfo FI = FunctionsToTrack[I];
1857330f729Sjoerg if (FI.Name != Name)
1867330f729Sjoerg continue;
1877330f729Sjoerg // Make sure the function is of the right type (allocator vs deallocator).
1887330f729Sjoerg if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
1897330f729Sjoerg return InvalidIdx;
1907330f729Sjoerg if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
1917330f729Sjoerg return InvalidIdx;
1927330f729Sjoerg
1937330f729Sjoerg return I;
1947330f729Sjoerg }
1957330f729Sjoerg // The function is not tracked.
1967330f729Sjoerg return InvalidIdx;
1977330f729Sjoerg }
1987330f729Sjoerg
isBadDeallocationArgument(const MemRegion * Arg)1997330f729Sjoerg static bool isBadDeallocationArgument(const MemRegion *Arg) {
2007330f729Sjoerg if (!Arg)
2017330f729Sjoerg return false;
2027330f729Sjoerg return isa<AllocaRegion>(Arg) || isa<BlockDataRegion>(Arg) ||
2037330f729Sjoerg isa<TypedRegion>(Arg);
2047330f729Sjoerg }
2057330f729Sjoerg
2067330f729Sjoerg /// Given the address expression, retrieve the value it's pointing to. Assume
2077330f729Sjoerg /// that value is itself an address, and return the corresponding symbol.
getAsPointeeSymbol(const Expr * Expr,CheckerContext & C)2087330f729Sjoerg static SymbolRef getAsPointeeSymbol(const Expr *Expr,
2097330f729Sjoerg CheckerContext &C) {
2107330f729Sjoerg ProgramStateRef State = C.getState();
2117330f729Sjoerg SVal ArgV = C.getSVal(Expr);
2127330f729Sjoerg
2137330f729Sjoerg if (Optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) {
2147330f729Sjoerg StoreManager& SM = C.getStoreManager();
2157330f729Sjoerg SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol();
2167330f729Sjoerg if (sym)
2177330f729Sjoerg return sym;
2187330f729Sjoerg }
2197330f729Sjoerg return nullptr;
2207330f729Sjoerg }
2217330f729Sjoerg
2227330f729Sjoerg // Report deallocator mismatch. Remove the region from tracking - reporting a
2237330f729Sjoerg // missing free error after this one is redundant.
2247330f729Sjoerg void MacOSKeychainAPIChecker::
generateDeallocatorMismatchReport(const AllocationPair & AP,const Expr * ArgExpr,CheckerContext & C) const2257330f729Sjoerg generateDeallocatorMismatchReport(const AllocationPair &AP,
2267330f729Sjoerg const Expr *ArgExpr,
2277330f729Sjoerg CheckerContext &C) const {
2287330f729Sjoerg ProgramStateRef State = C.getState();
2297330f729Sjoerg State = State->remove<AllocatedData>(AP.first);
2307330f729Sjoerg ExplodedNode *N = C.generateNonFatalErrorNode(State);
2317330f729Sjoerg
2327330f729Sjoerg if (!N)
2337330f729Sjoerg return;
2347330f729Sjoerg initBugType();
2357330f729Sjoerg SmallString<80> sbuf;
2367330f729Sjoerg llvm::raw_svector_ostream os(sbuf);
2377330f729Sjoerg unsigned int PDeallocIdx =
2387330f729Sjoerg FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx;
2397330f729Sjoerg
2407330f729Sjoerg os << "Deallocator doesn't match the allocator: '"
2417330f729Sjoerg << FunctionsToTrack[PDeallocIdx].Name << "' should be used.";
2427330f729Sjoerg auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
2437330f729Sjoerg Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
2447330f729Sjoerg Report->addRange(ArgExpr->getSourceRange());
2457330f729Sjoerg markInteresting(Report.get(), AP);
2467330f729Sjoerg C.emitReport(std::move(Report));
2477330f729Sjoerg }
2487330f729Sjoerg
checkPreStmt(const CallExpr * CE,CheckerContext & C) const2497330f729Sjoerg void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
2507330f729Sjoerg CheckerContext &C) const {
2517330f729Sjoerg unsigned idx = InvalidIdx;
2527330f729Sjoerg ProgramStateRef State = C.getState();
2537330f729Sjoerg
2547330f729Sjoerg const FunctionDecl *FD = C.getCalleeDecl(CE);
2557330f729Sjoerg if (!FD || FD->getKind() != Decl::Function)
2567330f729Sjoerg return;
2577330f729Sjoerg
2587330f729Sjoerg StringRef funName = C.getCalleeName(FD);
2597330f729Sjoerg if (funName.empty())
2607330f729Sjoerg return;
2617330f729Sjoerg
2627330f729Sjoerg // If it is a call to an allocator function, it could be a double allocation.
2637330f729Sjoerg idx = getTrackedFunctionIndex(funName, true);
2647330f729Sjoerg if (idx != InvalidIdx) {
2657330f729Sjoerg unsigned paramIdx = FunctionsToTrack[idx].Param;
2667330f729Sjoerg if (CE->getNumArgs() <= paramIdx)
2677330f729Sjoerg return;
2687330f729Sjoerg
2697330f729Sjoerg const Expr *ArgExpr = CE->getArg(paramIdx);
2707330f729Sjoerg if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C))
2717330f729Sjoerg if (const AllocationState *AS = State->get<AllocatedData>(V)) {
2727330f729Sjoerg // Remove the value from the state. The new symbol will be added for
2737330f729Sjoerg // tracking when the second allocator is processed in checkPostStmt().
2747330f729Sjoerg State = State->remove<AllocatedData>(V);
2757330f729Sjoerg ExplodedNode *N = C.generateNonFatalErrorNode(State);
2767330f729Sjoerg if (!N)
2777330f729Sjoerg return;
2787330f729Sjoerg initBugType();
2797330f729Sjoerg SmallString<128> sbuf;
2807330f729Sjoerg llvm::raw_svector_ostream os(sbuf);
2817330f729Sjoerg unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
2827330f729Sjoerg os << "Allocated data should be released before another call to "
2837330f729Sjoerg << "the allocator: missing a call to '"
2847330f729Sjoerg << FunctionsToTrack[DIdx].Name
2857330f729Sjoerg << "'.";
2867330f729Sjoerg auto Report =
2877330f729Sjoerg std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
2887330f729Sjoerg Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(V));
2897330f729Sjoerg Report->addRange(ArgExpr->getSourceRange());
2907330f729Sjoerg Report->markInteresting(AS->Region);
2917330f729Sjoerg C.emitReport(std::move(Report));
2927330f729Sjoerg }
2937330f729Sjoerg return;
2947330f729Sjoerg }
2957330f729Sjoerg
2967330f729Sjoerg // Is it a call to one of deallocator functions?
2977330f729Sjoerg idx = getTrackedFunctionIndex(funName, false);
2987330f729Sjoerg if (idx == InvalidIdx)
2997330f729Sjoerg return;
3007330f729Sjoerg
3017330f729Sjoerg unsigned paramIdx = FunctionsToTrack[idx].Param;
3027330f729Sjoerg if (CE->getNumArgs() <= paramIdx)
3037330f729Sjoerg return;
3047330f729Sjoerg
3057330f729Sjoerg // Check the argument to the deallocator.
3067330f729Sjoerg const Expr *ArgExpr = CE->getArg(paramIdx);
3077330f729Sjoerg SVal ArgSVal = C.getSVal(ArgExpr);
3087330f729Sjoerg
3097330f729Sjoerg // Undef is reported by another checker.
3107330f729Sjoerg if (ArgSVal.isUndef())
3117330f729Sjoerg return;
3127330f729Sjoerg
3137330f729Sjoerg SymbolRef ArgSM = ArgSVal.getAsLocSymbol();
3147330f729Sjoerg
3157330f729Sjoerg // If the argument is coming from the heap, globals, or unknown, do not
3167330f729Sjoerg // report it.
3177330f729Sjoerg bool RegionArgIsBad = false;
3187330f729Sjoerg if (!ArgSM) {
3197330f729Sjoerg if (!isBadDeallocationArgument(ArgSVal.getAsRegion()))
3207330f729Sjoerg return;
3217330f729Sjoerg RegionArgIsBad = true;
3227330f729Sjoerg }
3237330f729Sjoerg
3247330f729Sjoerg // Is the argument to the call being tracked?
3257330f729Sjoerg const AllocationState *AS = State->get<AllocatedData>(ArgSM);
3267330f729Sjoerg if (!AS)
3277330f729Sjoerg return;
3287330f729Sjoerg
3297330f729Sjoerg // TODO: We might want to report double free here.
3307330f729Sjoerg // (that would involve tracking all the freed symbols in the checker state).
3317330f729Sjoerg if (RegionArgIsBad) {
3327330f729Sjoerg // It is possible that this is a false positive - the argument might
3337330f729Sjoerg // have entered as an enclosing function parameter.
3347330f729Sjoerg if (isEnclosingFunctionParam(ArgExpr))
3357330f729Sjoerg return;
3367330f729Sjoerg
3377330f729Sjoerg ExplodedNode *N = C.generateNonFatalErrorNode(State);
3387330f729Sjoerg if (!N)
3397330f729Sjoerg return;
3407330f729Sjoerg initBugType();
3417330f729Sjoerg auto Report = std::make_unique<PathSensitiveBugReport>(
3427330f729Sjoerg *BT, "Trying to free data which has not been allocated.", N);
3437330f729Sjoerg Report->addRange(ArgExpr->getSourceRange());
3447330f729Sjoerg if (AS)
3457330f729Sjoerg Report->markInteresting(AS->Region);
3467330f729Sjoerg C.emitReport(std::move(Report));
3477330f729Sjoerg return;
3487330f729Sjoerg }
3497330f729Sjoerg
3507330f729Sjoerg // Process functions which might deallocate.
3517330f729Sjoerg if (FunctionsToTrack[idx].Kind == PossibleAPI) {
3527330f729Sjoerg
3537330f729Sjoerg if (funName == "CFStringCreateWithBytesNoCopy") {
3547330f729Sjoerg const Expr *DeallocatorExpr = CE->getArg(5)->IgnoreParenCasts();
3557330f729Sjoerg // NULL ~ default deallocator, so warn.
3567330f729Sjoerg if (DeallocatorExpr->isNullPointerConstant(C.getASTContext(),
3577330f729Sjoerg Expr::NPC_ValueDependentIsNotNull)) {
3587330f729Sjoerg const AllocationPair AP = std::make_pair(ArgSM, AS);
3597330f729Sjoerg generateDeallocatorMismatchReport(AP, ArgExpr, C);
3607330f729Sjoerg return;
3617330f729Sjoerg }
3627330f729Sjoerg // One of the default allocators, so warn.
3637330f729Sjoerg if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(DeallocatorExpr)) {
3647330f729Sjoerg StringRef DeallocatorName = DE->getFoundDecl()->getName();
3657330f729Sjoerg if (DeallocatorName == "kCFAllocatorDefault" ||
3667330f729Sjoerg DeallocatorName == "kCFAllocatorSystemDefault" ||
3677330f729Sjoerg DeallocatorName == "kCFAllocatorMalloc") {
3687330f729Sjoerg const AllocationPair AP = std::make_pair(ArgSM, AS);
3697330f729Sjoerg generateDeallocatorMismatchReport(AP, ArgExpr, C);
3707330f729Sjoerg return;
3717330f729Sjoerg }
3727330f729Sjoerg // If kCFAllocatorNull, which does not deallocate, we still have to
3737330f729Sjoerg // find the deallocator.
3747330f729Sjoerg if (DE->getFoundDecl()->getName() == "kCFAllocatorNull")
3757330f729Sjoerg return;
3767330f729Sjoerg }
3777330f729Sjoerg // In all other cases, assume the user supplied a correct deallocator
3787330f729Sjoerg // that will free memory so stop tracking.
3797330f729Sjoerg State = State->remove<AllocatedData>(ArgSM);
3807330f729Sjoerg C.addTransition(State);
3817330f729Sjoerg return;
3827330f729Sjoerg }
3837330f729Sjoerg
3847330f729Sjoerg llvm_unreachable("We know of no other possible APIs.");
3857330f729Sjoerg }
3867330f729Sjoerg
3877330f729Sjoerg // The call is deallocating a value we previously allocated, so remove it
3887330f729Sjoerg // from the next state.
3897330f729Sjoerg State = State->remove<AllocatedData>(ArgSM);
3907330f729Sjoerg
3917330f729Sjoerg // Check if the proper deallocator is used.
3927330f729Sjoerg unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
3937330f729Sjoerg if (PDeallocIdx != idx || (FunctionsToTrack[idx].Kind == ErrorAPI)) {
3947330f729Sjoerg const AllocationPair AP = std::make_pair(ArgSM, AS);
3957330f729Sjoerg generateDeallocatorMismatchReport(AP, ArgExpr, C);
3967330f729Sjoerg return;
3977330f729Sjoerg }
3987330f729Sjoerg
3997330f729Sjoerg C.addTransition(State);
4007330f729Sjoerg }
4017330f729Sjoerg
checkPostStmt(const CallExpr * CE,CheckerContext & C) const4027330f729Sjoerg void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE,
4037330f729Sjoerg CheckerContext &C) const {
4047330f729Sjoerg ProgramStateRef State = C.getState();
4057330f729Sjoerg const FunctionDecl *FD = C.getCalleeDecl(CE);
4067330f729Sjoerg if (!FD || FD->getKind() != Decl::Function)
4077330f729Sjoerg return;
4087330f729Sjoerg
4097330f729Sjoerg StringRef funName = C.getCalleeName(FD);
4107330f729Sjoerg
4117330f729Sjoerg // If a value has been allocated, add it to the set for tracking.
4127330f729Sjoerg unsigned idx = getTrackedFunctionIndex(funName, true);
4137330f729Sjoerg if (idx == InvalidIdx)
4147330f729Sjoerg return;
4157330f729Sjoerg
4167330f729Sjoerg const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
4177330f729Sjoerg // If the argument entered as an enclosing function parameter, skip it to
4187330f729Sjoerg // avoid false positives.
4197330f729Sjoerg if (isEnclosingFunctionParam(ArgExpr) &&
4207330f729Sjoerg C.getLocationContext()->getParent() == nullptr)
4217330f729Sjoerg return;
4227330f729Sjoerg
4237330f729Sjoerg if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) {
4247330f729Sjoerg // If the argument points to something that's not a symbolic region, it
4257330f729Sjoerg // can be:
4267330f729Sjoerg // - unknown (cannot reason about it)
4277330f729Sjoerg // - undefined (already reported by other checker)
4287330f729Sjoerg // - constant (null - should not be tracked,
4297330f729Sjoerg // other constant will generate a compiler warning)
4307330f729Sjoerg // - goto (should be reported by other checker)
4317330f729Sjoerg
4327330f729Sjoerg // The call return value symbol should stay alive for as long as the
4337330f729Sjoerg // allocated value symbol, since our diagnostics depend on the value
4347330f729Sjoerg // returned by the call. Ex: Data should only be freed if noErr was
4357330f729Sjoerg // returned during allocation.)
4367330f729Sjoerg SymbolRef RetStatusSymbol = C.getSVal(CE).getAsSymbol();
4377330f729Sjoerg C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol);
4387330f729Sjoerg
4397330f729Sjoerg // Track the allocated value in the checker state.
4407330f729Sjoerg State = State->set<AllocatedData>(V, AllocationState(ArgExpr, idx,
4417330f729Sjoerg RetStatusSymbol));
4427330f729Sjoerg assert(State);
4437330f729Sjoerg C.addTransition(State);
4447330f729Sjoerg }
4457330f729Sjoerg }
4467330f729Sjoerg
4477330f729Sjoerg // TODO: This logic is the same as in Malloc checker.
4487330f729Sjoerg const ExplodedNode *
getAllocationNode(const ExplodedNode * N,SymbolRef Sym,CheckerContext & C) const4497330f729Sjoerg MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N,
4507330f729Sjoerg SymbolRef Sym,
4517330f729Sjoerg CheckerContext &C) const {
4527330f729Sjoerg const LocationContext *LeakContext = N->getLocationContext();
4537330f729Sjoerg // Walk the ExplodedGraph backwards and find the first node that referred to
4547330f729Sjoerg // the tracked symbol.
4557330f729Sjoerg const ExplodedNode *AllocNode = N;
4567330f729Sjoerg
4577330f729Sjoerg while (N) {
4587330f729Sjoerg if (!N->getState()->get<AllocatedData>(Sym))
4597330f729Sjoerg break;
4607330f729Sjoerg // Allocation node, is the last node in the current or parent context in
4617330f729Sjoerg // which the symbol was tracked.
4627330f729Sjoerg const LocationContext *NContext = N->getLocationContext();
4637330f729Sjoerg if (NContext == LeakContext ||
4647330f729Sjoerg NContext->isParentOf(LeakContext))
4657330f729Sjoerg AllocNode = N;
4667330f729Sjoerg N = N->pred_empty() ? nullptr : *(N->pred_begin());
4677330f729Sjoerg }
4687330f729Sjoerg
4697330f729Sjoerg return AllocNode;
4707330f729Sjoerg }
4717330f729Sjoerg
4727330f729Sjoerg std::unique_ptr<PathSensitiveBugReport>
generateAllocatedDataNotReleasedReport(const AllocationPair & AP,ExplodedNode * N,CheckerContext & C) const4737330f729Sjoerg MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
4747330f729Sjoerg const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const {
4757330f729Sjoerg const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx];
4767330f729Sjoerg initBugType();
4777330f729Sjoerg SmallString<70> sbuf;
4787330f729Sjoerg llvm::raw_svector_ostream os(sbuf);
4797330f729Sjoerg os << "Allocated data is not released: missing a call to '"
4807330f729Sjoerg << FunctionsToTrack[FI.DeallocatorIdx].Name << "'.";
4817330f729Sjoerg
4827330f729Sjoerg // Most bug reports are cached at the location where they occurred.
4837330f729Sjoerg // With leaks, we want to unique them by the location where they were
4847330f729Sjoerg // allocated, and only report a single path.
4857330f729Sjoerg PathDiagnosticLocation LocUsedForUniqueing;
4867330f729Sjoerg const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C);
4877330f729Sjoerg const Stmt *AllocStmt = AllocNode->getStmtForDiagnostics();
4887330f729Sjoerg
4897330f729Sjoerg if (AllocStmt)
4907330f729Sjoerg LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt,
4917330f729Sjoerg C.getSourceManager(),
4927330f729Sjoerg AllocNode->getLocationContext());
4937330f729Sjoerg
4947330f729Sjoerg auto Report = std::make_unique<PathSensitiveBugReport>(
4957330f729Sjoerg *BT, os.str(), N, LocUsedForUniqueing,
4967330f729Sjoerg AllocNode->getLocationContext()->getDecl());
4977330f729Sjoerg
4987330f729Sjoerg Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first));
4997330f729Sjoerg markInteresting(Report.get(), AP);
5007330f729Sjoerg return Report;
5017330f729Sjoerg }
5027330f729Sjoerg
5037330f729Sjoerg /// If the return symbol is assumed to be error, remove the allocated info
5047330f729Sjoerg /// from consideration.
evalAssume(ProgramStateRef State,SVal Cond,bool Assumption) const5057330f729Sjoerg ProgramStateRef MacOSKeychainAPIChecker::evalAssume(ProgramStateRef State,
5067330f729Sjoerg SVal Cond,
5077330f729Sjoerg bool Assumption) const {
5087330f729Sjoerg AllocatedDataTy AMap = State->get<AllocatedData>();
5097330f729Sjoerg if (AMap.isEmpty())
5107330f729Sjoerg return State;
5117330f729Sjoerg
512*e038c9c4Sjoerg auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymbol());
5137330f729Sjoerg if (!CondBSE)
5147330f729Sjoerg return State;
5157330f729Sjoerg BinaryOperator::Opcode OpCode = CondBSE->getOpcode();
5167330f729Sjoerg if (OpCode != BO_EQ && OpCode != BO_NE)
5177330f729Sjoerg return State;
5187330f729Sjoerg
5197330f729Sjoerg // Match for a restricted set of patterns for cmparison of error codes.
5207330f729Sjoerg // Note, the comparisons of type '0 == st' are transformed into SymIntExpr.
5217330f729Sjoerg SymbolRef ReturnSymbol = nullptr;
5227330f729Sjoerg if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {
5237330f729Sjoerg const llvm::APInt &RHS = SIE->getRHS();
5247330f729Sjoerg bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr) ||
5257330f729Sjoerg (OpCode == BO_NE && RHS == NoErr);
5267330f729Sjoerg if (!Assumption)
5277330f729Sjoerg ErrorIsReturned = !ErrorIsReturned;
5287330f729Sjoerg if (ErrorIsReturned)
5297330f729Sjoerg ReturnSymbol = SIE->getLHS();
5307330f729Sjoerg }
5317330f729Sjoerg
5327330f729Sjoerg if (ReturnSymbol)
5337330f729Sjoerg for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
5347330f729Sjoerg if (ReturnSymbol == I->second.Region)
5357330f729Sjoerg State = State->remove<AllocatedData>(I->first);
5367330f729Sjoerg }
5377330f729Sjoerg
5387330f729Sjoerg return State;
5397330f729Sjoerg }
5407330f729Sjoerg
checkDeadSymbols(SymbolReaper & SR,CheckerContext & C) const5417330f729Sjoerg void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR,
5427330f729Sjoerg CheckerContext &C) const {
5437330f729Sjoerg ProgramStateRef State = C.getState();
5447330f729Sjoerg AllocatedDataTy AMap = State->get<AllocatedData>();
5457330f729Sjoerg if (AMap.isEmpty())
5467330f729Sjoerg return;
5477330f729Sjoerg
5487330f729Sjoerg bool Changed = false;
5497330f729Sjoerg AllocationPairVec Errors;
5507330f729Sjoerg for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
5517330f729Sjoerg if (!SR.isDead(I->first))
5527330f729Sjoerg continue;
5537330f729Sjoerg
5547330f729Sjoerg Changed = true;
5557330f729Sjoerg State = State->remove<AllocatedData>(I->first);
5567330f729Sjoerg // If the allocated symbol is null do not report.
5577330f729Sjoerg ConstraintManager &CMgr = State->getConstraintManager();
5587330f729Sjoerg ConditionTruthVal AllocFailed = CMgr.isNull(State, I.getKey());
5597330f729Sjoerg if (AllocFailed.isConstrainedTrue())
5607330f729Sjoerg continue;
5617330f729Sjoerg Errors.push_back(std::make_pair(I->first, &I->second));
5627330f729Sjoerg }
5637330f729Sjoerg if (!Changed) {
5647330f729Sjoerg // Generate the new, cleaned up state.
5657330f729Sjoerg C.addTransition(State);
5667330f729Sjoerg return;
5677330f729Sjoerg }
5687330f729Sjoerg
5697330f729Sjoerg static CheckerProgramPointTag Tag(this, "DeadSymbolsLeak");
5707330f729Sjoerg ExplodedNode *N = C.generateNonFatalErrorNode(C.getState(), &Tag);
5717330f729Sjoerg if (!N)
5727330f729Sjoerg return;
5737330f729Sjoerg
5747330f729Sjoerg // Generate the error reports.
5757330f729Sjoerg for (const auto &P : Errors)
5767330f729Sjoerg C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C));
5777330f729Sjoerg
5787330f729Sjoerg // Generate the new, cleaned up state.
5797330f729Sjoerg C.addTransition(State, N);
5807330f729Sjoerg }
5817330f729Sjoerg
checkPointerEscape(ProgramStateRef State,const InvalidatedSymbols & Escaped,const CallEvent * Call,PointerEscapeKind Kind) const5827330f729Sjoerg ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape(
5837330f729Sjoerg ProgramStateRef State, const InvalidatedSymbols &Escaped,
5847330f729Sjoerg const CallEvent *Call, PointerEscapeKind Kind) const {
5857330f729Sjoerg // FIXME: This branch doesn't make any sense at all, but it is an overfitted
5867330f729Sjoerg // replacement for a previous overfitted code that was making even less sense.
5877330f729Sjoerg if (!Call || Call->getDecl())
5887330f729Sjoerg return State;
5897330f729Sjoerg
5907330f729Sjoerg for (auto I : State->get<AllocatedData>()) {
5917330f729Sjoerg SymbolRef Sym = I.first;
5927330f729Sjoerg if (Escaped.count(Sym))
5937330f729Sjoerg State = State->remove<AllocatedData>(Sym);
5947330f729Sjoerg
5957330f729Sjoerg // This checker is special. Most checkers in fact only track symbols of
5967330f729Sjoerg // SymbolConjured type, eg. symbols returned from functions such as
5977330f729Sjoerg // malloc(). This checker tracks symbols returned as out-parameters.
5987330f729Sjoerg //
5997330f729Sjoerg // When a function is evaluated conservatively, the out-parameter's pointee
6007330f729Sjoerg // base region gets invalidated with a SymbolConjured. If the base region is
6017330f729Sjoerg // larger than the region we're interested in, the value we're interested in
6027330f729Sjoerg // would be SymbolDerived based on that SymbolConjured. However, such
6037330f729Sjoerg // SymbolDerived will never be listed in the Escaped set when the base
6047330f729Sjoerg // region is invalidated because ExprEngine doesn't know which symbols
6057330f729Sjoerg // were derived from a given symbol, while there can be infinitely many
6067330f729Sjoerg // valid symbols derived from any given symbol.
6077330f729Sjoerg //
6087330f729Sjoerg // Hence the extra boilerplate: remove the derived symbol when its parent
6097330f729Sjoerg // symbol escapes.
6107330f729Sjoerg //
6117330f729Sjoerg if (const auto *SD = dyn_cast<SymbolDerived>(Sym)) {
6127330f729Sjoerg SymbolRef ParentSym = SD->getParentSymbol();
6137330f729Sjoerg if (Escaped.count(ParentSym))
6147330f729Sjoerg State = State->remove<AllocatedData>(Sym);
6157330f729Sjoerg }
6167330f729Sjoerg }
6177330f729Sjoerg return State;
6187330f729Sjoerg }
6197330f729Sjoerg
6207330f729Sjoerg PathDiagnosticPieceRef
VisitNode(const ExplodedNode * N,BugReporterContext & BRC,PathSensitiveBugReport & BR)6217330f729Sjoerg MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
6227330f729Sjoerg const ExplodedNode *N, BugReporterContext &BRC,
6237330f729Sjoerg PathSensitiveBugReport &BR) {
6247330f729Sjoerg const AllocationState *AS = N->getState()->get<AllocatedData>(Sym);
6257330f729Sjoerg if (!AS)
6267330f729Sjoerg return nullptr;
6277330f729Sjoerg const AllocationState *ASPrev =
6287330f729Sjoerg N->getFirstPred()->getState()->get<AllocatedData>(Sym);
6297330f729Sjoerg if (ASPrev)
6307330f729Sjoerg return nullptr;
6317330f729Sjoerg
6327330f729Sjoerg // (!ASPrev && AS) ~ We started tracking symbol in node N, it must be the
6337330f729Sjoerg // allocation site.
6347330f729Sjoerg const CallExpr *CE =
6357330f729Sjoerg cast<CallExpr>(N->getLocation().castAs<StmtPoint>().getStmt());
6367330f729Sjoerg const FunctionDecl *funDecl = CE->getDirectCallee();
6377330f729Sjoerg assert(funDecl && "We do not support indirect function calls as of now.");
6387330f729Sjoerg StringRef funName = funDecl->getName();
6397330f729Sjoerg
6407330f729Sjoerg // Get the expression of the corresponding argument.
6417330f729Sjoerg unsigned Idx = getTrackedFunctionIndex(funName, true);
6427330f729Sjoerg assert(Idx != InvalidIdx && "This should be a call to an allocator.");
6437330f729Sjoerg const Expr *ArgExpr = CE->getArg(FunctionsToTrack[Idx].Param);
6447330f729Sjoerg PathDiagnosticLocation Pos(ArgExpr, BRC.getSourceManager(),
6457330f729Sjoerg N->getLocationContext());
6467330f729Sjoerg return std::make_shared<PathDiagnosticEventPiece>(Pos,
6477330f729Sjoerg "Data is allocated here.");
6487330f729Sjoerg }
6497330f729Sjoerg
printState(raw_ostream & Out,ProgramStateRef State,const char * NL,const char * Sep) const6507330f729Sjoerg void MacOSKeychainAPIChecker::printState(raw_ostream &Out,
6517330f729Sjoerg ProgramStateRef State,
6527330f729Sjoerg const char *NL,
6537330f729Sjoerg const char *Sep) const {
6547330f729Sjoerg
6557330f729Sjoerg AllocatedDataTy AMap = State->get<AllocatedData>();
6567330f729Sjoerg
6577330f729Sjoerg if (!AMap.isEmpty()) {
6587330f729Sjoerg Out << Sep << "KeychainAPIChecker :" << NL;
6597330f729Sjoerg for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
6607330f729Sjoerg I.getKey()->dumpToStream(Out);
6617330f729Sjoerg }
6627330f729Sjoerg }
6637330f729Sjoerg }
6647330f729Sjoerg
6657330f729Sjoerg
registerMacOSKeychainAPIChecker(CheckerManager & mgr)6667330f729Sjoerg void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
6677330f729Sjoerg mgr.registerChecker<MacOSKeychainAPIChecker>();
6687330f729Sjoerg }
6697330f729Sjoerg
shouldRegisterMacOSKeychainAPIChecker(const CheckerManager & mgr)670*e038c9c4Sjoerg bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) {
6717330f729Sjoerg return true;
6727330f729Sjoerg }
673