https://github.com/dkrupp created https://github.com/llvm/llvm-project/pull/92420
unix.Malloc checker will warn if a memory allocation function (malloc, calloc, realloc, alloca) is called with a tainted (attacker controlled) size parameter. A large, maliciously set size value can trigger memory exhaustion. To get this warning, the alpha.security.taint.TaintPropagation checker also needs to be switched on. The warning will only be emitted, if the analyzer cannot prove that the size is below reasonable bounds (<SIZE_MAX/4). There were no new reports with this extension on the following open source projects. | Project | New Reports | Resolved Reports | |---------|-------------|------------------| | memcached | No reports | No reports | tmux | No reports | No reports | curl | No reports | No reports | twin | No reports | No reports | vim | No reports | No reports | openssl | No reports | No reports | sqlite | No reports | No reports | ffmpeg | No reports | No reports | postgres | No reports | No reports | xerces | No reports | No reports >From 80767176cbe8e5717c5f42b113f305d81b635cb9 Mon Sep 17 00:00:00 2001 From: Daniel Krupp <daniel.kr...@ericsson.com> Date: Tue, 30 Apr 2024 15:20:52 +0200 Subject: [PATCH] [analyzer] Adding taint analysis capability to unix.Malloc checker unix.Malloc checker will warn if a memory allocation function (malloc, calloc, realloc, alloca) is called with a tainted (attacker controlled) size parameter. A large, maliciously set size value can trigger memory exhaustion. To get this warning, the alpha.security.taint.TaintPropagation checker also needs to be switched on. The warning will only be emitted, if the analyzer cannot prove that the size is below reasonable bounds (<SIZE_MAX/4). --- clang/docs/analyzer/checkers.rst | 35 +++++++ .../StaticAnalyzer/Checkers/MallocChecker.cpp | 96 ++++++++++++++++--- clang/test/Analysis/malloc.c | 42 +++++++- 3 files changed, 159 insertions(+), 14 deletions(-) diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index eb8b58323da4d..0cdf6dcab2875 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -1273,6 +1273,41 @@ Check for memory leaks, double free, and use-after-free problems. Traces memory .. literalinclude:: checkers/unix_malloc_example.c :language: c +If the ``alpha.security.taint.TaintPropagation`` checker is enabled, the checker +warns for cases when the ``size`` parameter of the ``malloc`` , ``calloc``, +``realloc``, ``alloca`` is tainted (potentially attacker controlled). If an +attacker can inject a large value as the size parameter, memory exhaustion +denial of service attack can be carried out. + +The analyzer emits warning only if it cannot prove that the size parameter is +within reasonable bounds (``<= SIZE_MAX/4``). This functionality partially +covers the SEI Cert coding standard rule `INT04-C +<https://wiki.sei.cmu.edu/confluence/display/c/INT04-C.+Enforce+limits+on+integer+values+originating+from+tainted+sources>`_. + +You can silence this warning either by bound checking the ``size`` parameter, or +by explicitly marking the ``size`` parameter as sanitized. See the +:ref:`alpha-security-taint-TaintPropagation` checker for more details. + +.. code-block:: c + + void t1(void) { + size_t size; + scanf("%zu", &size); + int *p = malloc(size); // warn: malloc is called with a tainted (potentially attacker controlled) value + free(p); + } + + void t3(void) { + size_t size; + scanf("%zu", &size); + if (1024<size) + return; + int *p = malloc(size); // No warning expected as the the user input is bound + free(p); + } + +.. _unix-MismatchedDeallocator: + .. _unix-MallocSizeof: unix.MallocSizeof (C) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index dd204b62dcc04..2cc9205c07814 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -60,6 +60,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" @@ -365,6 +366,7 @@ class MallocChecker 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[CK_NumCheckKinds]; #define CHECK_FN(NAME) \ void NAME(const CallEvent &Call, CheckerContext &C) const; @@ -462,6 +464,13 @@ class MallocChecker }; bool isMemCall(const CallEvent &Call) const; + void reportTaintBug(StringRef Msg, ProgramStateRef State, CheckerContext &C, + llvm::ArrayRef<SymbolRef> TaintedSyms, + AllocationFamily Family, const Expr *SizeEx) 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; @@ -521,9 +530,9 @@ class MallocChecker /// 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. /// @@ -534,9 +543,10 @@ class MallocChecker /// 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). @@ -649,8 +659,9 @@ class MallocChecker /// \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. @@ -1779,7 +1790,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; @@ -1787,10 +1798,71 @@ 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 Expr *SizeEx) const { + if (ExplodedNode *N = C.generateErrorNode(State)) { + + std::optional<MallocChecker::CheckKind> CheckKind = + getCheckIfTracked(Family); + if (!CheckKind) + return; + if (!BT_TaintedAlloc[*CheckKind]) + BT_TaintedAlloc[*CheckKind].reset(new BugType(CheckNames[*CheckKind], + "Tainted Memory Allocation", + categories::MemoryError)); + auto R = std::make_unique<PathSensitiveBugReport>( + *BT_TaintedAlloc[*CheckKind], Msg, N); + + bugreporter::trackExpressionValue(N, SizeEx, *R); + for (auto Sym : TaintedSyms) + R->markInteresting(Sym); + C.emitReport(std::move(R)); + } +} + +void MallocChecker::CheckTaintedness(CheckerContext &C, const CallEvent &Call, + const SVal SizeSVal, ProgramStateRef State, + AllocationFamily Family) const { + std::vector<SymbolRef> TaintedSyms = + clang::ento::taint::getTaintedSymbols(State, SizeSVal); + if (!TaintedSyms.empty()) { + 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 ok. + 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, Call.getArgExpr(0)); + } +} + ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init, ProgramStateRef State, - AllocationFamily Family) { + AllocationFamily Family) const { if (!State) return nullptr; @@ -1819,9 +1891,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, if (Size.isUndef()) Size = UnknownVal(); - // TODO: If Size is tainted and we cannot prove that it is within - // reasonable bounds, emit a warning that an attacker may - // provoke a memory exhaustion error. + CheckTaintedness(C, Call, Size, State, AF_Malloc); // Set the region's extent. State = setDynamicExtent(State, RetVal.getAsRegion(), @@ -2761,7 +2831,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; diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c index e5cb45ba73352..6dba76a57d83f 100644 --- a/clang/test/Analysis/malloc.c +++ b/clang/test/Analysis/malloc.c @@ -3,7 +3,8 @@ // RUN: -analyzer-checker=alpha.deadcode.UnreachableCode \ // RUN: -analyzer-checker=alpha.core.CastSize \ // RUN: -analyzer-checker=unix \ -// RUN: -analyzer-checker=debug.ExprInspection +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-checker=alpha.security.taint.TaintPropagation #include "Inputs/system-header-simulator.h" @@ -48,6 +49,45 @@ void myfoo(int *p); void myfooint(int p); char *fooRetPtr(void); +void t1(void) { + size_t size; + scanf("%zu", &size); + int *p = malloc(size); // expected-warning{{malloc is called with a tainted (potentially attacker controlled) value}} + free(p); +} + +void t2(void) { + size_t size; + scanf("%zu", &size); + int *p = calloc(size,2); // expected-warning{{calloc is called with a tainted (potentially attacker controlled) value}} + free(p); +} + +void t3(void) { + size_t size; + scanf("%zu", &size); + if (1024<size) + return; + int *p = malloc(size); // No warning expected as the the user input is bound + free(p); +} + +void t4(void) { + size_t size; + int *p = malloc(sizeof(int)); + scanf("%zu", &size); + p = (int*) realloc((void*) p, size); // // expected-warning{{realloc is called with a tainted (potentially attacker controlled) value}} + free(p); +} + +void t5(void) { + size_t size; + int *p = alloca(sizeof(int)); + scanf("%zu", &size); + p = (int*) alloca(size); // // expected-warning{{alloca is called with a tainted (potentially attacker controlled) value}} +} + + void f1(void) { int *p = malloc(12); return; // expected-warning{{Potential leak of memory pointed to by 'p'}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits