Skip to content

Commit 289725f

Browse files
authored
[analyzer] New optin.taint.TaintedAlloc checker for catching unbounded memory allocation calls (llvm#92420)
A new optional checker (optin.taint.TaintedAlloc) will warn if a memory allocation function (malloc, calloc, realloc, alloca, operator new[]) 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).
1 parent ae858b5 commit 289725f

File tree

6 files changed

+226
-20
lines changed

6 files changed

+226
-20
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ Warns when a nullable pointer is returned from a function that has _Nonnull retu
599599
optin
600600
^^^^^
601601
602-
Checkers for portability, performance or coding style specific rules.
602+
Checkers for portability, performance, optional security and coding style specific rules.
603603
604604
.. _optin-core-EnumCastOutOfRange:
605605
@@ -938,6 +938,53 @@ optin.portability.UnixAPI
938938
"""""""""""""""""""""""""
939939
Finds implementation-defined behavior in UNIX/Posix functions.
940940
941+
.. _optin-taint-TaintedAlloc:
942+
943+
optin.taint.TaintedAlloc (C, C++)
944+
"""""""""""""""""""""""""""""""""
945+
946+
This checker warns for cases when the ``size`` parameter of the ``malloc`` ,
947+
``calloc``, ``realloc``, ``alloca`` or the size parameter of the
948+
array new C++ operator is tainted (potentially attacker controlled).
949+
If an attacker can inject a large value as the size parameter, memory exhaustion
950+
denial of service attack can be carried out.
951+
952+
The ``alpha.security.taint.TaintPropagation`` checker also needs to be enabled for
953+
this checker to give warnings.
954+
955+
The analyzer emits warning only if it cannot prove that the size parameter is
956+
within reasonable bounds (``<= SIZE_MAX/4``). This functionality partially
957+
covers the SEI Cert coding standard rule `INT04-C
958+
<https://wiki.sei.cmu.edu/confluence/display/c/INT04-C.+Enforce+limits+on+integer+values+originating+from+tainted+sources>`_.
959+
960+
You can silence this warning either by bound checking the ``size`` parameter, or
961+
by explicitly marking the ``size`` parameter as sanitized. See the
962+
:ref:`alpha-security-taint-TaintPropagation` checker for more details.
963+
964+
.. code-block:: c
965+
966+
void vulnerable(void) {
967+
size_t size = 0;
968+
scanf("%zu", &size);
969+
int *p = malloc(size); // warn: malloc is called with a tainted (potentially attacker controlled) value
970+
free(p);
971+
}
972+
973+
void not_vulnerable(void) {
974+
size_t size = 0;
975+
scanf("%zu", &size);
976+
if (1024 < size)
977+
return;
978+
int *p = malloc(size); // No warning expected as the the user input is bound
979+
free(p);
980+
}
981+
982+
void vulnerable_cpp(void) {
983+
size_t size = 0;
984+
scanf("%zu", &size);
985+
int *ptr = new int[size];// warn: Memory allocation function is called with a tainted (potentially attacker controlled) value
986+
delete[] ptr;
987+
}
941988
942989
.. _security-checkers:
943990

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@ def CoreAlpha : Package<"core">, ParentPackage<Alpha>;
3636
// Note: OptIn is *not* intended for checkers that are too noisy to be on by
3737
// default. Such checkers belong in the alpha package.
3838
def OptIn : Package<"optin">;
39+
3940
def CoreOptIn : Package<"core">, ParentPackage<OptIn>;
4041

4142
// In the Portability package reside checkers for finding code that relies on
4243
// implementation-defined behavior. Such checks are wanted for cross-platform
4344
// development, but unwanted for developers who target only a single platform.
4445
def PortabilityOptIn : Package<"portability">, ParentPackage<OptIn>;
4546

47+
// Optional checkers related to taint security analysis.
48+
def TaintOptIn : Package<"taint">, ParentPackage<OptIn>;
49+
4650
def Nullability : Package<"nullability">,
4751
PackageOptions<[
4852
CmdLineOption<Boolean,
@@ -1718,6 +1722,23 @@ def UnixAPIPortabilityChecker : Checker<"UnixAPI">,
17181722

17191723
} // end optin.portability
17201724

1725+
1726+
//===----------------------------------------------------------------------===//
1727+
// Taint checkers.
1728+
//===----------------------------------------------------------------------===//
1729+
1730+
let ParentPackage = TaintOptIn in {
1731+
1732+
def TaintedAllocChecker: Checker<"TaintedAlloc">,
1733+
HelpText<"Check for memory allocations, where the size parameter "
1734+
"might be a tainted (attacker controlled) value.">,
1735+
Dependencies<[DynamicMemoryModeling]>,
1736+
//FIXME: GenericTaintChecker should be a dependency, but only after it
1737+
//is transformed into a modeling checker
1738+
Documentation<HasDocumentation>;
1739+
1740+
} // end "optin.taint"
1741+
17211742
//===----------------------------------------------------------------------===//
17221743
// NonDeterminism checkers.
17231744
//===----------------------------------------------------------------------===//

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "clang/Basic/TargetInfo.h"
6161
#include "clang/Lex/Lexer.h"
6262
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
63+
#include "clang/StaticAnalyzer/Checkers/Taint.h"
6364
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
6465
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
6566
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -322,6 +323,7 @@ class MallocChecker
322323
CK_NewDeleteLeaksChecker,
323324
CK_MismatchedDeallocatorChecker,
324325
CK_InnerPointerChecker,
326+
CK_TaintedAllocChecker,
325327
CK_NumCheckKinds
326328
};
327329

@@ -365,6 +367,7 @@ class MallocChecker
365367
mutable std::unique_ptr<BugType> BT_MismatchedDealloc;
366368
mutable std::unique_ptr<BugType> BT_OffsetFree[CK_NumCheckKinds];
367369
mutable std::unique_ptr<BugType> BT_UseZerroAllocated[CK_NumCheckKinds];
370+
mutable std::unique_ptr<BugType> BT_TaintedAlloc;
368371

369372
#define CHECK_FN(NAME) \
370373
void NAME(const CallEvent &Call, CheckerContext &C) const;
@@ -462,6 +465,13 @@ class MallocChecker
462465
};
463466

464467
bool isMemCall(const CallEvent &Call) const;
468+
void reportTaintBug(StringRef Msg, ProgramStateRef State, CheckerContext &C,
469+
llvm::ArrayRef<SymbolRef> TaintedSyms,
470+
AllocationFamily Family) const;
471+
472+
void checkTaintedness(CheckerContext &C, const CallEvent &Call,
473+
const SVal SizeSVal, ProgramStateRef State,
474+
AllocationFamily Family) const;
465475

466476
// TODO: Remove mutable by moving the initializtaion to the registry function.
467477
mutable std::optional<uint64_t> KernelZeroFlagVal;
@@ -521,9 +531,9 @@ class MallocChecker
521531
/// malloc leaves it undefined.
522532
/// \param [in] State The \c ProgramState right before allocation.
523533
/// \returns The ProgramState right after allocation.
524-
[[nodiscard]] static ProgramStateRef
534+
[[nodiscard]] ProgramStateRef
525535
MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx,
526-
SVal Init, ProgramStateRef State, AllocationFamily Family);
536+
SVal Init, ProgramStateRef State, AllocationFamily Family) const;
527537

528538
/// Models memory allocation.
529539
///
@@ -534,9 +544,10 @@ class MallocChecker
534544
/// malloc leaves it undefined.
535545
/// \param [in] State The \c ProgramState right before allocation.
536546
/// \returns The ProgramState right after allocation.
537-
[[nodiscard]] static ProgramStateRef
538-
MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init,
539-
ProgramStateRef State, AllocationFamily Family);
547+
[[nodiscard]] ProgramStateRef MallocMemAux(CheckerContext &C,
548+
const CallEvent &Call, SVal Size,
549+
SVal Init, ProgramStateRef State,
550+
AllocationFamily Family) const;
540551

541552
// Check if this malloc() for special flags. At present that means M_ZERO or
542553
// __GFP_ZERO (in which case, treat it like calloc).
@@ -649,8 +660,9 @@ class MallocChecker
649660
/// \param [in] Call The expression that reallocated memory
650661
/// \param [in] State The \c ProgramState right before reallocation.
651662
/// \returns The ProgramState right after allocation.
652-
[[nodiscard]] static ProgramStateRef
653-
CallocMem(CheckerContext &C, const CallEvent &Call, ProgramStateRef State);
663+
[[nodiscard]] ProgramStateRef CallocMem(CheckerContext &C,
664+
const CallEvent &Call,
665+
ProgramStateRef State) const;
654666

655667
/// See if deallocation happens in a suspicious context. If so, escape the
656668
/// pointers that otherwise would have been deallocated and return true.
@@ -1695,6 +1707,11 @@ MallocChecker::processNewAllocation(const CXXAllocatorCall &Call,
16951707
// MallocUpdateRefState() instead of MallocMemAux() which breaks the
16961708
// existing binding.
16971709
SVal Target = Call.getObjectUnderConstruction();
1710+
if (Call.getOriginExpr()->isArray()) {
1711+
if (auto SizeEx = NE->getArraySize())
1712+
checkTaintedness(C, Call, C.getSVal(*SizeEx), State, AF_CXXNewArray);
1713+
}
1714+
16981715
State = MallocUpdateRefState(C, NE, State, Family, Target);
16991716
State = ProcessZeroAllocCheck(Call, 0, State, Target);
17001717
return State;
@@ -1779,18 +1796,74 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
17791796
const CallEvent &Call,
17801797
const Expr *SizeEx, SVal Init,
17811798
ProgramStateRef State,
1782-
AllocationFamily Family) {
1799+
AllocationFamily Family) const {
17831800
if (!State)
17841801
return nullptr;
17851802

17861803
assert(SizeEx);
17871804
return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Family);
17881805
}
17891806

1807+
void MallocChecker::reportTaintBug(StringRef Msg, ProgramStateRef State,
1808+
CheckerContext &C,
1809+
llvm::ArrayRef<SymbolRef> TaintedSyms,
1810+
AllocationFamily Family) const {
1811+
if (ExplodedNode *N = C.generateNonFatalErrorNode(State, this)) {
1812+
if (!BT_TaintedAlloc)
1813+
BT_TaintedAlloc.reset(new BugType(CheckNames[CK_TaintedAllocChecker],
1814+
"Tainted Memory Allocation",
1815+
categories::TaintedData));
1816+
auto R = std::make_unique<PathSensitiveBugReport>(*BT_TaintedAlloc, Msg, N);
1817+
for (auto TaintedSym : TaintedSyms) {
1818+
R->markInteresting(TaintedSym);
1819+
}
1820+
C.emitReport(std::move(R));
1821+
}
1822+
}
1823+
1824+
void MallocChecker::checkTaintedness(CheckerContext &C, const CallEvent &Call,
1825+
const SVal SizeSVal, ProgramStateRef State,
1826+
AllocationFamily Family) const {
1827+
if (!ChecksEnabled[CK_TaintedAllocChecker])
1828+
return;
1829+
std::vector<SymbolRef> TaintedSyms =
1830+
taint::getTaintedSymbols(State, SizeSVal);
1831+
if (TaintedSyms.empty())
1832+
return;
1833+
1834+
SValBuilder &SVB = C.getSValBuilder();
1835+
QualType SizeTy = SVB.getContext().getSizeType();
1836+
QualType CmpTy = SVB.getConditionType();
1837+
// In case the symbol is tainted, we give a warning if the
1838+
// size is larger than SIZE_MAX/4
1839+
BasicValueFactory &BVF = SVB.getBasicValueFactory();
1840+
const llvm::APSInt MaxValInt = BVF.getMaxValue(SizeTy);
1841+
NonLoc MaxLength =
1842+
SVB.makeIntVal(MaxValInt / APSIntType(MaxValInt).getValue(4));
1843+
std::optional<NonLoc> SizeNL = SizeSVal.getAs<NonLoc>();
1844+
auto Cmp = SVB.evalBinOpNN(State, BO_GE, *SizeNL, MaxLength, CmpTy)
1845+
.getAs<DefinedOrUnknownSVal>();
1846+
if (!Cmp)
1847+
return;
1848+
auto [StateTooLarge, StateNotTooLarge] = State->assume(*Cmp);
1849+
if (!StateTooLarge && StateNotTooLarge) {
1850+
// We can prove that size is not too large so there is no issue.
1851+
return;
1852+
}
1853+
1854+
std::string Callee = "Memory allocation function";
1855+
if (Call.getCalleeIdentifier())
1856+
Callee = Call.getCalleeIdentifier()->getName().str();
1857+
reportTaintBug(
1858+
Callee + " is called with a tainted (potentially attacker controlled) "
1859+
"value. Make sure the value is bound checked.",
1860+
State, C, TaintedSyms, Family);
1861+
}
1862+
17901863
ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
17911864
const CallEvent &Call, SVal Size,
17921865
SVal Init, ProgramStateRef State,
1793-
AllocationFamily Family) {
1866+
AllocationFamily Family) const {
17941867
if (!State)
17951868
return nullptr;
17961869

@@ -1819,9 +1892,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
18191892
if (Size.isUndef())
18201893
Size = UnknownVal();
18211894

1822-
// TODO: If Size is tainted and we cannot prove that it is within
1823-
// reasonable bounds, emit a warning that an attacker may
1824-
// provoke a memory exhaustion error.
1895+
checkTaintedness(C, Call, Size, State, AF_Malloc);
18251896

18261897
// Set the region's extent.
18271898
State = setDynamicExtent(State, RetVal.getAsRegion(),
@@ -2761,7 +2832,7 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call,
27612832

27622833
ProgramStateRef MallocChecker::CallocMem(CheckerContext &C,
27632834
const CallEvent &Call,
2764-
ProgramStateRef State) {
2835+
ProgramStateRef State) const {
27652836
if (!State)
27662837
return nullptr;
27672838

@@ -3734,3 +3805,4 @@ REGISTER_CHECKER(MallocChecker)
37343805
REGISTER_CHECKER(NewDeleteChecker)
37353806
REGISTER_CHECKER(NewDeleteLeaksChecker)
37363807
REGISTER_CHECKER(MismatchedDeallocatorChecker)
3808+
REGISTER_CHECKER(TaintedAllocChecker)

clang/test/Analysis/malloc.c

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// RUN: -analyzer-checker=alpha.deadcode.UnreachableCode \
44
// RUN: -analyzer-checker=alpha.core.CastSize \
55
// RUN: -analyzer-checker=unix \
6-
// RUN: -analyzer-checker=debug.ExprInspection
6+
// RUN: -analyzer-checker=debug.ExprInspection \
7+
// RUN: -analyzer-checker=alpha.security.taint.TaintPropagation \
8+
// RUN: -analyzer-checker=optin.taint.TaintedAlloc
79

810
#include "Inputs/system-header-simulator.h"
911

@@ -48,6 +50,45 @@ void myfoo(int *p);
4850
void myfooint(int p);
4951
char *fooRetPtr(void);
5052

53+
void t1(void) {
54+
size_t size = 0;
55+
scanf("%zu", &size);
56+
int *p = malloc(size); // expected-warning{{malloc is called with a tainted (potentially attacker controlled) value}}
57+
free(p);
58+
}
59+
60+
void t2(void) {
61+
size_t size = 0;
62+
scanf("%zu", &size);
63+
int *p = calloc(size,2); // expected-warning{{calloc is called with a tainted (potentially attacker controlled) value}}
64+
free(p);
65+
}
66+
67+
void t3(void) {
68+
size_t size = 0;
69+
scanf("%zu", &size);
70+
if (1024 < size)
71+
return;
72+
int *p = malloc(size); // No warning expected as the the user input is bound
73+
free(p);
74+
}
75+
76+
void t4(void) {
77+
size_t size = 0;
78+
int *p = malloc(sizeof(int));
79+
scanf("%zu", &size);
80+
p = (int*) realloc((void*) p, size); // expected-warning{{realloc is called with a tainted (potentially attacker controlled) value}}
81+
free(p);
82+
}
83+
84+
void t5(void) {
85+
size_t size = 0;
86+
int *p = alloca(sizeof(int));
87+
scanf("%zu", &size);
88+
p = (int*) alloca(size); // expected-warning{{alloca is called with a tainted (potentially attacker controlled) value}}
89+
}
90+
91+
5192
void f1(void) {
5293
int *p = malloc(12);
5394
return; // expected-warning{{Potential leak of memory pointed to by 'p'}}

0 commit comments

Comments
 (0)