Skip to content

[analyzer] Add optin.taint.TaintedDiv checker #106389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading