//=== MallocChecker.cpp - A malloc/free checker -------------------*- C++ -*--// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines a variety of memory management related checkers, such as // leak, double free, and use-after-free. // // The following checkers are defined here: // // * MallocChecker // Despite its name, it models all sorts of memory allocations and // de- or reallocation, including but not limited to malloc, free, // relloc, new, delete. It also reports on a variety of memory misuse // errors. // Many other checkers interact very closely with this checker, in fact, // most are merely options to this one. Other checkers may register // MallocChecker, but do not enable MallocChecker's reports (more details // to follow around its field, ChecksEnabled). // It also has a boolean "Optimistic" checker option, which if set to true // will cause the checker to model user defined memory management related // functions annotated via the attribute ownership_takes, ownership_holds // and ownership_returns. // // * NewDeleteChecker // Enables the modeling of new, new[], delete, delete[] in MallocChecker, // and checks for related double-free and use-after-free errors. // // * NewDeleteLeaksChecker // Checks for leaks related to new, new[], delete, delete[]. // Depends on NewDeleteChecker. // // * MismatchedDeallocatorChecker // Enables checking whether memory is deallocated with the correspending // allocation function in MallocChecker, such as malloc() allocated // regions are only freed by free(), new by delete, new[] by delete[]. // // InnerPointerChecker interacts very closely with MallocChecker, but unlike // the above checkers, it has it's own file, hence the many InnerPointerChecker // related headers and non-static functions. // //===----------------------------------------------------------------------===// #include "AllocationState.h" #include "InterCheckerAPI.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include using namespace clang; using namespace ento; using namespace std::placeholders; //===----------------------------------------------------------------------===// // The types of allocation we're modeling. This is used to check whether a // dynamically allocated object is deallocated with the correct function, like // not using operator delete on an object created by malloc(), or alloca regions // aren't ever deallocated manually. //===----------------------------------------------------------------------===// namespace { // Used to check correspondence between allocators and deallocators. enum AllocationFamily { AF_None, AF_Malloc, AF_CXXNew, AF_CXXNewArray, AF_IfNameIndex, AF_Alloca, AF_InnerBuffer }; } // end of anonymous namespace /// Print names of allocators and deallocators. /// /// \returns true on success. static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E); /// Print expected name of an allocator based on the deallocator's family /// derived from the DeallocExpr. static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family); /// Print expected name of a deallocator based on the allocator's /// family. static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family); //===----------------------------------------------------------------------===// // The state of a symbol, in terms of memory management. //===----------------------------------------------------------------------===// namespace { class RefState { enum Kind { // Reference to allocated memory. Allocated, // Reference to zero-allocated memory. AllocatedOfSizeZero, // Reference to released/freed memory. Released, // The responsibility for freeing resources has transferred from // this reference. A relinquished symbol should not be freed. Relinquished, // We are no longer guaranteed to have observed all manipulations // of this pointer/memory. For example, it could have been // passed as a parameter to an opaque function. Escaped }; const Stmt *S; Kind K; AllocationFamily Family; RefState(Kind k, const Stmt *s, AllocationFamily family) : S(s), K(k), Family(family) { assert(family != AF_None); } public: bool isAllocated() const { return K == Allocated; } bool isAllocatedOfSizeZero() const { return K == AllocatedOfSizeZero; } bool isReleased() const { return K == Released; } bool isRelinquished() const { return K == Relinquished; } bool isEscaped() const { return K == Escaped; } AllocationFamily getAllocationFamily() const { return Family; } const Stmt *getStmt() const { return S; } bool operator==(const RefState &X) const { return K == X.K && S == X.S && Family == X.Family; } static RefState getAllocated(AllocationFamily family, const Stmt *s) { return RefState(Allocated, s, family); } static RefState getAllocatedOfSizeZero(const RefState *RS) { return RefState(AllocatedOfSizeZero, RS->getStmt(), RS->getAllocationFamily()); } static RefState getReleased(AllocationFamily family, const Stmt *s) { return RefState(Released, s, family); } static RefState getRelinquished(AllocationFamily family, const Stmt *s) { return RefState(Relinquished, s, family); } static RefState getEscaped(const RefState *RS) { return RefState(Escaped, RS->getStmt(), RS->getAllocationFamily()); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); ID.AddPointer(S); ID.AddInteger(Family); } LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { switch (K) { #define CASE(ID) case ID: OS << #ID; break; CASE(Allocated) CASE(AllocatedOfSizeZero) CASE(Released) CASE(Relinquished) CASE(Escaped) } } LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } }; } // end of anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) /// Check if the memory associated with this symbol was released. static bool isReleased(SymbolRef Sym, CheckerContext &C); /// Update the RefState to reflect the new memory allocation. /// The optional \p RetVal parameter specifies the newly allocated pointer /// value; if unspecified, the value of expression \p E is used. static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, AllocationFamily Family, std::optional RetVal = std::nullopt); //===----------------------------------------------------------------------===// // The modeling of memory reallocation. // // The terminology 'toPtr' and 'fromPtr' will be used: // toPtr = realloc(fromPtr, 20); //===----------------------------------------------------------------------===// REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) namespace { /// The state of 'fromPtr' after reallocation is known to have failed. enum OwnershipAfterReallocKind { // The symbol needs to be freed (e.g.: realloc) OAR_ToBeFreedAfterFailure, // The symbol has been freed (e.g.: reallocf) OAR_FreeOnFailure, // The symbol doesn't have to freed (e.g.: we aren't sure if, how and where // 'fromPtr' was allocated: // void Haha(int *ptr) { // ptr = realloc(ptr, 67); // // ... // } // ). OAR_DoNotTrackAfterFailure }; /// Stores information about the 'fromPtr' symbol after reallocation. /// /// This is important because realloc may fail, and that needs special modeling. /// Whether reallocation failed or not will not be known until later, so we'll /// store whether upon failure 'fromPtr' will be freed, or needs to be freed /// later, etc. struct ReallocPair { // The 'fromPtr'. SymbolRef ReallocatedSym; OwnershipAfterReallocKind Kind; ReallocPair(SymbolRef S, OwnershipAfterReallocKind K) : ReallocatedSym(S), Kind(K) {} void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Kind); ID.AddPointer(ReallocatedSym); } bool operator==(const ReallocPair &X) const { return ReallocatedSym == X.ReallocatedSym && Kind == X.Kind; } }; } // end of anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) /// Tells if the callee is one of the builtin new/delete operators, including /// placement operators and other standard overloads. static bool isStandardNewDelete(const FunctionDecl *FD); static bool isStandardNewDelete(const CallEvent &Call) { if (!Call.getDecl() || !isa(Call.getDecl())) return false; return isStandardNewDelete(cast(Call.getDecl())); } //===----------------------------------------------------------------------===// // Definition of the MallocChecker class. //===----------------------------------------------------------------------===// namespace { class MallocChecker : public Checker, check::EndFunction, check::PreCall, check::PostCall, check::NewAllocator, check::PostStmt, check::PostObjCMessage, check::Location, eval::Assume> { public: /// In pessimistic mode, the checker assumes that it does not know which /// functions might free the memory. /// In optimistic mode, the checker assumes that all user-defined functions /// which might free a pointer are annotated. bool ShouldIncludeOwnershipAnnotatedFunctions = false; bool ShouldRegisterNoOwnershipChangeVisitor = false; /// Many checkers are essentially built into this one, so enabling them will /// make MallocChecker perform additional modeling and reporting. enum CheckKind { /// When a subchecker is enabled but MallocChecker isn't, model memory /// management but do not emit warnings emitted with MallocChecker only /// enabled. CK_MallocChecker, CK_NewDeleteChecker, CK_NewDeleteLeaksChecker, CK_MismatchedDeallocatorChecker, CK_InnerPointerChecker, CK_NumCheckKinds }; using LeakInfo = std::pair; bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const; void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; void checkEndFunction(const ReturnStmt *S, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; void checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const; ProgramStateRef checkPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const; ProgramStateRef checkConstPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; private: mutable std::unique_ptr BT_DoubleFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_DoubleDelete; mutable std::unique_ptr BT_Leak[CK_NumCheckKinds]; mutable std::unique_ptr BT_UseFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_BadFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_FreeAlloca[CK_NumCheckKinds]; mutable std::unique_ptr BT_MismatchedDealloc; mutable std::unique_ptr BT_OffsetFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_UseZerroAllocated[CK_NumCheckKinds]; #define CHECK_FN(NAME) \ void NAME(const CallEvent &Call, CheckerContext &C) const; CHECK_FN(checkFree) CHECK_FN(checkIfNameIndex) CHECK_FN(checkBasicAlloc) CHECK_FN(checkKernelMalloc) CHECK_FN(checkCalloc) CHECK_FN(checkAlloca) CHECK_FN(checkStrdup) CHECK_FN(checkIfFreeNameIndex) CHECK_FN(checkCXXNewOrCXXDelete) CHECK_FN(checkGMalloc0) CHECK_FN(checkGMemdup) CHECK_FN(checkGMallocN) CHECK_FN(checkGMallocN0) CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) void checkRealloc(const CallEvent &Call, CheckerContext &C, bool ShouldFreeOnFail) const; using CheckFn = std::function; const CallDescriptionMap FreeingMemFnMap{ {{{"free"}, 1}, &MallocChecker::checkFree}, {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex}, {{{"kfree"}, 1}, &MallocChecker::checkFree}, {{{"g_free"}, 1}, &MallocChecker::checkFree}, }; bool isFreeingCall(const CallEvent &Call) const; static bool isFreeingOwnershipAttrCall(const FunctionDecl *Func); friend class NoOwnershipChangeVisitor; CallDescriptionMap AllocatingMemFnMap{ {{{"alloca"}, 1}, &MallocChecker::checkAlloca}, {{{"_alloca"}, 1}, &MallocChecker::checkAlloca}, {{{"malloc"}, 1}, &MallocChecker::checkBasicAlloc}, {{{"malloc"}, 3}, &MallocChecker::checkKernelMalloc}, {{{"calloc"}, 2}, &MallocChecker::checkCalloc}, {{{"valloc"}, 1}, &MallocChecker::checkBasicAlloc}, {{CDF_MaybeBuiltin, {"strndup"}, 2}, &MallocChecker::checkStrdup}, {{CDF_MaybeBuiltin, {"strdup"}, 1}, &MallocChecker::checkStrdup}, {{{"_strdup"}, 1}, &MallocChecker::checkStrdup}, {{{"kmalloc"}, 2}, &MallocChecker::checkKernelMalloc}, {{{"if_nameindex"}, 1}, &MallocChecker::checkIfNameIndex}, {{CDF_MaybeBuiltin, {"wcsdup"}, 1}, &MallocChecker::checkStrdup}, {{CDF_MaybeBuiltin, {"_wcsdup"}, 1}, &MallocChecker::checkStrdup}, {{{"g_malloc"}, 1}, &MallocChecker::checkBasicAlloc}, {{{"g_malloc0"}, 1}, &MallocChecker::checkGMalloc0}, {{{"g_try_malloc"}, 1}, &MallocChecker::checkBasicAlloc}, {{{"g_try_malloc0"}, 1}, &MallocChecker::checkGMalloc0}, {{{"g_memdup"}, 2}, &MallocChecker::checkGMemdup}, {{{"g_malloc_n"}, 2}, &MallocChecker::checkGMallocN}, {{{"g_malloc0_n"}, 2}, &MallocChecker::checkGMallocN0}, {{{"g_try_malloc_n"}, 2}, &MallocChecker::checkGMallocN}, {{{"g_try_malloc0_n"}, 2}, &MallocChecker::checkGMallocN0}, }; CallDescriptionMap ReallocatingMemFnMap{ {{{"realloc"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"reallocf"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, true)}, {{{"g_realloc"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"g_try_realloc"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, {{{"g_realloc_n"}, 3}, &MallocChecker::checkReallocN}, {{{"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN}, }; bool isMemCall(const CallEvent &Call) const; // TODO: Remove mutable by moving the initializtaion to the registry function. mutable std::optional KernelZeroFlagVal; using KernelZeroSizePtrValueTy = std::optional; /// Store the value of macro called `ZERO_SIZE_PTR`. /// The value is initialized at first use, before first use the outer /// Optional is empty, afterwards it contains another Optional that indicates /// if the macro value could be determined, and if yes the value itself. mutable std::optional KernelZeroSizePtrValue; /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. [[nodiscard]] ProgramStateRef processNewAllocation(const CXXAllocatorCall &Call, CheckerContext &C, AllocationFamily Family) const; /// Perform a zero-allocation check. /// /// \param [in] Call The expression that allocates memory. /// \param [in] IndexOfSizeArg Index of the argument that specifies the size /// of the memory that needs to be allocated. E.g. for malloc, this would be /// 0. /// \param [in] RetVal Specifies the newly allocated pointer value; /// if unspecified, the value of expression \p E is used. [[nodiscard]] static ProgramStateRef ProcessZeroAllocCheck(const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, std::optional RetVal = std::nullopt); /// Model functions with the ownership_returns attribute. /// /// User-defined function may have the ownership_returns attribute, which /// annotates that the function returns with an object that was allocated on /// the heap, and passes the ownertship to the callee. /// /// void __attribute((ownership_returns(malloc, 1))) *my_malloc(size_t); /// /// It has two parameters: /// - first: name of the resource (e.g. 'malloc') /// - (OPTIONAL) second: size of the allocated region /// /// \param [in] Call The expression that allocates memory. /// \param [in] Att The ownership_returns attribute. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. [[nodiscard]] ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const; /// Models memory allocation. /// /// \param [in] Call The expression that allocates memory. /// \param [in] SizeEx Size of the memory that needs to be allocated. /// \param [in] Init The value the allocated memory needs to be initialized. /// with. For example, \c calloc initializes the allocated memory to 0, /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. [[nodiscard]] static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, SVal Init, ProgramStateRef State, AllocationFamily Family); /// Models memory allocation. /// /// \param [in] Call The expression that allocates memory. /// \param [in] Size Size of the memory that needs to be allocated. /// \param [in] Init The value the allocated memory needs to be initialized. /// with. For example, \c calloc initializes the allocated memory to 0, /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. [[nodiscard]] static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init, ProgramStateRef State, AllocationFamily Family); // Check if this malloc() for special flags. At present that means M_ZERO or // __GFP_ZERO (in which case, treat it like calloc). [[nodiscard]] std::optional performKernelMalloc(const CallEvent &Call, CheckerContext &C, const ProgramStateRef &State) const; /// Model functions with the ownership_takes and ownership_holds attributes. /// /// User-defined function may have the ownership_takes and/or ownership_holds /// attributes, which annotates that the function frees the memory passed as a /// parameter. /// /// void __attribute((ownership_takes(malloc, 1))) my_free(void *); /// void __attribute((ownership_holds(malloc, 1))) my_hold(void *); /// /// They have two parameters: /// - first: name of the resource (e.g. 'malloc') /// - second: index of the parameter the attribute applies to /// /// \param [in] Call The expression that frees memory. /// \param [in] Att The ownership_takes or ownership_holds attribute. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const; /// Models memory deallocation. /// /// \param [in] Call The expression that frees memory. /// \param [in] State The \c ProgramState right before allocation. /// \param [in] Num Index of the argument that needs to be freed. This is /// normally 0, but for custom free functions it may be different. /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds /// attribute. /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known /// to have been allocated, or in other words, the symbol to be freed was /// registered as allocated by this checker. In the following case, \c ptr /// isn't known to be allocated. /// void Haha(int *ptr) { /// ptr = realloc(ptr, 67); /// // ... /// } /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAux(CheckerContext &C, const CallEvent &Call, ProgramStateRef State, unsigned Num, bool Hold, bool &IsKnownToBeAllocated, AllocationFamily Family, bool ReturnsNullOnFailure = false) const; /// Models memory deallocation. /// /// \param [in] ArgExpr The variable who's pointee needs to be freed. /// \param [in] Call The expression that frees the memory. /// \param [in] State The \c ProgramState right before allocation. /// normally 0, but for custom free functions it may be different. /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds /// attribute. /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known /// to have been allocated, or in other words, the symbol to be freed was /// registered as allocated by this checker. In the following case, \c ptr /// isn't known to be allocated. /// void Haha(int *ptr) { /// ptr = realloc(ptr, 67); /// // ... /// } /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. /// \returns The ProgramState right after deallocation. [[nodiscard]] ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, AllocationFamily Family, bool ReturnsNullOnFailure = false) const; // TODO: Needs some refactoring, as all other deallocation modeling // functions are suffering from out parameters and messy code due to how // realloc is handled. // /// Models memory reallocation. /// /// \param [in] Call The expression that reallocated memory /// \param [in] ShouldFreeOnFail Whether if reallocation fails, the supplied /// memory should be freed. /// \param [in] State The \c ProgramState right before reallocation. /// \param [in] SuffixWithN Whether the reallocation function we're modeling /// has an '_n' suffix, such as g_realloc_n. /// \returns The ProgramState right after reallocation. [[nodiscard]] ProgramStateRef ReallocMemAux(CheckerContext &C, const CallEvent &Call, bool ShouldFreeOnFail, ProgramStateRef State, AllocationFamily Family, bool SuffixWithN = false) const; /// Evaluates the buffer size that needs to be allocated. /// /// \param [in] Blocks The amount of blocks that needs to be allocated. /// \param [in] BlockBytes The size of a block. /// \returns The symbolic value of \p Blocks * \p BlockBytes. [[nodiscard]] static SVal evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, const Expr *BlockBytes); /// Models zero initialized array allocation. /// /// \param [in] Call The expression that reallocated memory /// \param [in] State The \c ProgramState right before reallocation. /// \returns The ProgramState right after allocation. [[nodiscard]] static ProgramStateRef CallocMem(CheckerContext &C, const CallEvent &Call, ProgramStateRef State); /// See if deallocation happens in a suspicious context. If so, escape the /// pointers that otherwise would have been deallocated and return true. bool suppressDeallocationsInSuspiciousContexts(const CallEvent &Call, CheckerContext &C) const; /// If in \p S \p Sym is used, check whether \p Sym was already freed. bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; /// If in \p S \p Sym is used, check whether \p Sym was allocated as a zero /// sized memory region. void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; /// If in \p S \p Sym is being freed, check whether \p Sym was already freed. bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const; /// Check if the function is known to free memory, or if it is /// "interesting" and should be modeled explicitly. /// /// \param [out] EscapingSymbol A function might not free memory in general, /// but could be known to free a particular symbol. In this case, false is /// returned and the single escaping symbol is returned through the out /// parameter. /// /// We assume that pointers do not escape through calls to system functions /// not handled by this checker. bool mayFreeAnyEscapedMemoryOrIsModeledExplicitly(const CallEvent *Call, ProgramStateRef State, SymbolRef &EscapingSymbol) const; /// Implementation of the checkPointerEscape callbacks. [[nodiscard]] ProgramStateRef checkPointerEscapeAux(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind, bool IsConstPointerEscape) const; // Implementation of the checkPreStmt and checkEndFunction callbacks. void checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const; ///@{ /// Tells if a given family/call/symbol is tracked by the current checker. /// Sets CheckKind to the kind of the checker responsible for this /// family/call/symbol. std::optional getCheckIfTracked(AllocationFamily Family, bool IsALeakCheck = false) const; std::optional getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck = false) const; ///@} static bool SummarizeValue(raw_ostream &os, SVal V); static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR); void HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family) const; void HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const; void HandleMismatchedDealloc(CheckerContext &C, SourceRange Range, const Expr *DeallocExpr, const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const; void HandleOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family, const Expr *AllocExpr = nullptr) const; void HandleUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const; void HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released, SymbolRef Sym, SymbolRef PrevSym) const; void HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const; void HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, SymbolRef Sym) const; void HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *FreeExpr, AllocationFamily Family) const; /// Find the location of the allocation for Sym on the path leading to the /// exploded node N. static LeakInfo getAllocationSite(const ExplodedNode *N, SymbolRef Sym, CheckerContext &C); void HandleLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const; /// Test if value in ArgVal equals to value in macro `ZERO_SIZE_PTR`. bool isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, SVal ArgVal) const; }; } // end anonymous namespace //===----------------------------------------------------------------------===// // Definition of NoOwnershipChangeVisitor. //===----------------------------------------------------------------------===// namespace { class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor { // The symbol whose (lack of) ownership change we are interested in. SymbolRef Sym; const MallocChecker &Checker; using OwnerSet = llvm::SmallPtrSet; // Collect which entities point to the allocated memory, and could be // responsible for deallocating it. class OwnershipBindingsHandler : public StoreManager::BindingsHandler { SymbolRef Sym; OwnerSet &Owners; public: OwnershipBindingsHandler(SymbolRef Sym, OwnerSet &Owners) : Sym(Sym), Owners(Owners) {} bool HandleBinding(StoreManager &SMgr, Store Store, const MemRegion *Region, SVal Val) override { if (Val.getAsSymbol() == Sym) Owners.insert(Region); return true; } LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); } LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &out) const { out << "Owners: {\n"; for (const MemRegion *Owner : Owners) { out << " "; Owner->dumpToStream(out); out << ",\n"; } out << "}\n"; } }; protected: OwnerSet getOwnersAtNode(const ExplodedNode *N) { OwnerSet Ret; ProgramStateRef State = N->getState(); OwnershipBindingsHandler Handler{Sym, Ret}; State->getStateManager().getStoreManager().iterBindings(State->getStore(), Handler); return Ret; } LLVM_DUMP_METHOD static std::string getFunctionName(const ExplodedNode *CallEnterN) { if (const CallExpr *CE = llvm::dyn_cast_or_null( CallEnterN->getLocationAs()->getCallExpr())) if (const FunctionDecl *FD = CE->getDirectCallee()) return FD->getQualifiedNameAsString(); return ""; } /// Syntactically checks whether the callee is a deallocating function. Since /// we have no path-sensitive information on this call (we would need a /// CallEvent instead of a CallExpr for that), its possible that a /// deallocation function was called indirectly through a function pointer, /// but we are not able to tell, so this is a best effort analysis. /// See namespace `memory_passed_to_fn_call_free_through_fn_ptr` in /// clang/test/Analysis/NewDeleteLeaks.cpp. bool isFreeingCallAsWritten(const CallExpr &Call) const { if (Checker.FreeingMemFnMap.lookupAsWritten(Call) || Checker.ReallocatingMemFnMap.lookupAsWritten(Call)) return true; if (const auto *Func = llvm::dyn_cast_or_null(Call.getCalleeDecl())) return MallocChecker::isFreeingOwnershipAttrCall(Func); return false; } /// Heuristically guess whether the callee intended to free memory. This is /// done syntactically, because we are trying to argue about alternative /// paths of execution, and as a consequence we don't have path-sensitive /// information. bool doesFnIntendToHandleOwnership(const Decl *Callee, ASTContext &ACtx) { using namespace clang::ast_matchers; const FunctionDecl *FD = dyn_cast(Callee); // Given that the stack frame was entered, the body should always be // theoretically obtainable. In case of body farms, the synthesized body // is not attached to declaration, thus triggering the '!FD->hasBody()' // branch. That said, would a synthesized body ever intend to handle // ownership? As of today they don't. And if they did, how would we // put notes inside it, given that it doesn't match any source locations? if (!FD || !FD->hasBody()) return false; auto Matches = match(findAll(stmt(anyOf(cxxDeleteExpr().bind("delete"), callExpr().bind("call")))), *FD->getBody(), ACtx); for (BoundNodes Match : Matches) { if (Match.getNodeAs("delete")) return true; if (const auto *Call = Match.getNodeAs("call")) if (isFreeingCallAsWritten(*Call)) return true; } // TODO: Ownership might change with an attempt to store the allocated // memory, not only through deallocation. Check for attempted stores as // well. return false; } bool wasModifiedInFunction(const ExplodedNode *CallEnterN, const ExplodedNode *CallExitEndN) override { if (!doesFnIntendToHandleOwnership( CallExitEndN->getFirstPred()->getLocationContext()->getDecl(), CallExitEndN->getState()->getAnalysisManager().getASTContext())) return true; if (CallEnterN->getState()->get(Sym) != CallExitEndN->getState()->get(Sym)) return true; OwnerSet CurrOwners = getOwnersAtNode(CallEnterN); OwnerSet ExitOwners = getOwnersAtNode(CallExitEndN); // Owners in the current set may be purged from the analyzer later on. // If a variable is dead (is not referenced directly or indirectly after // some point), it will be removed from the Store before the end of its // actual lifetime. // This means that if the ownership status didn't change, CurrOwners // must be a superset of, but not necessarily equal to ExitOwners. return !llvm::set_is_subset(ExitOwners, CurrOwners); } static PathDiagnosticPieceRef emitNote(const ExplodedNode *N) { PathDiagnosticLocation L = PathDiagnosticLocation::create( N->getLocation(), N->getState()->getStateManager().getContext().getSourceManager()); return std::make_shared( L, "Returning without deallocating memory or storing the pointer for " "later deallocation"); } PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, const ObjCMethodCall &Call, const ExplodedNode *N) override { // TODO: Implement. return nullptr; } PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, const CXXConstructorCall &Call, const ExplodedNode *N) override { // TODO: Implement. return nullptr; } PathDiagnosticPieceRef maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call, const ExplodedNode *N) override { // TODO: Factor the logic of "what constitutes as an entity being passed // into a function call" out by reusing the code in // NoStoreFuncVisitor::maybeEmitNoteForParameters, maybe by incorporating // the printing technology in UninitializedObject's FieldChainInfo. ArrayRef Parameters = Call.parameters(); for (unsigned I = 0; I < Call.getNumArgs() && I < Parameters.size(); ++I) { SVal V = Call.getArgSVal(I); if (V.getAsSymbol() == Sym) return emitNote(N); } return nullptr; } public: NoOwnershipChangeVisitor(SymbolRef Sym, const MallocChecker *Checker) : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough), Sym(Sym), Checker(*Checker) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; ID.AddPointer(&Tag); ID.AddPointer(Sym); } }; } // end anonymous namespace //===----------------------------------------------------------------------===// // Definition of MallocBugVisitor. //===----------------------------------------------------------------------===// namespace { /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. class MallocBugVisitor final : public BugReporterVisitor { protected: enum NotificationMode { Normal, ReallocationFailed }; // The allocated region symbol tracked by the main analysis. SymbolRef Sym; // The mode we are in, i.e. what kind of diagnostics will be emitted. NotificationMode Mode; // A symbol from when the primary region should have been reallocated. SymbolRef FailedReallocSymbol; // A C++ destructor stack frame in which memory was released. Used for // miscellaneous false positive suppression. const StackFrameContext *ReleaseDestructorLC; bool IsLeak; public: MallocBugVisitor(SymbolRef S, bool isLeak = false) : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), ReleaseDestructorLC(nullptr), IsLeak(isLeak) {} static void *getTag() { static int Tag = 0; return &Tag; } void Profile(llvm::FoldingSetNodeID &ID) const override { ID.AddPointer(getTag()); ID.AddPointer(Sym); } /// Did not track -> allocated. Other state (released) -> allocated. static inline bool isAllocated(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { return (isa_and_nonnull(Stmt) && (RSCurr && (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && (!RSPrev || !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); } /// Did not track -> released. Other state (allocated) -> released. /// The statement associated with the release might be missing. static inline bool isReleased(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { bool IsReleased = (RSCurr && RSCurr->isReleased()) && (!RSPrev || !RSPrev->isReleased()); assert(!IsReleased || (isa_and_nonnull(Stmt)) || (!Stmt && RSCurr->getAllocationFamily() == AF_InnerBuffer)); return IsReleased; } /// Did not track -> relinquished. Other state (allocated) -> relinquished. static inline bool isRelinquished(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { return ( isa_and_nonnull(Stmt) && (RSCurr && RSCurr->isRelinquished()) && (!RSPrev || !RSPrev->isRelinquished())); } /// If the expression is not a call, and the state change is /// released -> allocated, it must be the realloc return value /// check. If we have to handle more cases here, it might be cleaner just /// to track this extra bit in the state itself. static inline bool hasReallocFailed(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { return ((!isa_and_nonnull(Stmt)) && (RSCurr && (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && (RSPrev && !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); } PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) override; PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, PathSensitiveBugReport &BR) override { if (!IsLeak) return nullptr; PathDiagnosticLocation L = BR.getLocation(); // Do not add the statement itself as a range in case of leak. return std::make_shared(L, BR.getDescription(), false); } private: class StackHintGeneratorForReallocationFailed : public StackHintGeneratorForSymbol { public: StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) : StackHintGeneratorForSymbol(S, M) {} std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex) override { // Printed parameters start at 1, not 0. ++ArgIndex; SmallString<200> buf; llvm::raw_svector_ostream os(buf); os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) << " parameter failed"; return std::string(os.str()); } std::string getMessageForReturn(const CallExpr *CallExpr) override { return "Reallocation of returned value failed"; } }; }; } // end anonymous namespace // A map from the freed symbol to the symbol representing the return value of // the free function. REGISTER_MAP_WITH_PROGRAMSTATE(FreeReturnValue, SymbolRef, SymbolRef) namespace { class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; public: StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} ProgramStateRef getState() const { return state; } bool VisitSymbol(SymbolRef sym) override { state = state->remove(sym); return true; } }; } // end anonymous namespace static bool isStandardNewDelete(const FunctionDecl *FD) { if (!FD) return false; OverloadedOperatorKind Kind = FD->getOverloadedOperator(); if (Kind != OO_New && Kind != OO_Array_New && Kind != OO_Delete && Kind != OO_Array_Delete) return false; // This is standard if and only if it's not defined in a user file. SourceLocation L = FD->getLocation(); // If the header for operator delete is not included, it's still defined // in an invalid source location. Check to make sure we don't crash. return !L.isValid() || FD->getASTContext().getSourceManager().isInSystemHeader(L); } //===----------------------------------------------------------------------===// // Methods of MallocChecker and MallocBugVisitor. //===----------------------------------------------------------------------===// bool MallocChecker::isFreeingOwnershipAttrCall(const FunctionDecl *Func) { if (Func->hasAttrs()) { for (const auto *I : Func->specific_attrs()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); if (OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) return true; } } return false; } bool MallocChecker::isFreeingCall(const CallEvent &Call) const { if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) return true; if (const auto *Func = dyn_cast_or_null(Call.getDecl())) return isFreeingOwnershipAttrCall(Func); return false; } bool MallocChecker::isMemCall(const CallEvent &Call) const { if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) return true; if (!ShouldIncludeOwnershipAnnotatedFunctions) return false; const auto *Func = dyn_cast(Call.getDecl()); return Func && Func->hasAttr(); } std::optional MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C, const ProgramStateRef &State) const { // 3-argument malloc(), as commonly used in {Free,Net,Open}BSD Kernels: // // void *malloc(unsigned long size, struct malloc_type *mtp, int flags); // // One of the possible flags is M_ZERO, which means 'give me back an // allocation which is already zeroed', like calloc. // 2-argument kmalloc(), as used in the Linux kernel: // // void *kmalloc(size_t size, gfp_t flags); // // Has the similar flag value __GFP_ZERO. // This logic is largely cloned from O_CREAT in UnixAPIChecker, maybe some // code could be shared. ASTContext &Ctx = C.getASTContext(); llvm::Triple::OSType OS = Ctx.getTargetInfo().getTriple().getOS(); if (!KernelZeroFlagVal) { switch (OS) { case llvm::Triple::FreeBSD: KernelZeroFlagVal = 0x0100; break; case llvm::Triple::NetBSD: KernelZeroFlagVal = 0x0002; break; case llvm::Triple::OpenBSD: KernelZeroFlagVal = 0x0008; break; case llvm::Triple::Linux: // __GFP_ZERO KernelZeroFlagVal = 0x8000; break; default: // FIXME: We need a more general way of getting the M_ZERO value. // See also: O_CREAT in UnixAPIChecker.cpp. // Fall back to normal malloc behavior on platforms where we don't // know M_ZERO. return std::nullopt; } } // We treat the last argument as the flags argument, and callers fall-back to // normal malloc on a None return. This works for the FreeBSD kernel malloc // as well as Linux kmalloc. if (Call.getNumArgs() < 2) return std::nullopt; const Expr *FlagsEx = Call.getArgExpr(Call.getNumArgs() - 1); const SVal V = C.getSVal(FlagsEx); if (!isa(V)) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. return std::nullopt; } NonLoc Flags = V.castAs(); NonLoc ZeroFlag = C.getSValBuilder() .makeIntVal(*KernelZeroFlagVal, FlagsEx->getType()) .castAs(); SVal MaskedFlagsUC = C.getSValBuilder().evalBinOpNN(State, BO_And, Flags, ZeroFlag, FlagsEx->getType()); if (MaskedFlagsUC.isUnknownOrUndef()) return std::nullopt; DefinedSVal MaskedFlags = MaskedFlagsUC.castAs(); // Check if maskedFlags is non-zero. ProgramStateRef TrueState, FalseState; std::tie(TrueState, FalseState) = State->assume(MaskedFlags); // If M_ZERO is set, treat this like calloc (initialized). if (TrueState && !FalseState) { SVal ZeroVal = C.getSValBuilder().makeZeroVal(Ctx.CharTy); return MallocMemAux(C, Call, Call.getArgExpr(0), ZeroVal, TrueState, AF_Malloc); } return std::nullopt; } SVal MallocChecker::evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, const Expr *BlockBytes) { SValBuilder &SB = C.getSValBuilder(); SVal BlocksVal = C.getSVal(Blocks); SVal BlockBytesVal = C.getSVal(BlockBytes); ProgramStateRef State = C.getState(); SVal TotalSize = SB.evalBinOp(State, BO_Mul, BlocksVal, BlockBytesVal, SB.getContext().getSizeType()); return TotalSize; } void MallocChecker::checkBasicAlloc(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 0, State); C.addTransition(State); } void MallocChecker::checkKernelMalloc(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); std::optional MaybeState = performKernelMalloc(Call, C, State); if (MaybeState) State = *MaybeState; else State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AF_Malloc); C.addTransition(State); } static bool isStandardRealloc(const CallEvent &Call) { const FunctionDecl *FD = dyn_cast(Call.getDecl()); assert(FD); ASTContext &AC = FD->getASTContext(); if (isa(FD)) return false; return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy && FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy && FD->getParamDecl(1)->getType().getDesugaredType(AC) == AC.getSizeType(); } static bool isGRealloc(const CallEvent &Call) { const FunctionDecl *FD = dyn_cast(Call.getDecl()); assert(FD); ASTContext &AC = FD->getASTContext(); if (isa(FD)) return false; return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy && FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy && FD->getParamDecl(1)->getType().getDesugaredType(AC) == AC.UnsignedLongTy; } void MallocChecker::checkRealloc(const CallEvent &Call, CheckerContext &C, bool ShouldFreeOnFail) const { // HACK: CallDescription currently recognizes non-standard realloc functions // as standard because it doesn't check the type, or wether its a non-method // function. This should be solved by making CallDescription smarter. // Mind that this came from a bug report, and all other functions suffer from // this. // https://bugs.llvm.org/show_bug.cgi?id=46253 if (!isStandardRealloc(Call) && !isGRealloc(Call)) return; ProgramStateRef State = C.getState(); State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } void MallocChecker::checkCalloc(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); State = CallocMem(C, Call, State); State = ProcessZeroAllocCheck(Call, 0, State); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; if (suppressDeallocationsInSuspiciousContexts(Call, C)) return; State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, AF_Malloc); C.addTransition(State); } void MallocChecker::checkAlloca(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AF_Alloca); State = ProcessZeroAllocCheck(Call, 0, State); C.addTransition(State); } void MallocChecker::checkStrdup(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; State = MallocUpdateRefState(C, CE, State, AF_Malloc); C.addTransition(State); } void MallocChecker::checkIfNameIndex(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. State = MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); C.addTransition(State); } void MallocChecker::checkIfFreeNameIndex(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, AF_IfNameIndex); C.addTransition(State); } void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; assert(isStandardNewDelete(Call)); // Process direct calls to operator new/new[]/delete/delete[] functions // as distinct from new/new[]/delete/delete[] expressions that are // processed by the checkPostStmt callbacks for CXXNewExpr and // CXXDeleteExpr. const FunctionDecl *FD = C.getCalleeDecl(CE); switch (FD->getOverloadedOperator()) { case OO_New: State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); State = ProcessZeroAllocCheck(Call, 0, State); break; case OO_Array_New: State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AF_CXXNewArray); State = ProcessZeroAllocCheck(Call, 0, State); break; case OO_Delete: State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, AF_CXXNew); break; case OO_Array_Delete: State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, AF_CXXNewArray); break; default: llvm_unreachable("not a new/delete operator"); } C.addTransition(State); } void MallocChecker::checkGMalloc0(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 0, State); C.addTransition(State); } void MallocChecker::checkGMemdup(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } void MallocChecker::checkGMallocN(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); SVal Init = UndefinedVal(); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 0, State); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } void MallocChecker::checkGMallocN0(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); SValBuilder &SB = C.getSValBuilder(); SVal Init = SB.makeZeroVal(SB.getContext().CharTy); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 0, State); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } void MallocChecker::checkReallocN(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State, AF_Malloc, /*SuffixWithN=*/true); State = ProcessZeroAllocCheck(Call, 1, State); State = ProcessZeroAllocCheck(Call, 2, State); C.addTransition(State); } void MallocChecker::checkOwnershipAttr(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; if (ShouldIncludeOwnershipAnnotatedFunctions || ChecksEnabled[CK_MismatchedDeallocatorChecker]) { // Check all the attributes, if there are any. // There can be multiple of these attributes. if (FD->hasAttrs()) for (const auto *I : FD->specific_attrs()) { switch (I->getOwnKind()) { case OwnershipAttr::Returns: State = MallocMemReturnsAttr(C, Call, I, State); break; case OwnershipAttr::Takes: case OwnershipAttr::Holds: State = FreeMemAttr(C, Call, I, State); break; } } } C.addTransition(State); } void MallocChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (C.wasInlined) return; if (!Call.getOriginExpr()) return; ProgramStateRef State = C.getState(); if (const CheckFn *Callback = FreeingMemFnMap.lookup(Call)) { (*Callback)(this, Call, C); return; } if (const CheckFn *Callback = AllocatingMemFnMap.lookup(Call)) { (*Callback)(this, Call, C); return; } if (const CheckFn *Callback = ReallocatingMemFnMap.lookup(Call)) { (*Callback)(this, Call, C); return; } if (isStandardNewDelete(Call)) { checkCXXNewOrCXXDelete(Call, C); return; } checkOwnershipAttr(Call, C); } // Performs a 0-sized allocations check. ProgramStateRef MallocChecker::ProcessZeroAllocCheck( const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, std::optional RetVal) { if (!State) return nullptr; if (!RetVal) RetVal = Call.getReturnValue(); const Expr *Arg = nullptr; if (const CallExpr *CE = dyn_cast(Call.getOriginExpr())) { Arg = CE->getArg(IndexOfSizeArg); } else if (const CXXNewExpr *NE = dyn_cast(Call.getOriginExpr())) { if (NE->isArray()) { Arg = *NE->getArraySize(); } else { return State; } } else llvm_unreachable("not a CallExpr or CXXNewExpr"); assert(Arg); auto DefArgVal = State->getSVal(Arg, Call.getLocationContext()).getAs(); if (!DefArgVal) return State; // Check if the allocation size is 0. ProgramStateRef TrueState, FalseState; SValBuilder &SvalBuilder = State->getStateManager().getSValBuilder(); DefinedSVal Zero = SvalBuilder.makeZeroVal(Arg->getType()).castAs(); std::tie(TrueState, FalseState) = State->assume(SvalBuilder.evalEQ(State, *DefArgVal, Zero)); if (TrueState && !FalseState) { SymbolRef Sym = RetVal->getAsLocSymbol(); if (!Sym) return State; const RefState *RS = State->get(Sym); if (RS) { if (RS->isAllocated()) return TrueState->set(Sym, RefState::getAllocatedOfSizeZero(RS)); else return State; } else { // Case of zero-size realloc. Historically 'realloc(ptr, 0)' is treated as // 'free(ptr)' and the returned value from 'realloc(ptr, 0)' is not // tracked. Add zero-reallocated Sym to the state to catch references // to zero-allocated memory. return TrueState->add(Sym); } } // Assume the value is non-zero going forward. assert(FalseState); return FalseState; } static QualType getDeepPointeeType(QualType T) { QualType Result = T, PointeeType = T->getPointeeType(); while (!PointeeType.isNull()) { Result = PointeeType; PointeeType = PointeeType->getPointeeType(); } return Result; } /// \returns true if the constructor invoked by \p NE has an argument of a /// pointer/reference to a record type. static bool hasNonTrivialConstructorCall(const CXXNewExpr *NE) { const CXXConstructExpr *ConstructE = NE->getConstructExpr(); if (!ConstructE) return false; if (!NE->getAllocatedType()->getAsCXXRecordDecl()) return false; const CXXConstructorDecl *CtorD = ConstructE->getConstructor(); // Iterate over the constructor parameters. for (const auto *CtorParam : CtorD->parameters()) { QualType CtorParamPointeeT = CtorParam->getType()->getPointeeType(); if (CtorParamPointeeT.isNull()) continue; CtorParamPointeeT = getDeepPointeeType(CtorParamPointeeT); if (CtorParamPointeeT->getAsCXXRecordDecl()) return true; } return false; } ProgramStateRef MallocChecker::processNewAllocation(const CXXAllocatorCall &Call, CheckerContext &C, AllocationFamily Family) const { if (!isStandardNewDelete(Call)) return nullptr; const CXXNewExpr *NE = Call.getOriginExpr(); const ParentMap &PM = C.getLocationContext()->getParentMap(); ProgramStateRef State = C.getState(); // Non-trivial constructors have a chance to escape 'this', but marking all // invocations of trivial constructors as escaped would cause too great of // reduction of true positives, so let's just do that for constructors that // have an argument of a pointer-to-record type. if (!PM.isConsumedExpr(NE) && hasNonTrivialConstructorCall(NE)) return State; // The return value from operator new is bound to a specified initialization // value (if any) and we don't want to loose this value. So we call // MallocUpdateRefState() instead of MallocMemAux() which breaks the // existing binding. SVal Target = Call.getObjectUnderConstruction(); State = MallocUpdateRefState(C, NE, State, Family, Target); State = ProcessZeroAllocCheck(Call, 0, State, Target); return State; } void MallocChecker::checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const { if (!C.wasInlined) { ProgramStateRef State = processNewAllocation( Call, C, (Call.getOriginExpr()->isArray() ? AF_CXXNewArray : AF_CXXNew)); C.addTransition(State); } } static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) { // If the first selector piece is one of the names below, assume that the // object takes ownership of the memory, promising to eventually deallocate it // with free(). // Ex: [NSData dataWithBytesNoCopy:bytes length:10]; // (...unless a 'freeWhenDone' parameter is false, but that's checked later.) StringRef FirstSlot = Call.getSelector().getNameForSlot(0); return FirstSlot == "dataWithBytesNoCopy" || FirstSlot == "initWithBytesNoCopy" || FirstSlot == "initWithCharactersNoCopy"; } static std::optional getFreeWhenDoneArg(const ObjCMethodCall &Call) { Selector S = Call.getSelector(); // FIXME: We should not rely on fully-constrained symbols being folded. for (unsigned i = 1; i < S.getNumArgs(); ++i) if (S.getNameForSlot(i).equals("freeWhenDone")) return !Call.getArgSVal(i).isZeroConstant(); return std::nullopt; } void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const { if (C.wasInlined) return; if (!isKnownDeallocObjCMethodName(Call)) return; if (std::optional FreeWhenDone = getFreeWhenDoneArg(Call)) if (!*FreeWhenDone) return; if (Call.hasNonZeroCallbackArg()) return; bool IsKnownToBeAllocatedMemory; ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), Call, C.getState(), /*Hold=*/true, IsKnownToBeAllocatedMemory, AF_Malloc, /*ReturnsNullOnFailure=*/true); C.addTransition(State); } ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; if (Att->getModule()->getName() != "malloc") return nullptr; if (!Att->args().empty()) { return MallocMemAux(C, Call, Call.getArgExpr(Att->args_begin()->getASTIndex()), UndefinedVal(), State, AF_Malloc); } return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc); } ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, SVal Init, ProgramStateRef State, AllocationFamily Family) { if (!State) return nullptr; assert(SizeEx); return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Family); } ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init, ProgramStateRef State, AllocationFamily Family) { if (!State) return nullptr; const Expr *CE = Call.getOriginExpr(); // We expect the malloc functions to return a pointer. if (!Loc::isLocType(CE->getType())) return nullptr; // Bind the return value to the symbolic value from the heap region. // TODO: move use of this functions to an EvalCall callback, becasue // BindExpr() should'nt be used elsewhere. unsigned Count = C.blockCount(); SValBuilder &SVB = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); DefinedSVal RetVal = ((Family == AF_Alloca) ? SVB.getAllocaRegionVal(CE, LCtx, Count) : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count) .castAs()); State = State->BindExpr(CE, C.getLocationContext(), RetVal); // Fill the region with the initialization value. State = State->bindDefaultInitial(RetVal, Init, LCtx); // If Size is somehow undefined at this point, this line prevents a crash. if (Size.isUndef()) Size = UnknownVal(); // Set the region's extent. State = setDynamicExtent(State, RetVal.getAsRegion(), Size.castAs(), SVB); return MallocUpdateRefState(C, CE, State, Family); } static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, AllocationFamily Family, std::optional RetVal) { if (!State) return nullptr; // Get the return value. if (!RetVal) RetVal = C.getSVal(E); // We expect the malloc functions to return a pointer. if (!RetVal->getAs()) return nullptr; SymbolRef Sym = RetVal->getAsLocSymbol(); // This is a return value of a function that was not inlined, such as malloc() // or new(). We've checked that in the caller. Therefore, it must be a symbol. assert(Sym); // Set the symbol's state to Allocated. return State->set(Sym, RefState::getAllocated(Family, E)); } ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; if (Att->getModule()->getName() != "malloc") return nullptr; bool IsKnownToBeAllocated = false; for (const auto &Arg : Att->args()) { ProgramStateRef StateI = FreeMemAux(C, Call, State, Arg.getASTIndex(), Att->getOwnKind() == OwnershipAttr::Holds, IsKnownToBeAllocated, AF_Malloc); if (StateI) State = StateI; } return State; } ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const CallEvent &Call, ProgramStateRef State, unsigned Num, bool Hold, bool &IsKnownToBeAllocated, AllocationFamily Family, bool ReturnsNullOnFailure) const { if (!State) return nullptr; if (Call.getNumArgs() < (Num + 1)) return nullptr; return FreeMemAux(C, Call.getArgExpr(Num), Call, State, Hold, IsKnownToBeAllocated, Family, ReturnsNullOnFailure); } /// Checks if the previous call to free on the given symbol failed - if free /// failed, returns true. Also, returns the corresponding return value symbol. static bool didPreviousFreeFail(ProgramStateRef State, SymbolRef Sym, SymbolRef &RetStatusSymbol) { const SymbolRef *Ret = State->get(Sym); if (Ret) { assert(*Ret && "We should not store the null return symbol"); ConstraintManager &CMgr = State->getConstraintManager(); ConditionTruthVal FreeFailed = CMgr.isNull(State, *Ret); RetStatusSymbol = *Ret; return FreeFailed.isConstrainedTrue(); } return false; } static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) { if (const CallExpr *CE = dyn_cast(E)) { // FIXME: This doesn't handle indirect calls. const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) return false; os << *FD; if (!FD->isOverloadedOperator()) os << "()"; return true; } if (const ObjCMessageExpr *Msg = dyn_cast(E)) { if (Msg->isInstanceMessage()) os << "-"; else os << "+"; Msg->getSelector().print(os); return true; } if (const CXXNewExpr *NE = dyn_cast(E)) { os << "'" << getOperatorSpelling(NE->getOperatorNew()->getOverloadedOperator()) << "'"; return true; } if (const CXXDeleteExpr *DE = dyn_cast(E)) { os << "'" << getOperatorSpelling(DE->getOperatorDelete()->getOverloadedOperator()) << "'"; return true; } return false; } static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) { switch(Family) { case AF_Malloc: os << "malloc()"; return; case AF_CXXNew: os << "'new'"; return; case AF_CXXNewArray: os << "'new[]'"; return; case AF_IfNameIndex: os << "'if_nameindex()'"; return; case AF_InnerBuffer: os << "container-specific allocator"; return; case AF_Alloca: case AF_None: llvm_unreachable("not a deallocation expression"); } } static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) { switch(Family) { case AF_Malloc: os << "free()"; return; case AF_CXXNew: os << "'delete'"; return; case AF_CXXNewArray: os << "'delete[]'"; return; case AF_IfNameIndex: os << "'if_freenameindex()'"; return; case AF_InnerBuffer: os << "container-specific deallocator"; return; case AF_Alloca: case AF_None: llvm_unreachable("suspicious argument"); } } ProgramStateRef MallocChecker::FreeMemAux( CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, AllocationFamily Family, bool ReturnsNullOnFailure) const { if (!State) return nullptr; SVal ArgVal = C.getSVal(ArgExpr); if (!isa(ArgVal)) return nullptr; DefinedOrUnknownSVal location = ArgVal.castAs(); // Check for null dereferences. if (!isa(location)) return nullptr; // The explicit NULL case, no operation is performed. ProgramStateRef notNullState, nullState; std::tie(notNullState, nullState) = State->assume(location); if (nullState && !notNullState) return nullptr; // Unknown values could easily be okay // Undefined values are handled elsewhere if (ArgVal.isUnknownOrUndef()) return nullptr; const MemRegion *R = ArgVal.getAsRegion(); const Expr *ParentExpr = Call.getOriginExpr(); // NOTE: We detected a bug, but the checker under whose name we would emit the // error could be disabled. Generally speaking, the MallocChecker family is an // integral part of the Static Analyzer, and disabling any part of it should // only be done under exceptional circumstances, such as frequent false // positives. If this is the case, we can reasonably believe that there are // serious faults in our understanding of the source code, and even if we // don't emit an warning, we should terminate further analysis with a sink // node. // Nonlocs can't be freed, of course. // Non-region locations (labels and fixed addresses) also shouldn't be freed. if (!R) { // Exception: // If the macro ZERO_SIZE_PTR is defined, this could be a kernel source // code. In that case, the ZERO_SIZE_PTR defines a special value used for a // zero-sized memory block which is allowed to be freed, despite not being a // null pointer. if (Family != AF_Malloc || !isArgZERO_SIZE_PTR(State, C, ArgVal)) HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); return nullptr; } R = R->StripCasts(); // Blocks might show up as heap data, but should not be free()d if (isa(R)) { HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); return nullptr; } const MemSpaceRegion *MS = R->getMemorySpace(); // Parameters, locals, statics, globals, and memory returned by // __builtin_alloca() shouldn't be freed. if (!isa(MS)) { // Regions returned by malloc() are represented by SymbolicRegion objects // within HeapSpaceRegion. Of course, free() can work on memory allocated // outside the current function, so UnknownSpaceRegion is also a // possibility here. if (isa(R)) HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); else HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); return nullptr; } const SymbolicRegion *SrBase = dyn_cast(R->getBaseRegion()); // Various cases could lead to non-symbol values here. // For now, ignore them. if (!SrBase) return nullptr; SymbolRef SymBase = SrBase->getSymbol(); const RefState *RsBase = State->get(SymBase); SymbolRef PreviousRetStatusSymbol = nullptr; IsKnownToBeAllocated = RsBase && (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero()); if (RsBase) { // Memory returned by alloca() shouldn't be freed. if (RsBase->getAllocationFamily() == AF_Alloca) { HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); return nullptr; } // Check for double free first. if ((RsBase->isReleased() || RsBase->isRelinquished()) && !didPreviousFreeFail(State, SymBase, PreviousRetStatusSymbol)) { HandleDoubleFree(C, ParentExpr->getSourceRange(), RsBase->isReleased(), SymBase, PreviousRetStatusSymbol); return nullptr; // If the pointer is allocated or escaped, but we are now trying to free it, // check that the call to free is proper. } else if (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero() || RsBase->isEscaped()) { // Check if an expected deallocation function matches the real one. bool DeallocMatchesAlloc = RsBase->getAllocationFamily() == Family; if (!DeallocMatchesAlloc) { HandleMismatchedDealloc(C, ArgExpr->getSourceRange(), ParentExpr, RsBase, SymBase, Hold); return nullptr; } // Check if the memory location being freed is the actual location // allocated, or an offset. RegionOffset Offset = R->getAsOffset(); if (Offset.isValid() && !Offset.hasSymbolicOffset() && Offset.getOffset() != 0) { const Expr *AllocExpr = cast(RsBase->getStmt()); HandleOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family, AllocExpr); return nullptr; } } } if (SymBase->getType()->isFunctionPointerType()) { HandleFunctionPtrFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); return nullptr; } // Clean out the info on previous call to free return info. State = State->remove(SymBase); // Keep track of the return value. If it is NULL, we will know that free // failed. if (ReturnsNullOnFailure) { SVal RetVal = C.getSVal(ParentExpr); SymbolRef RetStatusSymbol = RetVal.getAsSymbol(); if (RetStatusSymbol) { C.getSymbolManager().addSymbolDependency(SymBase, RetStatusSymbol); State = State->set(SymBase, RetStatusSymbol); } } // If we don't know anything about this symbol, a free on it may be totally // valid. If this is the case, lets assume that the allocation family of the // freeing function is the same as the symbols allocation family, and go with // that. assert(!RsBase || (RsBase && RsBase->getAllocationFamily() == Family)); // Normal free. if (Hold) return State->set(SymBase, RefState::getRelinquished(Family, ParentExpr)); return State->set(SymBase, RefState::getReleased(Family, ParentExpr)); } std::optional MallocChecker::getCheckIfTracked(AllocationFamily Family, bool IsALeakCheck) const { switch (Family) { case AF_Malloc: case AF_Alloca: case AF_IfNameIndex: { if (ChecksEnabled[CK_MallocChecker]) return CK_MallocChecker; return std::nullopt; } case AF_CXXNew: case AF_CXXNewArray: { if (IsALeakCheck) { if (ChecksEnabled[CK_NewDeleteLeaksChecker]) return CK_NewDeleteLeaksChecker; } else { if (ChecksEnabled[CK_NewDeleteChecker]) return CK_NewDeleteChecker; } return std::nullopt; } case AF_InnerBuffer: { if (ChecksEnabled[CK_InnerPointerChecker]) return CK_InnerPointerChecker; return std::nullopt; } case AF_None: { llvm_unreachable("no family"); } } llvm_unreachable("unhandled family"); } std::optional MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck) const { if (C.getState()->contains(Sym)) return CK_MallocChecker; const RefState *RS = C.getState()->get(Sym); assert(RS); return getCheckIfTracked(RS->getAllocationFamily(), IsALeakCheck); } bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) { if (std::optional IntVal = V.getAs()) os << "an integer (" << IntVal->getValue() << ")"; else if (std::optional ConstAddr = V.getAs()) os << "a constant address (" << ConstAddr->getValue() << ")"; else if (std::optional Label = V.getAs()) os << "the address of the label '" << Label->getLabel()->getName() << "'"; else return false; return true; } bool MallocChecker::SummarizeRegion(raw_ostream &os, const MemRegion *MR) { switch (MR->getKind()) { case MemRegion::FunctionCodeRegionKind: { const NamedDecl *FD = cast(MR)->getDecl(); if (FD) os << "the address of the function '" << *FD << '\''; else os << "the address of a function"; return true; } case MemRegion::BlockCodeRegionKind: os << "block text"; return true; case MemRegion::BlockDataRegionKind: // FIXME: where the block came from? os << "a block"; return true; default: { const MemSpaceRegion *MS = MR->getMemorySpace(); if (isa(MS)) { const VarRegion *VR = dyn_cast(MR); const VarDecl *VD; if (VR) VD = VR->getDecl(); else VD = nullptr; if (VD) os << "the address of the local variable '" << VD->getName() << "'"; else os << "the address of a local stack variable"; return true; } if (isa(MS)) { const VarRegion *VR = dyn_cast(MR); const VarDecl *VD; if (VR) VD = VR->getDecl(); else VD = nullptr; if (VD) os << "the address of the parameter '" << VD->getName() << "'"; else os << "the address of a parameter"; return true; } if (isa(MS)) { const VarRegion *VR = dyn_cast(MR); const VarDecl *VD; if (VR) VD = VR->getDecl(); else VD = nullptr; if (VD) { if (VD->isStaticLocal()) os << "the address of the static variable '" << VD->getName() << "'"; else os << "the address of the global variable '" << VD->getName() << "'"; } else os << "the address of a global variable"; return true; } return false; } } } void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(Family); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_BadFree[*CheckKind]) BT_BadFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Bad free", categories::MemoryError)); SmallString<100> buf; llvm::raw_svector_ostream os(buf); const MemRegion *MR = ArgVal.getAsRegion(); while (const ElementRegion *ER = dyn_cast_or_null(MR)) MR = ER->getSuperRegion(); os << "Argument to "; if (!printMemFnName(os, C, DeallocExpr)) os << "deallocator"; os << " is "; bool Summarized = MR ? SummarizeRegion(os, MR) : SummarizeValue(os, ArgVal); if (Summarized) os << ", which is not memory allocated by "; else os << "not memory allocated by "; printExpectedAllocName(os, Family); auto R = std::make_unique(*BT_BadFree[*CheckKind], os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); } } void MallocChecker::HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { std::optional CheckKind; if (ChecksEnabled[CK_MallocChecker]) CheckKind = CK_MallocChecker; else if (ChecksEnabled[CK_MismatchedDeallocatorChecker]) CheckKind = CK_MismatchedDeallocatorChecker; else { C.addSink(); return; } if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_FreeAlloca[*CheckKind]) BT_FreeAlloca[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Free alloca()", categories::MemoryError)); auto R = std::make_unique( *BT_FreeAlloca[*CheckKind], "Memory allocated by alloca() should not be deallocated", N); R->markInteresting(ArgVal.getAsRegion()); R->addRange(Range); C.emitReport(std::move(R)); } } void MallocChecker::HandleMismatchedDealloc(CheckerContext &C, SourceRange Range, const Expr *DeallocExpr, const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const { if (!ChecksEnabled[CK_MismatchedDeallocatorChecker]) { C.addSink(); return; } if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_MismatchedDealloc) BT_MismatchedDealloc.reset( new BugType(CheckNames[CK_MismatchedDeallocatorChecker], "Bad deallocator", categories::MemoryError)); SmallString<100> buf; llvm::raw_svector_ostream os(buf); const Expr *AllocExpr = cast(RS->getStmt()); SmallString<20> AllocBuf; llvm::raw_svector_ostream AllocOs(AllocBuf); SmallString<20> DeallocBuf; llvm::raw_svector_ostream DeallocOs(DeallocBuf); if (OwnershipTransferred) { if (printMemFnName(DeallocOs, C, DeallocExpr)) os << DeallocOs.str() << " cannot"; else os << "Cannot"; os << " take ownership of memory"; if (printMemFnName(AllocOs, C, AllocExpr)) os << " allocated by " << AllocOs.str(); } else { os << "Memory"; if (printMemFnName(AllocOs, C, AllocExpr)) os << " allocated by " << AllocOs.str(); os << " should be deallocated by "; printExpectedDeallocName(os, RS->getAllocationFamily()); if (printMemFnName(DeallocOs, C, DeallocExpr)) os << ", not " << DeallocOs.str(); } auto R = std::make_unique(*BT_MismatchedDealloc, os.str(), N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor(Sym); C.emitReport(std::move(R)); } } void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family, const Expr *AllocExpr) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(Family); if (!CheckKind) return; ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT_OffsetFree[*CheckKind]) BT_OffsetFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Offset free", categories::MemoryError)); SmallString<100> buf; llvm::raw_svector_ostream os(buf); SmallString<20> AllocNameBuf; llvm::raw_svector_ostream AllocNameOs(AllocNameBuf); const MemRegion *MR = ArgVal.getAsRegion(); assert(MR && "Only MemRegion based symbols can have offset free errors"); RegionOffset Offset = MR->getAsOffset(); assert((Offset.isValid() && !Offset.hasSymbolicOffset() && Offset.getOffset() != 0) && "Only symbols with a valid offset can have offset free errors"); int offsetBytes = Offset.getOffset() / C.getASTContext().getCharWidth(); os << "Argument to "; if (!printMemFnName(os, C, DeallocExpr)) os << "deallocator"; os << " is offset by " << offsetBytes << " " << ((abs(offsetBytes) > 1) ? "bytes" : "byte") << " from the start of "; if (AllocExpr && printMemFnName(AllocNameOs, C, AllocExpr)) os << "memory allocated by " << AllocNameOs.str(); else os << "allocated memory"; auto R = std::make_unique(*BT_OffsetFree[*CheckKind], os.str(), N); R->markInteresting(MR->getBaseRegion()); R->addRange(Range); C.emitReport(std::move(R)); } void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker] && !ChecksEnabled[CK_InnerPointerChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_UseFree[*CheckKind]) BT_UseFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Use-after-free", categories::MemoryError)); AllocationFamily AF = C.getState()->get(Sym)->getAllocationFamily(); auto R = std::make_unique( *BT_UseFree[*CheckKind], AF == AF_InnerBuffer ? "Inner pointer of container used after re/deallocation" : "Use of memory after it is freed", N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor(Sym); if (AF == AF_InnerBuffer) R->addVisitor(allocation_state::getInnerPointerBRVisitor(Sym)); C.emitReport(std::move(R)); } } void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released, SymbolRef Sym, SymbolRef PrevSym) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_DoubleFree[*CheckKind]) BT_DoubleFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Double free", categories::MemoryError)); auto R = std::make_unique( *BT_DoubleFree[*CheckKind], (Released ? "Attempt to free released memory" : "Attempt to free non-owned memory"), N); R->addRange(Range); R->markInteresting(Sym); if (PrevSym) R->markInteresting(PrevSym); R->addVisitor(Sym); C.emitReport(std::move(R)); } } void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const { if (!ChecksEnabled[CK_NewDeleteChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_DoubleDelete) BT_DoubleDelete.reset(new BugType(CheckNames[CK_NewDeleteChecker], "Double delete", categories::MemoryError)); auto R = std::make_unique( *BT_DoubleDelete, "Attempt to delete released memory", N); R->markInteresting(Sym); R->addVisitor(Sym); C.emitReport(std::move(R)); } } void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_UseZerroAllocated[*CheckKind]) BT_UseZerroAllocated[*CheckKind].reset( new BugType(CheckNames[*CheckKind], "Use of zero allocated", categories::MemoryError)); auto R = std::make_unique( *BT_UseZerroAllocated[*CheckKind], "Use of memory allocated with size zero", N); R->addRange(Range); if (Sym) { R->markInteresting(Sym); R->addVisitor(Sym); } C.emitReport(std::move(R)); } } void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *FreeExpr, AllocationFamily Family) const { if (!ChecksEnabled[CK_MallocChecker]) { C.addSink(); return; } std::optional CheckKind = getCheckIfTracked(Family); if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_BadFree[*CheckKind]) BT_BadFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Bad free", categories::MemoryError)); SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); const MemRegion *MR = ArgVal.getAsRegion(); while (const ElementRegion *ER = dyn_cast_or_null(MR)) MR = ER->getSuperRegion(); Os << "Argument to "; if (!printMemFnName(Os, C, FreeExpr)) Os << "deallocator"; Os << " is a function pointer"; auto R = std::make_unique(*BT_BadFree[*CheckKind], Os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); } } ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, bool ShouldFreeOnFail, ProgramStateRef State, AllocationFamily Family, bool SuffixWithN) const { if (!State) return nullptr; const CallExpr *CE = cast(Call.getOriginExpr()); if (SuffixWithN && CE->getNumArgs() < 3) return nullptr; else if (CE->getNumArgs() < 2) return nullptr; const Expr *arg0Expr = CE->getArg(0); SVal Arg0Val = C.getSVal(arg0Expr); if (!isa(Arg0Val)) return nullptr; DefinedOrUnknownSVal arg0Val = Arg0Val.castAs(); SValBuilder &svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal PtrEQ = svalBuilder.evalEQ( State, arg0Val, svalBuilder.makeNullWithType(arg0Expr->getType())); // Get the size argument. const Expr *Arg1 = CE->getArg(1); // Get the value of the size argument. SVal TotalSize = C.getSVal(Arg1); if (SuffixWithN) TotalSize = evalMulForBufferSize(C, Arg1, CE->getArg(2)); if (!isa(TotalSize)) return nullptr; // Compare the size argument to 0. DefinedOrUnknownSVal SizeZero = svalBuilder.evalEQ(State, TotalSize.castAs(), svalBuilder.makeIntValWithWidth( svalBuilder.getContext().getSizeType(), 0)); ProgramStateRef StatePtrIsNull, StatePtrNotNull; std::tie(StatePtrIsNull, StatePtrNotNull) = State->assume(PtrEQ); ProgramStateRef StateSizeIsZero, StateSizeNotZero; std::tie(StateSizeIsZero, StateSizeNotZero) = State->assume(SizeZero); // We only assume exceptional states if they are definitely true; if the // state is under-constrained, assume regular realloc behavior. bool PrtIsNull = StatePtrIsNull && !StatePtrNotNull; bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero; // If the ptr is NULL and the size is not 0, the call is equivalent to // malloc(size). if (PrtIsNull && !SizeIsZero) { ProgramStateRef stateMalloc = MallocMemAux( C, Call, TotalSize, UndefinedVal(), StatePtrIsNull, Family); return stateMalloc; } if (PrtIsNull && SizeIsZero) return State; assert(!PrtIsNull); bool IsKnownToBeAllocated = false; // If the size is 0, free the memory. if (SizeIsZero) // The semantics of the return value are: // If size was equal to 0, either NULL or a pointer suitable to be passed // to free() is returned. We just free the input pointer and do not add // any constrains on the output pointer. if (ProgramStateRef stateFree = FreeMemAux( C, Call, StateSizeIsZero, 0, false, IsKnownToBeAllocated, Family)) return stateFree; // Default behavior. if (ProgramStateRef stateFree = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocated, Family)) { ProgramStateRef stateRealloc = MallocMemAux(C, Call, TotalSize, UnknownVal(), stateFree, Family); if (!stateRealloc) return nullptr; OwnershipAfterReallocKind Kind = OAR_ToBeFreedAfterFailure; if (ShouldFreeOnFail) Kind = OAR_FreeOnFailure; else if (!IsKnownToBeAllocated) Kind = OAR_DoNotTrackAfterFailure; // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). SymbolRef FromPtr = arg0Val.getLocSymbolInBase(); SVal RetVal = C.getSVal(CE); SymbolRef ToPtr = RetVal.getAsSymbol(); assert(FromPtr && ToPtr && "By this point, FreeMemAux and MallocMemAux should have checked " "whether the argument or the return value is symbolic!"); // Record the info about the reallocated symbol so that we could properly // process failed reallocation. stateRealloc = stateRealloc->set(ToPtr, ReallocPair(FromPtr, Kind)); // The reallocated symbol should stay alive for as long as the new symbol. C.getSymbolManager().addSymbolDependency(ToPtr, FromPtr); return stateRealloc; } return nullptr; } ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallEvent &Call, ProgramStateRef State) { if (!State) return nullptr; if (Call.getNumArgs() < 2) return nullptr; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); return MallocMemAux(C, Call, TotalSize, zeroVal, State, AF_Malloc); } MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, CheckerContext &C) { const LocationContext *LeakContext = N->getLocationContext(); // Walk the ExplodedGraph backwards and find the first node that referred to // the tracked symbol. const ExplodedNode *AllocNode = N; const MemRegion *ReferenceRegion = nullptr; while (N) { ProgramStateRef State = N->getState(); if (!State->get(Sym)) break; // Find the most recent expression bound to the symbol in the current // context. if (!ReferenceRegion) { if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) { SVal Val = State->getSVal(MR); if (Val.getAsLocSymbol() == Sym) { const VarRegion *VR = MR->getBaseRegion()->getAs(); // Do not show local variables belonging to a function other than // where the error is reported. if (!VR || (VR->getStackFrame() == LeakContext->getStackFrame())) ReferenceRegion = MR; } } } // Allocation node, is the last node in the current or parent context in // which the symbol was tracked. const LocationContext *NContext = N->getLocationContext(); if (NContext == LeakContext || NContext->isParentOf(LeakContext)) AllocNode = N; N = N->pred_empty() ? nullptr : *(N->pred_begin()); } return LeakInfo(AllocNode, ReferenceRegion); } void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const { if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteLeaksChecker]) return; const RefState *RS = C.getState()->get(Sym); assert(RS && "cannot leak an untracked symbol"); AllocationFamily Family = RS->getAllocationFamily(); if (Family == AF_Alloca) return; std::optional CheckKind = getCheckIfTracked(Family, true); if (!CheckKind) return; assert(N); if (!BT_Leak[*CheckKind]) { // Leaks should not be reported if they are post-dominated by a sink: // (1) Sinks are higher importance bugs. // (2) NoReturnFunctionChecker uses sink nodes to represent paths ending // with __noreturn functions such as assert() or exit(). We choose not // to report leaks on such paths. BT_Leak[*CheckKind].reset(new BugType(CheckNames[*CheckKind], "Memory leak", categories::MemoryError, /*SuppressOnSink=*/true)); } // Most bug reports are cached at the location where they occurred. // With leaks, we want to unique them by the location where they were // allocated, and only report a single path. PathDiagnosticLocation LocUsedForUniqueing; const ExplodedNode *AllocNode = nullptr; const MemRegion *Region = nullptr; std::tie(AllocNode, Region) = getAllocationSite(N, Sym, C); const Stmt *AllocationStmt = AllocNode->getStmtForDiagnostics(); if (AllocationStmt) LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocationStmt, C.getSourceManager(), AllocNode->getLocationContext()); SmallString<200> buf; llvm::raw_svector_ostream os(buf); if (Region && Region->canPrintPretty()) { os << "Potential leak of memory pointed to by "; Region->printPretty(os); } else { os << "Potential memory leak"; } auto R = std::make_unique( *BT_Leak[*CheckKind], os.str(), N, LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl()); R->markInteresting(Sym); R->addVisitor(Sym, true); if (ShouldRegisterNoOwnershipChangeVisitor) R->addVisitor(Sym, this); C.emitReport(std::move(R)); } void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef state = C.getState(); RegionStateTy OldRS = state->get(); RegionStateTy::Factory &F = state->get_context(); RegionStateTy RS = OldRS; SmallVector Errors; for (auto [Sym, State] : RS) { if (SymReaper.isDead(Sym)) { if (State.isAllocated() || State.isAllocatedOfSizeZero()) Errors.push_back(Sym); // Remove the dead symbol from the map. RS = F.remove(RS, Sym); } } if (RS == OldRS) { // We shouldn't have touched other maps yet. assert(state->get() == C.getState()->get()); assert(state->get() == C.getState()->get()); return; } // Cleanup the Realloc Pairs Map. ReallocPairsTy RP = state->get(); for (auto [Sym, ReallocPair] : RP) { if (SymReaper.isDead(Sym) || SymReaper.isDead(ReallocPair.ReallocatedSym)) { state = state->remove(Sym); } } // Cleanup the FreeReturnValue Map. FreeReturnValueTy FR = state->get(); for (auto [Sym, RetSym] : FR) { if (SymReaper.isDead(Sym) || SymReaper.isDead(RetSym)) { state = state->remove(Sym); } } // Generate leak node. ExplodedNode *N = C.getPredecessor(); if (!Errors.empty()) { static CheckerProgramPointTag Tag("MallocChecker", "DeadSymbolsLeak"); N = C.generateNonFatalErrorNode(C.getState(), &Tag); if (N) { for (SymbolRef Sym : Errors) { HandleLeak(Sym, N, C); } } } C.addTransition(state->set(RS), N); } void MallocChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (const auto *DC = dyn_cast(&Call)) { const CXXDeleteExpr *DE = DC->getOriginExpr(); if (!ChecksEnabled[CK_NewDeleteChecker]) if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) checkUseAfterFree(Sym, C, DE->getArgument()); if (!isStandardNewDelete(DC->getDecl())) return; ProgramStateRef State = C.getState(); bool IsKnownToBeAllocated; State = FreeMemAux(C, DE->getArgument(), Call, State, /*Hold*/ false, IsKnownToBeAllocated, (DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew)); C.addTransition(State); return; } if (const auto *DC = dyn_cast(&Call)) { SymbolRef Sym = DC->getCXXThisVal().getAsSymbol(); if (!Sym || checkDoubleDelete(Sym, C)) return; } // We will check for double free in the post visit. if (const AnyFunctionCall *FC = dyn_cast(&Call)) { const FunctionDecl *FD = FC->getDecl(); if (!FD) return; if (ChecksEnabled[CK_MallocChecker] && isFreeingCall(Call)) return; } // Check if the callee of a method is deleted. if (const CXXInstanceCall *CC = dyn_cast(&Call)) { SymbolRef Sym = CC->getCXXThisVal().getAsSymbol(); if (!Sym || checkUseAfterFree(Sym, C, CC->getCXXThisExpr())) return; } // Check arguments for being used after free. for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) { SVal ArgSVal = Call.getArgSVal(I); if (isa(ArgSVal)) { SymbolRef Sym = ArgSVal.getAsSymbol(); if (!Sym) continue; if (checkUseAfterFree(Sym, C, Call.getArgExpr(I))) return; } } } void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { checkEscapeOnReturn(S, C); } // In the CFG, automatic destructors come after the return statement. // This callback checks for returning memory that is freed by automatic // destructors, as those cannot be reached in checkPreStmt(). void MallocChecker::checkEndFunction(const ReturnStmt *S, CheckerContext &C) const { checkEscapeOnReturn(S, C); } void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const { if (!S) return; const Expr *E = S->getRetValue(); if (!E) return; // Check if we are returning a symbol. ProgramStateRef State = C.getState(); SVal RetVal = C.getSVal(E); SymbolRef Sym = RetVal.getAsSymbol(); if (!Sym) // If we are returning a field of the allocated struct or an array element, // the callee could still free the memory. // TODO: This logic should be a part of generic symbol escape callback. if (const MemRegion *MR = RetVal.getAsRegion()) if (isa(MR)) if (const SymbolicRegion *BMR = dyn_cast(MR->getBaseRegion())) Sym = BMR->getSymbol(); // Check if we are returning freed memory. if (Sym) checkUseAfterFree(Sym, C, E); } // TODO: Blocks should be either inlined or should call invalidate regions // upon invocation. After that's in place, special casing here will not be // needed. void MallocChecker::checkPostStmt(const BlockExpr *BE, CheckerContext &C) const { // Scan the BlockDecRefExprs for any object the retain count checker // may be tracking. if (!BE->getBlockDecl()->hasCaptures()) return; ProgramStateRef state = C.getState(); const BlockDataRegion *R = cast(C.getSVal(BE).getAsRegion()); auto ReferencedVars = R->referenced_vars(); if (ReferencedVars.empty()) return; SmallVector Regions; const LocationContext *LC = C.getLocationContext(); MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); for (const auto &Var : ReferencedVars) { const VarRegion *VR = Var.getCapturedRegion(); if (VR->getSuperRegion() == R) { VR = MemMgr.getVarRegion(VR->getDecl(), LC); } Regions.push_back(VR); } state = state->scanReachableSymbols(Regions).getState(); C.addTransition(state); } static bool isReleased(SymbolRef Sym, CheckerContext &C) { assert(Sym); const RefState *RS = C.getState()->get(Sym); return (RS && RS->isReleased()); } bool MallocChecker::suppressDeallocationsInSuspiciousContexts( const CallEvent &Call, CheckerContext &C) const { if (Call.getNumArgs() == 0) return false; StringRef FunctionStr = ""; if (const auto *FD = dyn_cast(C.getStackFrame()->getDecl())) if (const Stmt *Body = FD->getBody()) if (Body->getBeginLoc().isValid()) FunctionStr = Lexer::getSourceText(CharSourceRange::getTokenRange( {FD->getBeginLoc(), Body->getBeginLoc()}), C.getSourceManager(), C.getLangOpts()); // We do not model the Integer Set Library's retain-count based allocation. if (!FunctionStr.contains("__isl_")) return false; ProgramStateRef State = C.getState(); for (const Expr *Arg : cast(Call.getOriginExpr())->arguments()) if (SymbolRef Sym = C.getSVal(Arg).getAsSymbol()) if (const RefState *RS = State->get(Sym)) State = State->set(Sym, RefState::getEscaped(RS)); C.addTransition(State); return true; } bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { if (isReleased(Sym, C)) { HandleUseAfterFree(C, S->getSourceRange(), Sym); return true; } return false; } void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { assert(Sym); if (const RefState *RS = C.getState()->get(Sym)) { if (RS->isAllocatedOfSizeZero()) HandleUseZeroAlloc(C, RS->getStmt()->getSourceRange(), Sym); } else if (C.getState()->contains(Sym)) { HandleUseZeroAlloc(C, S->getSourceRange(), Sym); } } bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const { if (isReleased(Sym, C)) { HandleDoubleDelete(C, Sym); return true; } return false; } // Check if the location is a freed symbolic region. void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const { SymbolRef Sym = l.getLocSymbolInBase(); if (Sym) { checkUseAfterFree(Sym, C, S); checkUseZeroAllocated(Sym, C, S); } } // If a symbolic region is assumed to NULL (or another constant), stop tracking // it - assuming that allocation failed on this path. ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const { RegionStateTy RS = state->get(); for (SymbolRef Sym : llvm::make_first_range(RS)) { // If the symbol is assumed to be NULL, remove it from consideration. ConstraintManager &CMgr = state->getConstraintManager(); ConditionTruthVal AllocFailed = CMgr.isNull(state, Sym); if (AllocFailed.isConstrainedTrue()) state = state->remove(Sym); } // Realloc returns 0 when reallocation fails, which means that we should // restore the state of the pointer being reallocated. ReallocPairsTy RP = state->get(); for (auto [Sym, ReallocPair] : RP) { // If the symbol is assumed to be NULL, remove it from consideration. ConstraintManager &CMgr = state->getConstraintManager(); ConditionTruthVal AllocFailed = CMgr.isNull(state, Sym); if (!AllocFailed.isConstrainedTrue()) continue; SymbolRef ReallocSym = ReallocPair.ReallocatedSym; if (const RefState *RS = state->get(ReallocSym)) { if (RS->isReleased()) { switch (ReallocPair.Kind) { case OAR_ToBeFreedAfterFailure: state = state->set(ReallocSym, RefState::getAllocated(RS->getAllocationFamily(), RS->getStmt())); break; case OAR_DoNotTrackAfterFailure: state = state->remove(ReallocSym); break; default: assert(ReallocPair.Kind == OAR_FreeOnFailure); } } } state = state->remove(Sym); } return state; } bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( const CallEvent *Call, ProgramStateRef State, SymbolRef &EscapingSymbol) const { assert(Call); EscapingSymbol = nullptr; // For now, assume that any C++ or block call can free memory. // TODO: If we want to be more optimistic here, we'll need to make sure that // regions escape to C++ containers. They seem to do that even now, but for // mysterious reasons. if (!isa(Call)) return true; // Check Objective-C messages by selector name. if (const ObjCMethodCall *Msg = dyn_cast(Call)) { // If it's not a framework call, or if it takes a callback, assume it // can free memory. if (!Call->isInSystemHeader() || Call->argumentsMayEscape()) return true; // If it's a method we know about, handle it explicitly post-call. // This should happen before the "freeWhenDone" check below. if (isKnownDeallocObjCMethodName(*Msg)) return false; // If there's a "freeWhenDone" parameter, but the method isn't one we know // about, we can't be sure that the object will use free() to deallocate the // memory, so we can't model it explicitly. The best we can do is use it to // decide whether the pointer escapes. if (std::optional FreeWhenDone = getFreeWhenDoneArg(*Msg)) return *FreeWhenDone; // If the first selector piece ends with "NoCopy", and there is no // "freeWhenDone" parameter set to zero, we know ownership is being // transferred. Again, though, we can't be sure that the object will use // free() to deallocate the memory, so we can't model it explicitly. StringRef FirstSlot = Msg->getSelector().getNameForSlot(0); if (FirstSlot.ends_with("NoCopy")) return true; // If the first selector starts with addPointer, insertPointer, // or replacePointer, assume we are dealing with NSPointerArray or similar. // This is similar to C++ containers (vector); we still might want to check // that the pointers get freed by following the container itself. if (FirstSlot.starts_with("addPointer") || FirstSlot.starts_with("insertPointer") || FirstSlot.starts_with("replacePointer") || FirstSlot.equals("valueWithPointer")) { return true; } // We should escape receiver on call to 'init'. This is especially relevant // to the receiver, as the corresponding symbol is usually not referenced // after the call. if (Msg->getMethodFamily() == OMF_init) { EscapingSymbol = Msg->getReceiverSVal().getAsSymbol(); return true; } // Otherwise, assume that the method does not free memory. // Most framework methods do not free memory. return false; } // At this point the only thing left to handle is straight function calls. const FunctionDecl *FD = cast(Call)->getDecl(); if (!FD) return true; // If it's one of the allocation functions we can reason about, we model // its behavior explicitly. if (isMemCall(*Call)) return false; // If it's not a system call, assume it frees memory. if (!Call->isInSystemHeader()) return true; // White list the system functions whose arguments escape. const IdentifierInfo *II = FD->getIdentifier(); if (!II) return true; StringRef FName = II->getName(); // White list the 'XXXNoCopy' CoreFoundation functions. // We specifically check these before if (FName.ends_with("NoCopy")) { // Look for the deallocator argument. We know that the memory ownership // is not transferred only if the deallocator argument is // 'kCFAllocatorNull'. for (unsigned i = 1; i < Call->getNumArgs(); ++i) { const Expr *ArgE = Call->getArgExpr(i)->IgnoreParenCasts(); if (const DeclRefExpr *DE = dyn_cast(ArgE)) { StringRef DeallocatorName = DE->getFoundDecl()->getName(); if (DeallocatorName == "kCFAllocatorNull") return false; } } return true; } // Associating streams with malloced buffers. The pointer can escape if // 'closefn' is specified (and if that function does free memory), // but it will not if closefn is not specified. // Currently, we do not inspect the 'closefn' function (PR12101). if (FName == "funopen") if (Call->getNumArgs() >= 4 && Call->getArgSVal(4).isConstant(0)) return false; // Do not warn on pointers passed to 'setbuf' when used with std streams, // these leaks might be intentional when setting the buffer for stdio. // http://stackoverflow.com/questions/2671151/who-frees-setvbuf-buffer if (FName == "setbuf" || FName =="setbuffer" || FName == "setlinebuf" || FName == "setvbuf") { if (Call->getNumArgs() >= 1) { const Expr *ArgE = Call->getArgExpr(0)->IgnoreParenCasts(); if (const DeclRefExpr *ArgDRE = dyn_cast(ArgE)) if (const VarDecl *D = dyn_cast(ArgDRE->getDecl())) if (D->getCanonicalDecl()->getName().contains("std")) return true; } } // A bunch of other functions which either take ownership of a pointer or // wrap the result up in a struct or object, meaning it can be freed later. // (See RetainCountChecker.) Not all the parameters here are invalidated, // but the Malloc checker cannot differentiate between them. The right way // of doing this would be to implement a pointer escapes callback. if (FName == "CGBitmapContextCreate" || FName == "CGBitmapContextCreateWithData" || FName == "CVPixelBufferCreateWithBytes" || FName == "CVPixelBufferCreateWithPlanarBytes" || FName == "OSAtomicEnqueue") { return true; } if (FName == "postEvent" && FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") { return true; } if (FName == "connectImpl" && FD->getQualifiedNameAsString() == "QObject::connectImpl") { return true; } if (FName == "singleShotImpl" && FD->getQualifiedNameAsString() == "QTimer::singleShotImpl") { return true; } // Handle cases where we know a buffer's /address/ can escape. // Note that the above checks handle some special cases where we know that // even though the address escapes, it's still our responsibility to free the // buffer. if (Call->argumentsMayEscape()) return true; // Otherwise, assume that the function does not free memory. // Most system calls do not free the memory. return false; } ProgramStateRef MallocChecker::checkPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { return checkPointerEscapeAux(State, Escaped, Call, Kind, /*IsConstPointerEscape*/ false); } ProgramStateRef MallocChecker::checkConstPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { // If a const pointer escapes, it may not be freed(), but it could be deleted. return checkPointerEscapeAux(State, Escaped, Call, Kind, /*IsConstPointerEscape*/ true); } static bool checkIfNewOrNewArrayFamily(const RefState *RS) { return (RS->getAllocationFamily() == AF_CXXNewArray || RS->getAllocationFamily() == AF_CXXNew); } ProgramStateRef MallocChecker::checkPointerEscapeAux( ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind, bool IsConstPointerEscape) const { // If we know that the call does not free memory, or we want to process the // call later, keep tracking the top level arguments. SymbolRef EscapingSymbol = nullptr; if (Kind == PSK_DirectEscapeOnCall && !mayFreeAnyEscapedMemoryOrIsModeledExplicitly(Call, State, EscapingSymbol) && !EscapingSymbol) { return State; } for (SymbolRef sym : Escaped) { if (EscapingSymbol && EscapingSymbol != sym) continue; if (const RefState *RS = State->get(sym)) if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) if (!IsConstPointerEscape || checkIfNewOrNewArrayFamily(RS)) State = State->set(sym, RefState::getEscaped(RS)); } return State; } bool MallocChecker::isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, SVal ArgVal) const { if (!KernelZeroSizePtrValue) KernelZeroSizePtrValue = tryExpandAsInteger("ZERO_SIZE_PTR", C.getPreprocessor()); const llvm::APSInt *ArgValKnown = C.getSValBuilder().getKnownValue(State, ArgVal); return ArgValKnown && *KernelZeroSizePtrValue && ArgValKnown->getSExtValue() == **KernelZeroSizePtrValue; } static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, ProgramStateRef prevState) { ReallocPairsTy currMap = currState->get(); ReallocPairsTy prevMap = prevState->get(); for (const ReallocPairsTy::value_type &Pair : prevMap) { SymbolRef sym = Pair.first; if (!currMap.lookup(sym)) return sym; } return nullptr; } static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { if (const IdentifierInfo *II = DD->getParent()->getIdentifier()) { StringRef N = II->getName(); if (N.contains_insensitive("ptr") || N.contains_insensitive("pointer")) { if (N.contains_insensitive("ref") || N.contains_insensitive("cnt") || N.contains_insensitive("intrusive") || N.contains_insensitive("shared")) { return true; } } } return false; } PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); const RefState *RSCurr = state->get(Sym); const RefState *RSPrev = statePrev->get(Sym); const Stmt *S = N->getStmtForDiagnostics(); // When dealing with containers, we sometimes want to give a note // even if the statement is missing. if (!S && (!RSCurr || RSCurr->getAllocationFamily() != AF_InnerBuffer)) return nullptr; const LocationContext *CurrentLC = N->getLocationContext(); // If we find an atomic fetch_add or fetch_sub within the destructor in which // the pointer was released (before the release), this is likely a destructor // of a shared pointer. // Because we don't model atomics, and also because we don't know that the // original reference count is positive, we should not report use-after-frees // on objects deleted in such destructors. This can probably be improved // through better shared pointer modeling. if (ReleaseDestructorLC) { if (const auto *AE = dyn_cast(S)) { AtomicExpr::AtomicOp Op = AE->getOp(); if (Op == AtomicExpr::AO__c11_atomic_fetch_add || Op == AtomicExpr::AO__c11_atomic_fetch_sub) { if (ReleaseDestructorLC == CurrentLC || ReleaseDestructorLC->isParentOf(CurrentLC)) { BR.markInvalid(getTag(), S); } } } } // FIXME: We will eventually need to handle non-statement-based events // (__attribute__((cleanup))). // Find out if this is an interesting point and what is the kind. StringRef Msg; std::unique_ptr StackHint = nullptr; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); if (Mode == Normal) { if (isAllocated(RSCurr, RSPrev, S)) { Msg = "Memory is allocated"; StackHint = std::make_unique( Sym, "Returned allocated memory"); } else if (isReleased(RSCurr, RSPrev, S)) { const auto Family = RSCurr->getAllocationFamily(); switch (Family) { case AF_Alloca: case AF_Malloc: case AF_CXXNew: case AF_CXXNewArray: case AF_IfNameIndex: Msg = "Memory is released"; StackHint = std::make_unique( Sym, "Returning; memory was released"); break; case AF_InnerBuffer: { const MemRegion *ObjRegion = allocation_state::getContainerObjRegion(statePrev, Sym); const auto *TypedRegion = cast(ObjRegion); QualType ObjTy = TypedRegion->getValueType(); OS << "Inner buffer of '" << ObjTy << "' "; if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { OS << "deallocated by call to destructor"; StackHint = std::make_unique( Sym, "Returning; inner buffer was deallocated"); } else { OS << "reallocated by call to '"; const Stmt *S = RSCurr->getStmt(); if (const auto *MemCallE = dyn_cast(S)) { OS << MemCallE->getMethodDecl()->getDeclName(); } else if (const auto *OpCallE = dyn_cast(S)) { OS << OpCallE->getDirectCallee()->getDeclName(); } else if (const auto *CallE = dyn_cast(S)) { auto &CEMgr = BRC.getStateManager().getCallEventManager(); CallEventRef<> Call = CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0}); if (const auto *D = dyn_cast_or_null(Call->getDecl())) OS << D->getDeclName(); else OS << "unknown"; } OS << "'"; StackHint = std::make_unique( Sym, "Returning; inner buffer was reallocated"); } Msg = OS.str(); break; } case AF_None: llvm_unreachable("Unhandled allocation family!"); } // See if we're releasing memory while inlining a destructor // (or one of its callees). This turns on various common // false positive suppressions. bool FoundAnyDestructor = false; for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) { if (const auto *DD = dyn_cast(LC->getDecl())) { if (isReferenceCountingPointerDestructor(DD)) { // This immediately looks like a reference-counting destructor. // We're bad at guessing the original reference count of the object, // so suppress the report for now. BR.markInvalid(getTag(), DD); } else if (!FoundAnyDestructor) { assert(!ReleaseDestructorLC && "There can be only one release point!"); // Suspect that it's a reference counting pointer destructor. // On one of the next nodes might find out that it has atomic // reference counting operations within it (see the code above), // and if so, we'd conclude that it likely is a reference counting // pointer destructor. ReleaseDestructorLC = LC->getStackFrame(); // It is unlikely that releasing memory is delegated to a destructor // inside a destructor of a shared pointer, because it's fairly hard // to pass the information that the pointer indeed needs to be // released into it. So we're only interested in the innermost // destructor. FoundAnyDestructor = true; } } } } else if (isRelinquished(RSCurr, RSPrev, S)) { Msg = "Memory ownership is transferred"; StackHint = std::make_unique(Sym, ""); } else if (hasReallocFailed(RSCurr, RSPrev, S)) { Mode = ReallocationFailed; Msg = "Reallocation failed"; StackHint = std::make_unique( Sym, "Reallocation failed"); if (SymbolRef sym = findFailedReallocSymbol(state, statePrev)) { // Is it possible to fail two reallocs WITHOUT testing in between? assert((!FailedReallocSymbol || FailedReallocSymbol == sym) && "We only support one failed realloc at a time."); BR.markInteresting(sym); FailedReallocSymbol = sym; } } // We are in a special mode if a reallocation failed later in the path. } else if (Mode == ReallocationFailed) { assert(FailedReallocSymbol && "No symbol to look for."); // Is this is the first appearance of the reallocated symbol? if (!statePrev->get(FailedReallocSymbol)) { // We're at the reallocation point. Msg = "Attempt to reallocate memory"; StackHint = std::make_unique( Sym, "Returned reallocated memory"); FailedReallocSymbol = nullptr; Mode = Normal; } } if (Msg.empty()) { assert(!StackHint); return nullptr; } assert(StackHint); // Generate the extra diagnostic. PathDiagnosticLocation Pos; if (!S) { assert(RSCurr->getAllocationFamily() == AF_InnerBuffer); auto PostImplCall = N->getLocation().getAs(); if (!PostImplCall) return nullptr; Pos = PathDiagnosticLocation(PostImplCall->getLocation(), BRC.getSourceManager()); } else { Pos = PathDiagnosticLocation(S, BRC.getSourceManager(), N->getLocationContext()); } auto P = std::make_shared(Pos, Msg, true); BR.addCallStackHint(P, std::move(StackHint)); return P; } void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { RegionStateTy RS = State->get(); if (!RS.isEmpty()) { Out << Sep << "MallocChecker :" << NL; for (auto [Sym, Data] : RS) { const RefState *RefS = State->get(Sym); AllocationFamily Family = RefS->getAllocationFamily(); std::optional CheckKind = getCheckIfTracked(Family); if (!CheckKind) CheckKind = getCheckIfTracked(Family, true); Sym->dumpToStream(Out); Out << " : "; Data.dump(Out); if (CheckKind) Out << " (" << CheckNames[*CheckKind].getName() << ")"; Out << NL; } } } namespace clang { namespace ento { namespace allocation_state { ProgramStateRef markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) { AllocationFamily Family = AF_InnerBuffer; return State->set(Sym, RefState::getReleased(Family, Origin)); } } // end namespace allocation_state } // end namespace ento } // end namespace clang // Intended to be used in InnerPointerChecker to register the part of // MallocChecker connected to it. void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { MallocChecker *checker = mgr.getChecker(); checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true; checker->CheckNames[MallocChecker::CK_InnerPointerChecker] = mgr.getCurrentCheckerName(); } void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { auto *checker = mgr.registerChecker(); checker->ShouldIncludeOwnershipAnnotatedFunctions = mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); checker->ShouldRegisterNoOwnershipChangeVisitor = mgr.getAnalyzerOptions().getCheckerBooleanOption( checker, "AddNoOwnershipChangeNotes"); } bool ento::shouldRegisterDynamicMemoryModeling(const CheckerManager &mgr) { return true; } #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &mgr) { \ MallocChecker *checker = mgr.getChecker(); \ checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ checker->CheckNames[MallocChecker::CK_##name] = \ mgr.getCurrentCheckerName(); \ } \ \ bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) REGISTER_CHECKER(NewDeleteLeaksChecker) REGISTER_CHECKER(MismatchedDeallocatorChecker)