Skip to content

Commit

Permalink
[analyzer] Add optin.taint.TaintedDiv checker (llvm#106389)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dkrupp authored Oct 1, 2024
1 parent 308c9a9 commit 09b8dbf
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 19 deletions.
28 changes: 28 additions & 0 deletions clang/docs/analyzer/checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,34 @@ by explicitly marking the ``size`` parameter as sanitized. See the
delete[] ptr;
}
.. _optin-taint-TaintedDiv:
optin.taint.TaintedDiv (C, C++, ObjC)
"""""""""""""""""""""""""""""""""""""
This checker warns when the denominator in a division
operation is a tainted (potentially attacker controlled) value.
If the attacker can set the denominator to 0, a runtime error can
be triggered. The checker warns when the denominator is a tainted
value and the analyzer cannot prove that it is not 0. This warning
is more pessimistic than the :ref:`core-DivideZero` checker
which warns only when it can prove that the denominator is 0.
.. code-block:: c
int vulnerable(int n) {
size_t size = 0;
scanf("%zu", &size);
return n / size; // warn: Division by a tainted value, possibly zero
}
int not_vulnerable(int n) {
size_t size = 0;
scanf("%zu", &size);
if (!size)
return 0;
return n / size; // no warning
}
.. _security-checkers:
security
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,12 @@ def TaintedAllocChecker: Checker<"TaintedAlloc">,
Dependencies<[DynamicMemoryModeling, TaintPropagationChecker]>,
Documentation<HasDocumentation>;

def TaintedDivChecker: Checker<"TaintedDiv">,
HelpText<"Check for divisions where the denominator is tainted "
"(attacker controlled) and might be 0.">,
Dependencies<[TaintPropagationChecker]>,
Documentation<HasDocumentation>;

} // end "optin.taint"

//===----------------------------------------------------------------------===//
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ class CheckerManager {
return static_cast<CHECKER *>(CheckerTags[tag]);
}

template <typename CHECKER> bool isRegisteredChecker() {
return CheckerTags.contains(getTag<CHECKER>());
}

//===----------------------------------------------------------------------===//
// Functions for running checkers for AST traversing.
//===----------------------------------------------------------------------===//
Expand Down
53 changes: 44 additions & 9 deletions clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@ using namespace ento;
using namespace taint;

