Skip to content

Commit 09b8dbf

Browse files
authored
[analyzer] Add optin.taint.TaintedDiv checker (#106389)
Tainted division operation is separated out from the core.DivideZero checker into the optional optin.taint.TaintedDiv checker. The checker warns when the denominator in a division operation is an attacker controlled value.
1 parent 308c9a9 commit 09b8dbf

File tree

7 files changed

+130
-19
lines changed

7 files changed

+130
-19
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,34 @@ by explicitly marking the ``size`` parameter as sanitized. See the
12881288
delete[] ptr;
12891289
}
12901290
1291+
.. _optin-taint-TaintedDiv:
1292+
1293+
optin.taint.TaintedDiv (C, C++, ObjC)
1294+
"""""""""""""""""""""""""""""""""""""
1295+
This checker warns when the denominator in a division
1296+
operation is a tainted (potentially attacker controlled) value.
1297+
If the attacker can set the denominator to 0, a runtime error can
1298+
be triggered. The checker warns when the denominator is a tainted
1299+
value and the analyzer cannot prove that it is not 0. This warning
1300+
is more pessimistic than the :ref:`core-DivideZero` checker
1301+
which warns only when it can prove that the denominator is 0.
1302+
1303+
.. code-block:: c
1304+
1305+
int vulnerable(int n) {
1306+
size_t size = 0;
1307+
scanf("%zu", &size);
1308+
return n / size; // warn: Division by a tainted value, possibly zero
1309+
}
1310+
1311+
int not_vulnerable(int n) {
1312+
size_t size = 0;
1313+
scanf("%zu", &size);
1314+
if (!size)
1315+
return 0;
1316+
return n / size; // no warning
1317+
}
1318+
12911319
.. _security-checkers:
12921320
12931321
security

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,12 @@ def TaintedAllocChecker: Checker<"TaintedAlloc">,
17031703
Dependencies<[DynamicMemoryModeling, TaintPropagationChecker]>,
17041704
Documentation<HasDocumentation>;
17051705

1706+
def TaintedDivChecker: Checker<"TaintedDiv">,
1707+
HelpText<"Check for divisions where the denominator is tainted "
1708+
"(attacker controlled) and might be 0.">,
1709+
Dependencies<[TaintPropagationChecker]>,
1710+
Documentation<HasDocumentation>;
1711+
17061712
} // end "optin.taint"
17071713

17081714
//===----------------------------------------------------------------------===//

clang/include/clang/StaticAnalyzer/Core/CheckerManager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ class CheckerManager {
221221
return static_cast<CHECKER *>(CheckerTags[tag]);
222222
}
223223

224+
template <typename CHECKER> bool isRegisteredChecker() {
225+
return CheckerTags.contains(getTag<CHECKER>());
226+
}
227+
224228
//===----------------------------------------------------------------------===//
225229
// Functions for running checkers for AST traversing.
226230
//===----------------------------------------------------------------------===//

clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,20 @@ using namespace ento;
2525
using namespace taint;
2626

