aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp457
1 files changed, 262 insertions, 195 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 79ab05f2c786..fe202c79ed62 100644
--- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -46,6 +46,7 @@
#include "AllocationState.h"
#include "InterCheckerAPI.h"
+#include "NoOwnershipChangeVisitor.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
@@ -60,6 +61,7 @@
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Lexer.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Checkers/Taint.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -78,13 +80,11 @@
#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 <climits>
#include <functional>
#include <optional>
#include <utility>
@@ -322,6 +322,7 @@ public:
CK_NewDeleteLeaksChecker,
CK_MismatchedDeallocatorChecker,
CK_InnerPointerChecker,
+ CK_TaintedAllocChecker,
CK_NumCheckKinds
};
@@ -365,6 +366,7 @@ private:
mutable std::unique_ptr<BugType> BT_MismatchedDealloc;
mutable std::unique_ptr<BugType> BT_OffsetFree[CK_NumCheckKinds];
mutable std::unique_ptr<BugType> BT_UseZerroAllocated[CK_NumCheckKinds];
+ mutable std::unique_ptr<BugType> BT_TaintedAlloc;
#define CHECK_FN(NAME) \
void NAME(const CallEvent &Call, CheckerContext &C) const;
@@ -382,6 +384,8 @@ private:
CHECK_FN(checkGMemdup)
CHECK_FN(checkGMallocN)
CHECK_FN(checkGMallocN0)
+ CHECK_FN(preGetdelim)
+ CHECK_FN(checkGetdelim)
CHECK_FN(checkReallocN)
CHECK_FN(checkOwnershipAttr)
@@ -391,57 +395,82 @@ private:
using CheckFn = std::function<void(const MallocChecker *,
const CallEvent &Call, CheckerContext &C)>;
+ const CallDescriptionMap<CheckFn> PreFnMap{
+ // NOTE: the following CallDescription also matches the C++ standard
+ // library function std::getline(); the callback will filter it out.
+ {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetdelim},
+ {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim},
+ };
+
const CallDescriptionMap<CheckFn> FreeingMemFnMap{
- {{{"free"}, 1}, &MallocChecker::checkFree},
- {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex},
- {{{"kfree"}, 1}, &MallocChecker::checkFree},
- {{{"g_free"}, 1}, &MallocChecker::checkFree},
+ {{CDM::CLibrary, {"free"}, 1}, &MallocChecker::checkFree},
+ {{CDM::CLibrary, {"if_freenameindex"}, 1},
+ &MallocChecker::checkIfFreeNameIndex},
+ {{CDM::CLibrary, {"kfree"}, 1}, &MallocChecker::checkFree},
+ {{CDM::CLibrary, {"g_free"}, 1}, &MallocChecker::checkFree},
};
bool isFreeingCall(const CallEvent &Call) const;
static bool isFreeingOwnershipAttrCall(const FunctionDecl *Func);
- friend class NoOwnershipChangeVisitor;
+ friend class NoMemOwnershipChangeVisitor;
CallDescriptionMap<CheckFn> 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},
+ {{CDM::CLibrary, {"alloca"}, 1}, &MallocChecker::checkAlloca},
+ {{CDM::CLibrary, {"_alloca"}, 1}, &MallocChecker::checkAlloca},
+ // The line for "alloca" also covers "__builtin_alloca", but the
+ // _with_align variant must be listed separately because it takes an
+ // extra argument:
+ {{CDM::CLibrary, {"__builtin_alloca_with_align"}, 2},
+ &MallocChecker::checkAlloca},
+ {{CDM::CLibrary, {"malloc"}, 1}, &MallocChecker::checkBasicAlloc},
+ {{CDM::CLibrary, {"malloc"}, 3}, &MallocChecker::checkKernelMalloc},
+ {{CDM::CLibrary, {"calloc"}, 2}, &MallocChecker::checkCalloc},
+ {{CDM::CLibrary, {"valloc"}, 1}, &MallocChecker::checkBasicAlloc},
+ {{CDM::CLibrary, {"strndup"}, 2}, &MallocChecker::checkStrdup},
+ {{CDM::CLibrary, {"strdup"}, 1}, &MallocChecker::checkStrdup},
+ {{CDM::CLibrary, {"_strdup"}, 1}, &MallocChecker::checkStrdup},
+ {{CDM::CLibrary, {"kmalloc"}, 2}, &MallocChecker::checkKernelMalloc},
+ {{CDM::CLibrary, {"if_nameindex"}, 1}, &MallocChecker::checkIfNameIndex},
+ {{CDM::CLibrary, {"wcsdup"}, 1}, &MallocChecker::checkStrdup},
+ {{CDM::CLibrary, {"_wcsdup"}, 1}, &MallocChecker::checkStrdup},
+ {{CDM::CLibrary, {"g_malloc"}, 1}, &MallocChecker::checkBasicAlloc},
+ {{CDM::CLibrary, {"g_malloc0"}, 1}, &MallocChecker::checkGMalloc0},
+ {{CDM::CLibrary, {"g_try_malloc"}, 1}, &MallocChecker::checkBasicAlloc},
+ {{CDM::CLibrary, {"g_try_malloc0"}, 1}, &MallocChecker::checkGMalloc0},
+ {{CDM::CLibrary, {"g_memdup"}, 2}, &MallocChecker::checkGMemdup},
+ {{CDM::CLibrary, {"g_malloc_n"}, 2}, &MallocChecker::checkGMallocN},
+ {{CDM::CLibrary, {"g_malloc0_n"}, 2}, &MallocChecker::checkGMallocN0},
+ {{CDM::CLibrary, {"g_try_malloc_n"}, 2}, &MallocChecker::checkGMallocN},
+ {{CDM::CLibrary, {"g_try_malloc0_n"}, 2}, &MallocChecker::checkGMallocN0},
};
CallDescriptionMap<CheckFn> ReallocatingMemFnMap{
- {{{"realloc"}, 2},
+ {{CDM::CLibrary, {"realloc"}, 2},
std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)},
- {{{"reallocf"}, 2},
+ {{CDM::CLibrary, {"reallocf"}, 2},
std::bind(&MallocChecker::checkRealloc, _1, _2, _3, true)},
- {{{"g_realloc"}, 2},
+ {{CDM::CLibrary, {"g_realloc"}, 2},
std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)},
- {{{"g_try_realloc"}, 2},
+ {{CDM::CLibrary, {"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},
+ {{CDM::CLibrary, {"g_realloc_n"}, 3}, &MallocChecker::checkReallocN},
+ {{CDM::CLibrary, {"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN},
+
+ // NOTE: the following CallDescription also matches the C++ standard
+ // library function std::getline(); the callback will filter it out.
+ {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim},
+ {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim},
};
bool isMemCall(const CallEvent &Call) const;
+ void reportTaintBug(StringRef Msg, ProgramStateRef State, CheckerContext &C,
+ llvm::ArrayRef<SymbolRef> TaintedSyms,
+ AllocationFamily Family) const;
+
+ void checkTaintedness(CheckerContext &C, const CallEvent &Call,
+ const SVal SizeSVal, ProgramStateRef State,
+ AllocationFamily Family) const;
// TODO: Remove mutable by moving the initializtaion to the registry function.
mutable std::optional<uint64_t> KernelZeroFlagVal;
@@ -501,9 +530,9 @@ private:
/// malloc leaves it undefined.
/// \param [in] State The \c ProgramState right before allocation.
/// \returns The ProgramState right after allocation.
- [[nodiscard]] static ProgramStateRef
+ [[nodiscard]] ProgramStateRef
MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx,
- SVal Init, ProgramStateRef State, AllocationFamily Family);
+ SVal Init, ProgramStateRef State, AllocationFamily Family) const;
/// Models memory allocation.
///
@@ -514,9 +543,10 @@ private:
/// 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);
+ [[nodiscard]] ProgramStateRef MallocMemAux(CheckerContext &C,
+ const CallEvent &Call, SVal Size,
+ SVal Init, ProgramStateRef State,
+ AllocationFamily Family) const;
// Check if this malloc() for special flags. At present that means M_ZERO or
// __GFP_ZERO (in which case, treat it like calloc).
@@ -588,11 +618,14 @@ private:
/// }
/// \param [in] ReturnsNullOnFailure Whether the memory deallocation function
/// we're modeling returns with Null on failure.
+ /// \param [in] ArgValOpt Optional value to use for the argument instead of
+ /// the one obtained from ArgExpr.
/// \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;
+ AllocationFamily Family, bool ReturnsNullOnFailure = false,
+ std::optional<SVal> ArgValOpt = {}) const;
// TODO: Needs some refactoring, as all other deallocation modeling
// functions are suffering from out parameters and messy code due to how
@@ -626,8 +659,9 @@ private:
/// \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);
+ [[nodiscard]] ProgramStateRef CallocMem(CheckerContext &C,
+ const CallEvent &Call,
+ ProgramStateRef State) const;
/// See if deallocation happens in a suspicious context. If so, escape the
/// pointers that otherwise would have been deallocated and return true.
@@ -730,61 +764,8 @@ private:
//===----------------------------------------------------------------------===//
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<const MemRegion *, 8>;
-
- // 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";
- }
- };
-
+class NoMemOwnershipChangeVisitor final : public NoOwnershipChangeVisitor {
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<CallExpr>(
- CallEnterN->getLocationAs<CallEnter>()->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
@@ -793,8 +774,9 @@ protected:
/// 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))
+ const auto *MallocChk = static_cast<const MallocChecker *>(&Checker);
+ if (MallocChk->FreeingMemFnMap.lookupAsWritten(Call) ||
+ MallocChk->ReallocatingMemFnMap.lookupAsWritten(Call))
return true;
if (const auto *Func =
@@ -804,23 +786,21 @@ protected:
return false;
}
+ bool hasResourceStateChanged(ProgramStateRef CallEnterState,
+ ProgramStateRef CallExitEndState) final {
+ return CallEnterState->get<RegionState>(Sym) !=
+ CallExitEndState->get<RegionState>(Sym);
+ }
+
/// 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) {
+ bool doesFnIntendToHandleOwnership(const Decl *Callee,
+ ASTContext &ACtx) final {
using namespace clang::ast_matchers;
const FunctionDecl *FD = dyn_cast<FunctionDecl>(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);
@@ -838,30 +818,7 @@ protected:
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<RegionState>(Sym) !=
- CallExitEndN->getState()->get<RegionState>(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) {
+ PathDiagnosticPieceRef emitNote(const ExplodedNode *N) final {
PathDiagnosticLocation L = PathDiagnosticLocation::create(
N->getLocation(),
N->getState()->getStateManager().getContext().getSourceManager());
@@ -870,42 +827,9 @@ protected:
"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<ParmVarDecl *> 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) {}
+ NoMemOwnershipChangeVisitor(SymbolRef Sym, const MallocChecker *Checker)
+ : NoOwnershipChangeVisitor(Sym, Checker) {}
void Profile(llvm::FoldingSetNodeID &ID) const override {
static int Tag = 0;
@@ -1242,9 +1166,6 @@ static bool isStandardRealloc(const CallEvent &Call) {
assert(FD);
ASTContext &AC = FD->getASTContext();
- if (isa<CXXMethodDecl>(FD))
- return false;
-
return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy &&
FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy &&
FD->getParamDecl(1)->getType().getDesugaredType(AC) ==
@@ -1256,9 +1177,6 @@ static bool isGRealloc(const CallEvent &Call) {
assert(FD);
ASTContext &AC = FD->getASTContext();
- if (isa<CXXMethodDecl>(FD))
- return false;
-
return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy &&
FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy &&
FD->getParamDecl(1)->getType().getDesugaredType(AC) ==
@@ -1267,14 +1185,14 @@ static bool isGRealloc(const CallEvent &Call) {
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
+ // Ignore calls to functions whose type does not match the expected type of
+ // either the standard realloc or g_realloc from GLib.
+ // FIXME: Should we perform this kind of checking consistently for each
+ // function? If yes, then perhaps extend the `CallDescription` interface to
+ // handle this.
if (!isStandardRealloc(Call) && !isGRealloc(Call))
return;
+
ProgramStateRef State = C.getState();
State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AF_Malloc);
State = ProcessZeroAllocCheck(Call, 1, State);
@@ -1423,6 +1341,62 @@ void MallocChecker::checkGMallocN0(const CallEvent &Call,
C.addTransition(State);
}
+static bool isFromStdNamespace(const CallEvent &Call) {
+ const Decl *FD = Call.getDecl();
+ assert(FD && "a CallDescription cannot match a call without a Decl");
+ return FD->isInStdNamespace();
+}
+
+void MallocChecker::preGetdelim(const CallEvent &Call,
+ CheckerContext &C) const {
+ // Discard calls to the C++ standard library function std::getline(), which
+ // is completely unrelated to the POSIX getline() that we're checking.
+ if (isFromStdNamespace(Call))
+ return;
+
+ ProgramStateRef State = C.getState();
+ const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State);
+ if (!LinePtr)
+ return;
+
+ // FreeMemAux takes IsKnownToBeAllocated as an output parameter, and it will
+ // be true after the call if the symbol was registered by this checker.
+ // We do not need this value here, as FreeMemAux will take care
+ // of reporting any violation of the preconditions.
+ bool IsKnownToBeAllocated = false;
+ State = FreeMemAux(C, Call.getArgExpr(0), Call, State, false,
+ IsKnownToBeAllocated, AF_Malloc, false, LinePtr);
+ if (State)
+ C.addTransition(State);
+}
+
+void MallocChecker::checkGetdelim(const CallEvent &Call,
+ CheckerContext &C) const {
+ // Discard calls to the C++ standard library function std::getline(), which
+ // is completely unrelated to the POSIX getline() that we're checking.
+ if (isFromStdNamespace(Call))
+ return;
+
+ ProgramStateRef State = C.getState();
+ // Handle the post-conditions of getline and getdelim:
+ // Register the new conjured value as an allocated buffer.
+ const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return;
+
+ SValBuilder &SVB = C.getSValBuilder();
+
+ const auto LinePtr =
+ getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>();
+ const auto Size =
+ getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>();
+ if (!LinePtr || !Size || !LinePtr->getAsRegion())
+ return;
+
+ State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size, SVB);
+ C.addTransition(MallocUpdateRefState(C, CE, State, AF_Malloc, *LinePtr));
+}
+
void MallocChecker::checkReallocN(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -1622,6 +1596,11 @@ MallocChecker::processNewAllocation(const CXXAllocatorCall &Call,
// MallocUpdateRefState() instead of MallocMemAux() which breaks the
// existing binding.
SVal Target = Call.getObjectUnderConstruction();
+ if (Call.getOriginExpr()->isArray()) {
+ if (auto SizeEx = NE->getArraySize())
+ checkTaintedness(C, Call, C.getSVal(*SizeEx), State, AF_CXXNewArray);
+ }
+
State = MallocUpdateRefState(C, NE, State, Family, Target);
State = ProcessZeroAllocCheck(Call, 0, State, Target);
return State;
@@ -1654,7 +1633,7 @@ static std::optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) {
// 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"))
+ if (S.getNameForSlot(i) == "freeWhenDone")
return !Call.getArgSVal(i).isZeroConstant();
return std::nullopt;
@@ -1706,7 +1685,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
const CallEvent &Call,
const Expr *SizeEx, SVal Init,
ProgramStateRef State,
- AllocationFamily Family) {
+ AllocationFamily Family) const {
if (!State)
return nullptr;
@@ -1714,10 +1693,66 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Family);
}
+void MallocChecker::reportTaintBug(StringRef Msg, ProgramStateRef State,
+ CheckerContext &C,
+ llvm::ArrayRef<SymbolRef> TaintedSyms,
+ AllocationFamily Family) const {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(State, this)) {
+ if (!BT_TaintedAlloc)
+ BT_TaintedAlloc.reset(new BugType(CheckNames[CK_TaintedAllocChecker],
+ "Tainted Memory Allocation",
+ categories::TaintedData));
+ auto R = std::make_unique<PathSensitiveBugReport>(*BT_TaintedAlloc, Msg, N);
+ for (auto TaintedSym : TaintedSyms) {
+ R->markInteresting(TaintedSym);
+ }
+ C.emitReport(std::move(R));
+ }
+}
+
+void MallocChecker::checkTaintedness(CheckerContext &C, const CallEvent &Call,
+ const SVal SizeSVal, ProgramStateRef State,
+ AllocationFamily Family) const {
+ if (!ChecksEnabled[CK_TaintedAllocChecker])
+ return;
+ std::vector<SymbolRef> TaintedSyms =
+ taint::getTaintedSymbols(State, SizeSVal);
+ if (TaintedSyms.empty())
+ return;
+
+ SValBuilder &SVB = C.getSValBuilder();
+ QualType SizeTy = SVB.getContext().getSizeType();
+ QualType CmpTy = SVB.getConditionType();
+ // In case the symbol is tainted, we give a warning if the
+ // size is larger than SIZE_MAX/4
+ BasicValueFactory &BVF = SVB.getBasicValueFactory();
+ const llvm::APSInt MaxValInt = BVF.getMaxValue(SizeTy);
+ NonLoc MaxLength =
+ SVB.makeIntVal(MaxValInt / APSIntType(MaxValInt).getValue(4));
+ std::optional<NonLoc> SizeNL = SizeSVal.getAs<NonLoc>();
+ auto Cmp = SVB.evalBinOpNN(State, BO_GE, *SizeNL, MaxLength, CmpTy)
+ .getAs<DefinedOrUnknownSVal>();
+ if (!Cmp)
+ return;
+ auto [StateTooLarge, StateNotTooLarge] = State->assume(*Cmp);
+ if (!StateTooLarge && StateNotTooLarge) {
+ // We can prove that size is not too large so there is no issue.
+ return;
+ }
+
+ std::string Callee = "Memory allocation function";
+ if (Call.getCalleeIdentifier())
+ Callee = Call.getCalleeIdentifier()->getName().str();
+ reportTaintBug(
+ Callee + " is called with a tainted (potentially attacker controlled) "
+ "value. Make sure the value is bound checked.",
+ State, C, TaintedSyms, Family);
+}
+
ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
const CallEvent &Call, SVal Size,
SVal Init, ProgramStateRef State,
- AllocationFamily Family) {
+ AllocationFamily Family) const {
if (!State)
return nullptr;
@@ -1746,6 +1781,8 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
if (Size.isUndef())
Size = UnknownVal();
+ checkTaintedness(C, Call, Size, State, AF_Malloc);
+
// Set the region's extent.
State = setDynamicExtent(State, RetVal.getAsRegion(),
Size.castAs<DefinedOrUnknownSVal>(), SVB);
@@ -1769,9 +1806,18 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E,
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);
+ // FIXME: In theory this assertion should fail for `alloca()` calls (because
+ // `AllocaRegion`s are not symbolic); but in practice this does not happen.
+ // As the current code appears to work correctly, I'm not touching this issue
+ // now, but it would be good to investigate and clarify this.
+ // Also note that perhaps the special `AllocaRegion` should be replaced by
+ // `SymbolicRegion` (or turned into a subclass of `SymbolicRegion`) to enable
+ // proper tracking of memory allocated by `alloca()` -- and after that change
+ // this assertion would become valid again.
// Set the symbol's state to Allocated.
return State->set<RegionState>(Sym, RefState::getAllocated(Family, E));
@@ -1895,15 +1941,17 @@ static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
}
}
-ProgramStateRef MallocChecker::FreeMemAux(
- CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call,
- ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated,
- AllocationFamily Family, bool ReturnsNullOnFailure) const {
+ProgramStateRef
+MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
+ const CallEvent &Call, ProgramStateRef State,
+ bool Hold, bool &IsKnownToBeAllocated,
+ AllocationFamily Family, bool ReturnsNullOnFailure,
+ std::optional<SVal> ArgValOpt) const {
if (!State)
return nullptr;
- SVal ArgVal = C.getSVal(ArgExpr);
+ SVal ArgVal = ArgValOpt.value_or(C.getSVal(ArgExpr));
if (!isa<DefinedOrUnknownSVal>(ArgVal))
return nullptr;
DefinedOrUnknownSVal location = ArgVal.castAs<DefinedOrUnknownSVal>();
@@ -2673,7 +2721,7 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call,
ProgramStateRef MallocChecker::CallocMem(CheckerContext &C,
const CallEvent &Call,
- ProgramStateRef State) {
+ ProgramStateRef State) const {
if (!State)
return nullptr;
@@ -2790,7 +2838,7 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N,
R->markInteresting(Sym);
R->addVisitor<MallocBugVisitor>(Sym, true);
if (ShouldRegisterNoOwnershipChangeVisitor)
- R->addVisitor<NoOwnershipChangeVisitor>(Sym, this);
+ R->addVisitor<NoMemOwnershipChangeVisitor>(Sym, this);
C.emitReport(std::move(R));
}
@@ -2881,6 +2929,13 @@ void MallocChecker::checkPreCall(const CallEvent &Call,
return;
}
+ // We need to handle getline pre-conditions here before the pointed region
+ // gets invalidated by StreamChecker
+ if (const auto *PreFN = PreFnMap.lookup(Call)) {
+ (*PreFN)(this, Call, C);
+ return;
+ }
+
// We will check for double free in the post visit.
if (const AnyFunctionCall *FC = dyn_cast<AnyFunctionCall>(&Call)) {
const FunctionDecl *FD = FC->getDecl();
@@ -3160,7 +3215,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(
if (FirstSlot.starts_with("addPointer") ||
FirstSlot.starts_with("insertPointer") ||
FirstSlot.starts_with("replacePointer") ||
- FirstSlot.equals("valueWithPointer")) {
+ FirstSlot == "valueWithPointer") {
return true;
}
@@ -3356,7 +3411,7 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) {
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")) {
+ N.contains_insensitive("shared") || N.ends_with_insensitive("rc")) {
return true;
}
}
@@ -3388,13 +3443,24 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
// 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 (ReleaseDestructorLC && (ReleaseDestructorLC == CurrentLC ||
+ ReleaseDestructorLC->isParentOf(CurrentLC))) {
if (const auto *AE = dyn_cast<AtomicExpr>(S)) {
+ // Check for manual use of atomic builtins.
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);
+ }
+ } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
+ // Check for `std::atomic` and such. This covers both regular method calls
+ // and operator calls.
+ if (const auto *MD =
+ dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee())) {
+ const CXXRecordDecl *RD = MD->getParent();
+ // A bit wobbly with ".contains()" because it may be like
+ // "__atomic_base" or something.
+ if (StringRef(RD->getNameAsString()).contains("atomic")) {
BR.markInvalid(getTag(), S);
}
}
@@ -3628,3 +3694,4 @@ REGISTER_CHECKER(MallocChecker)
REGISTER_CHECKER(NewDeleteChecker)
REGISTER_CHECKER(NewDeleteLeaksChecker)
REGISTER_CHECKER(MismatchedDeallocatorChecker)
+REGISTER_CHECKER(TaintedAllocChecker)