namespace {
class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > {
const BugType BT{this, "Division by zero"};
const BugType TaintBT{this, "Division by zero", categories::TaintedData};
class DivZeroChecker : public Checker<check::PreStmt<BinaryOperator>> {
void reportBug(StringRef Msg, ProgramStateRef StateZero,
CheckerContext &C) const;
void reportTaintBug(StringRef Msg, ProgramStateRef StateZero,
CheckerContext &C,
llvm::ArrayRef<SymbolRef> TaintedSyms) const;

public:
/// This checker class implements several user facing checkers
enum CheckKind { CK_DivideZero, CK_TaintedDivChecker, CK_NumCheckKinds };
bool ChecksEnabled[CK_NumCheckKinds] = {false};
CheckerNameRef CheckNames[CK_NumCheckKinds];
mutable std::unique_ptr<BugType> BugTypes[CK_NumCheckKinds];

void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
};
} // end anonymous namespace
Expand All @@ -48,8 +52,14 @@ static const Expr *getDenomExpr(const ExplodedNode *N) {

void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
CheckerContext &C) const {
if (!ChecksEnabled[CK_DivideZero])
return;
if (!BugTypes[CK_DivideZero])
BugTypes[CK_DivideZero].reset(
new BugType(CheckNames[CK_DivideZero], "Division by zero"));
if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
auto R = std::make_unique<PathSensitiveBugReport>(*BugTypes[CK_DivideZero],
Msg, N);
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
C.emitReport(std::move(R));
}
Expand All @@ -58,8 +68,15 @@ void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero,
void DivZeroChecker::reportTaintBug(
StringRef Msg, ProgramStateRef StateZero, CheckerContext &C,
llvm::ArrayRef<SymbolRef> TaintedSyms) const {
if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
auto R = std::make_unique<PathSensitiveBugReport>(TaintBT, Msg, N);
if (!ChecksEnabled[CK_TaintedDivChecker])
return;
if (!BugTypes[CK_TaintedDivChecker])
BugTypes[CK_TaintedDivChecker].reset(
new BugType(CheckNames[CK_TaintedDivChecker], "Division by zero",
categories::TaintedData));
if (ExplodedNode *N = C.generateNonFatalErrorNode(StateZero)) {
auto R = std::make_unique<PathSensitiveBugReport>(
*BugTypes[CK_TaintedDivChecker], Msg, N);
bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
for (auto Sym : TaintedSyms)
R->markInteresting(Sym);
Expand Down Expand Up @@ -101,8 +118,8 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
if ((stateNotZero && stateZero)) {
std::vector<SymbolRef> taintedSyms = getTaintedSymbols(C.getState(), *DV);
if (!taintedSyms.empty()) {
reportTaintBug("Division by a tainted value, possibly zero", stateZero, C,
taintedSyms);
reportTaintBug("Division by a tainted value, possibly zero", stateNotZero,
C, taintedSyms);
return;
}
}
Expand All @@ -113,9 +130,27 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B,
}

void ento::registerDivZeroChecker(CheckerManager &mgr) {
mgr.registerChecker<DivZeroChecker>();
DivZeroChecker *checker = mgr.registerChecker<DivZeroChecker>();
checker->ChecksEnabled[DivZeroChecker::CK_DivideZero] = true;
checker->CheckNames[DivZeroChecker::CK_DivideZero] =
mgr.getCurrentCheckerName();
}

bool ento::shouldRegisterDivZeroChecker(const CheckerManager &mgr) {
return true;
}

void ento::registerTaintedDivChecker(CheckerManager &mgr) {
DivZeroChecker *checker;
if (!mgr.isRegisteredChecker<DivZeroChecker>())
checker = mgr.registerChecker<DivZeroChecker>();
else
checker = mgr.getChecker<DivZeroChecker>();
checker->ChecksEnabled[DivZeroChecker::CK_TaintedDivChecker] = true;
checker->CheckNames[DivZeroChecker::CK_TaintedDivChecker] =
mgr.getCurrentCheckerName();
}

bool ento::shouldRegisterTaintedDivChecker(const CheckerManager &mgr) {
return true;
}
34 changes: 34 additions & 0 deletions clang/test/Analysis/divzero-tainted-div-difference.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
// RUN: -Wno-incompatible-library-redeclaration -verify=normaldiv %s \
// RUN: -analyzer-checker=optin.taint.GenericTaint \
// RUN: -analyzer-checker=core

// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
// RUN: -Wno-incompatible-library-redeclaration -verify=tainteddiv %s \
// RUN: -analyzer-checker=optin.taint.GenericTaint \
// RUN: -analyzer-checker=optin.taint.TaintedDiv

int getchar(void);


//If we are sure that we divide by zero
//we emit a divide by zero warning
int testDivZero(void) {
int x = getchar(); // taint source
if (!x)
return 5 / x; // normaldiv-warning{{Division by zero}}
return 8;
}

// The attacker provided value might be 0
int testDivZero2(void) {
int x = getchar(); // taint source
return 5 / x; // tainteddiv-warning{{Division by a tainted value}}
}

int testDivZero3(void) {
int x = getchar(); // taint source
if (!x)
return 0;
return 5 / x; // no warning
}
2 changes: 1 addition & 1 deletion clang/test/Analysis/taint-diagnostic-visitor.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=optin.taint,core,alpha.security.ArrayBoundV2,optin.taint.TaintedAlloc -analyzer-output=text -verify %s
// RUN: %clang_cc1 -analyze -analyzer-checker=optin.taint,core,alpha.security.ArrayBoundV2 -analyzer-output=text -verify %s

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

Expand Down
22 changes: 13 additions & 9 deletions clang/test/Analysis/taint-generic.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// RUN: %clang_analyze_cc1 -Wno-format-security -Wno-pointer-to-int-cast \
// RUN: -Wno-incompatible-library-redeclaration -verify %s \
// RUN: -analyzer-checker=optin.taint.GenericTaint \
// RUN: -analyzer-checker=optin.taint.TaintedDiv \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \
// RUN: -analyzer-checker=debug.ExprInspection \
Expand All @@ -11,16 +12,15 @@
// RUN: -Wno-incompatible-library-redeclaration -verify %s \
// RUN: -DFILE_IS_STRUCT \
// RUN: -analyzer-checker=optin.taint.GenericTaint \
// RUN: -analyzer-checker=optin.taint.TaintedDiv \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \
// RUN: -analyzer-checker=debug.ExprInspection \
// RUN: -analyzer-config \
// RUN: optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml

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

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

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

int testTaintedDivFP(void) {
int x;
scanf("%d", &x);
if (!x)
return 0;
return 5/x; // x cannot be 0, so no tainted warning either
}

// Zero-sized VLAs.
void testTaintedVLASize(void) {
int x;
Expand Down

0 comments on commit 09b8dbf

Please sign in to comment.