2727
namespace {
28-
class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > {
29-
const BugType BT{this, "Division by zero"};
30-
const BugType TaintBT{this, "Division by zero", categories::TaintedData};
28+
class DivZeroChecker : public Checker<check::PreStmt<BinaryOperator>> {
3129
void reportBug(StringRef Msg, ProgramStateRef StateZero,
3230
CheckerContext &C) const;
3331
void reportTaintBug(StringRef Msg, ProgramStateRef StateZero,
3432
CheckerContext &C,
3533
llvm::ArrayRef<SymbolRef> TaintedSyms) const;
3634

3735
public:
36+
/// This checker class implements several user facing checkers
37+
enum CheckKind { CK_DivideZero, CK_TaintedDivChecker, CK_NumCheckKinds };
38+
bool ChecksEnabled[CK_NumCheckKinds] = {false};
39+
CheckerNameRef CheckNames[CK_NumCheckKinds];
40+
mutable std::unique_ptr<BugType> BugTypes[CK_NumCheckKinds];
41+
3842
void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
3943
};
4044
} // end anonymous namespace
@@ -48,8 +52,14 @@ static const Expr *getDenomExpr(const ExplodedNode *N) {
4852

4953
void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
5054
CheckerContext &C) const {
55+
if (!ChecksEnabled[CK_DivideZero])
56+
return;
57+
if (!BugTypes[CK_DivideZero])
58+
BugTypes[CK_DivideZero].reset(
59+
new BugType(CheckNames[CK_DivideZero], "Division by zero"));
5160
if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
52-
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
61+
auto R = std::make_unique<PathSensitiveBugReport>(*BugTypes[CK_DivideZero],
62+
Msg, N);
5363
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
5464
C.emitReport(std::move(R));
5565
}
@@ -58,8 +68,15 @@ void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
5868
void DivZeroChecker::reportTaintBug(
5969
StringRef Msg, ProgramStateRef StateZero, CheckerContext &C,
6070
llvm::ArrayRef<SymbolRef> TaintedSyms) const {
61-
if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
62-
auto R = std::make_unique<PathSensitiveBugReport>(TaintBT, Msg, N);
71+
if (!ChecksEnabled[CK_TaintedDivChecker])
72+
return;
73+
if (!BugTypes[CK_TaintedDivChecker])
74+
BugTypes[CK_TaintedDivChecker].reset(
75+
new BugType(CheckNames[CK_TaintedDivChecker], "Division by zero",
76+
categories::TaintedData));
77+
if (ExplodedNode *N = C.generateNonFatalErrorNode(StateZero)) {
78+
auto R = std::make_unique<PathSensitiveBugReport>(
79+
*BugTypes[CK_TaintedDivChecker], Msg, N);
6380
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
6481
for (auto Sym : TaintedSyms)
6582
R->markInteresting(Sym);
@@ -101,8 +118,8 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
101118
if ((stateNotZero && stateZero)) {
102119
std::vector<SymbolRef> taintedSyms = getTaintedSymbols(C.getState(), *DV);
103120
if (!taintedSyms.empty()) {
104-
reportTaintBug("Division by a tainted value, possibly zero", stateZero, C,
105-
taintedSyms);
121+
reportTaintBug("Division by a tainted value, possibly zero", stateNotZero,
122+
C, taintedSyms);
106123
return;
107124
}
108125
}
@@ -113,9 +130,27 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
113130
}
114131

115132
void ento::registerDivZeroChecker(CheckerManager &mgr) {
116-
mgr.registerChecker<DivZeroChecker>();
133+
DivZeroChecker *checker = mgr.registerChecker<DivZeroChecker>();
134+
checker->ChecksEnabled[DivZeroChecker::CK_DivideZero] = true;
135+
checker->CheckNames[DivZeroChecker::CK_DivideZero] =
136+
mgr.getCurrentCheckerName();
117137
}
118138

119139
bool ento::shouldRegisterDivZeroChecker(const CheckerManager &mgr) {
120140
return true;
121141
}
142+
143+
void ento::registerTaintedDivChecker(CheckerManager &mgr) {
144+
DivZeroChecker *checker;
145+
if (!mgr.isRegisteredChecker<DivZeroChecker>())
146+
checker = mgr.registerChecker<DivZeroChecker>();
147+
else
148+
checker = mgr.getChecker<DivZeroChecker>();
149+
checker->ChecksEnabled[DivZeroChecker::CK_TaintedDivChecker] = true;
150+
checker->CheckNames[DivZeroChecker::CK_TaintedDivChecker] =
151+
mgr.getCurrentCheckerName();
152+
}
153+
154+
bool ento::shouldRegisterTaintedDivChecker(const CheckerManager &mgr) {
155+
return true;
156+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
2+
// RUN: -Wno-incompatible-library-redeclaration -verify=normaldiv %s \
3+
// RUN: -analyzer-checker=optin.taint.GenericTaint \
4+
// RUN: -analyzer-checker=core
5+
6+
// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
7+
// RUN: -Wno-incompatible-library-redeclaration -verify=tainteddiv %s \
8+
// RUN: -analyzer-checker=optin.taint.GenericTaint \
9+
// RUN: -analyzer-checker=optin.taint.TaintedDiv
10+
11+
int getchar(void);
12+
13+
14+
//If we are sure that we divide by zero
15+
//we emit a divide by zero warning
16+
int testDivZero(void) {
17+
int x = getchar(); // taint source
18+
if (!x)
19+
return 5 / x; // normaldiv-warning{{Division by zero}}
20+
return 8;
21+
}
22+
23+
// The attacker provided value might be 0
24+
int testDivZero2(void) {
25+
int x = getchar(); // taint source
26+
return 5 / x; // tainteddiv-warning{{Division by a tainted value}}
27+
}
28+
29+
int testDivZero3(void) {
30+
int x = getchar(); // taint source
31+
if (!x)
32+
return 0;
33+
return 5 / x; // no warning
34+
}

clang/test/Analysis/taint-diagnostic-visitor.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -analyze -analyzer-checker=optin.taint,core,alpha.security.ArrayBoundV2,optin.taint.TaintedAlloc -analyzer-output=text -verify %s
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=optin.taint,core,alpha.security.ArrayBoundV2 -analyzer-output=text -verify %s
22

33
// This file is for testing enhanced diagnostics produced by the GenericTaintChecker
44

clang/test/Analysis/taint-generic.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
22
// RUN: -Wno-incompatible-library-redeclaration -verify %s \
33
// RUN: -analyzer-checker=optin.taint.GenericTaint \
4+
// RUN: -analyzer-checker=optin.taint.TaintedDiv \
45
// RUN: -analyzer-checker=core \
56
// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \
67
// RUN: -analyzer-checker=debug.ExprInspection \
@@ -11,16 +12,15 @@
1112
// RUN: -Wno-incompatible-library-redeclaration -verify %s \
1213
// RUN: -DFILE_IS_STRUCT \
1314
// RUN: -analyzer-checker=optin.taint.GenericTaint \
15+
// RUN: -analyzer-checker=optin.taint.TaintedDiv \
1416
// RUN: -analyzer-checker=core \
1517
// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \
1618
// RUN: -analyzer-checker=debug.ExprInspection \
1719
// RUN: -analyzer-config \
1820
// RUN: optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml
1921

20-
// RUN: not %clang_analyze_cc1 -Wno-pointer-to-int-cast \
21-
// RUN: -Wno-incompatible-library-redeclaration -verify %s \
22+
// RUN: not %clang_analyze_cc1 -verify %s \
2223
// RUN: -analyzer-checker=optin.taint.GenericTaint \
23-
// RUN: -analyzer-checker=debug.ExprInspection \
2424
// RUN: -analyzer-config \
2525
// RUN: optin.taint.TaintPropagation:Config=justguessit \
2626
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-FILE
@@ -30,10 +30,8 @@
3030
// CHECK-INVALID-FILE-SAME: that expects a valid filename instead of
3131
// CHECK-INVALID-FILE-SAME: 'justguessit'
3232

33-
// RUN: not %clang_analyze_cc1 -Wno-incompatible-library-redeclaration \
34-
// RUN: -verify %s \
33+
// RUN: not %clang_analyze_cc1 -verify %s \
3534
// RUN: -analyzer-checker=optin.taint.GenericTaint \
36-
// RUN: -analyzer-checker=debug.ExprInspection \
3735
// RUN: -analyzer-config \
3836
// RUN: optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-ill-formed.yaml \
3937
// RUN: 2>&1 | FileCheck -DMSG=%errc_EINVAL %s -check-prefix=CHECK-ILL-FORMED
@@ -42,10 +40,8 @@
4240
// CHECK-ILL-FORMED-SAME: 'optin.taint.TaintPropagation:Config',
4341
// CHECK-ILL-FORMED-SAME: that expects a valid yaml file: [[MSG]]
4442

45-
// RUN: not %clang_analyze_cc1 -Wno-incompatible-library-redeclaration \
46-
// RUN: -verify %s \
43+
// RUN: not %clang_analyze_cc1 -verify %s \
4744
// RUN: -analyzer-checker=optin.taint.GenericTaint \
48-
// RUN: -analyzer-checker=debug.ExprInspection \
4945
// RUN: -analyzer-config \
5046
// RUN: optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-invalid-arg.yaml \
5147
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-ARG
@@ -408,6 +404,14 @@ int testDivByZero(void) {
408404
return 5/x; // expected-warning {{Division by a tainted value, possibly zero}}
409405
}
410406

407+
int testTaintedDivFP(void) {
408+
int x;
409+
scanf("%d", &x);
410+
if (!x)
411+
return 0;
412+
return 5/x; // x cannot be 0, so no tainted warning either
413+
}
414+
411415
// Zero-sized VLAs.
412416
void testTaintedVLASize(void) {
413417
int x;

0 commit comments

Comments
 (0)