analyzer: new warning: -Wanalyzer-tainted-assertion [PR106235]
This patch adds a new -Wanalyzer-tainted-assertion warning to -fanalyzer's "taint" mode (which also requires -fanalyzer-checker=taint). It complains about attacker-controlled values being used in assertions, or in any expression affecting control flow that guards a "noreturn" function. As noted in the docs part of the patch, in such cases: - when assertion-checking is enabled: an attacker could trigger a denial of service by injecting an assertion failure - when assertion-checking is disabled, such as by defining NDEBUG, an attacker could inject data that subverts the process, since it presumably violates a precondition that is being assumed by the code. For example, given: #include <assert.h> int __attribute__((tainted_args)) test_tainted_assert (int n) { assert (n > 0); return n * n; } compiling with -fanalyzer -fanalyzer-checker=taint gives: t.c: In function 'test_tainted_assert': t.c:6:3: warning: use of attacked-controlled value in condition for assertion [CWE-617] [-Wanalyzer-tainted-assertion] 6 | assert (n > 0); | ^~~~~~ 'test_tainted_assert': event 1 | | 4 | test_tainted_assert (int n) | | ^~~~~~~~~~~~~~~~~~~ | | | | | (1) function 'test_tainted_assert' marked with '__attribute__((tainted_args))' | +--> 'test_tainted_assert': event 2 | | 4 | test_tainted_assert (int n) | | ^~~~~~~~~~~~~~~~~~~ | | | | | (2) entry to 'test_tainted_assert' | 'test_tainted_assert': events 3-6 | |/usr/include/assert.h:106:10: | 106 | if (expr) \ | | ^ | | | | | (3) use of attacker-controlled value for control flow | | (4) following 'false' branch (when 'n <= 0')... |...... | 109 | __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ | | ~~~~~~~~~~~~~ | | | | | (5) ...to here | | (6) treating '__assert_fail' as an assertion failure handler due to '__attribute__((__noreturn__))' | The testcases have various examples for BUG and BUG_ON from the Linux kernel; there, the diagnostic treats "panic" as an assertion failure handler, due to '__attribute__((__noreturn__))'. gcc/analyzer/ChangeLog: PR analyzer/106235 * analyzer.opt (Wanalyzer-tainted-assertion): New. * checker-path.cc (checker_path::fixup_locations): Pass false to pending_diagnostic::fixup_location. * diagnostic-manager.cc (get_emission_location): Pass true to pending_diagnostic::fixup_location. * pending-diagnostic.cc (pending_diagnostic::fixup_location): Add bool param. * pending-diagnostic.h (pending_diagnostic::fixup_location): Add bool param to decl. * sm-taint.cc (taint_state_machine::m_tainted_control_flow): New. (taint_diagnostic::describe_state_change): Drop "final". (class tainted_assertion): New. (taint_state_machine::taint_state_machine): Initialize m_tainted_control_flow. (taint_state_machine::alt_get_inherited_state): Support comparisons being tainted, based on their arguments. (is_assertion_failure_handler_p): New. (taint_state_machine::on_stmt): Complain about calls to assertion failure handlers guarded by an attacker-controller conditional. Detect attacker-controlled gcond conditionals and gswitch index values. (taint_state_machine::check_control_flow_arg_for_taint): New. gcc/ChangeLog: PR analyzer/106235 * doc/gcc/gcc-command-options/option-summary.rst: Add -Wno-analyzer-tainted-assertion. * doc/gcc/gcc-command-options/options-that-control-static-analysis.rst: Add -Wno-analyzer-tainted-assertion. gcc/testsuite/ChangeLog: PR analyzer/106235 * gcc.dg/analyzer/taint-assert-BUG_ON.c: New test. * gcc.dg/analyzer/taint-assert-macro-expansion.c: New test. * gcc.dg/analyzer/taint-assert.c: New test. * gcc.dg/analyzer/taint-assert-system-header.c: New test. * gcc.dg/analyzer/test-assert.h: New header. * gcc.dg/plugin/analyzer_gil_plugin.c (gil_diagnostic::fixup_location): Add bool param. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
parent
58e7732a2f
commit
d777b38cde
14 changed files with 821 additions and 21 deletions
|
@ -174,6 +174,10 @@ Wanalyzer-tainted-array-index
|
|||
Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
|
||||
Warn about code paths in which an unsanitized value is used as an array index.
|
||||
|
||||
Wanalyzer-tainted-assertion
|
||||
Common Var(warn_analyzer_tainted_assertion) Init(1) Warning
|
||||
Warn about code paths in which an 'assert()' is made involving an unsanitized value.
|
||||
|
||||
Wanalyzer-tainted-divisor
|
||||
Common Var(warn_analyzer_tainted_divisor) Init(1) Warning
|
||||
Warn about code paths in which an unsanitized value is used as a divisor.
|
||||
|
|
|
@ -1316,7 +1316,7 @@ void
|
|||
checker_path::fixup_locations (pending_diagnostic *pd)
|
||||
{
|
||||
for (checker_event *e : m_events)
|
||||
e->set_location (pd->fixup_location (e->get_location ()));
|
||||
e->set_location (pd->fixup_location (e->get_location (), false));
|
||||
}
|
||||
|
||||
/* Return true if there is a (start_cfg_edge_event, end_cfg_edge_event) pair
|
||||
|
|
|
@ -933,7 +933,7 @@ get_emission_location (const gimple *stmt, function *fun,
|
|||
location_t loc = get_stmt_location (stmt, fun);
|
||||
|
||||
/* Allow the pending_diagnostic to fix up the location. */
|
||||
loc = pd.fixup_location (loc);
|
||||
loc = pd.fixup_location (loc, true);
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ fixup_location_in_macro_p (cpp_hashnode *macro)
|
|||
Don't unwind inside macros for which fixup_location_in_macro_p is true. */
|
||||
|
||||
location_t
|
||||
pending_diagnostic::fixup_location (location_t loc) const
|
||||
pending_diagnostic::fixup_location (location_t loc, bool) const
|
||||
{
|
||||
if (linemap_location_from_macro_expansion_p (line_table, loc))
|
||||
{
|
||||
|
|
|
@ -214,10 +214,10 @@ class pending_diagnostic
|
|||
diagnostic deduplication. */
|
||||
static bool same_tree_p (tree t1, tree t2);
|
||||
|
||||
/* A vfunc for fixing up locations (both the primary location for the
|
||||
diagnostic, and for events in their paths), e.g. to avoid unwinding
|
||||
inside specific macros. */
|
||||
virtual location_t fixup_location (location_t loc) const;
|
||||
/* Vfunc for fixing up locations, e.g. to avoid unwinding
|
||||
inside specific macros. PRIMARY is true for the primary location
|
||||
for the diagnostic, and FALSE for events in their paths. */
|
||||
virtual location_t fixup_location (location_t loc, bool primary) const;
|
||||
|
||||
/* For greatest precision-of-wording, the various following "describe_*"
|
||||
virtual functions give the pending diagnostic a way to describe events
|
||||
|
|
|
@ -109,6 +109,10 @@ public:
|
|||
state_t combine_states (state_t s0, state_t s1) const;
|
||||
|
||||
private:
|
||||
void check_control_flow_arg_for_taint (sm_context *sm_ctxt,
|
||||
const gimple *stmt,
|
||||
tree expr) const;
|
||||
|
||||
void check_for_tainted_size_arg (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gcall *call,
|
||||
|
@ -130,6 +134,9 @@ public:
|
|||
|
||||
/* Stop state, for a value we don't want to track any more. */
|
||||
state_t m_stop;
|
||||
|
||||
/* Global state, for when the last condition had tainted arguments. */
|
||||
state_t m_tainted_control_flow;
|
||||
};
|
||||
|
||||
/* Class for diagnostics relating to taint_state_machine. */
|
||||
|
@ -149,8 +156,7 @@ public:
|
|||
&& m_has_bounds == other.m_has_bounds);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
final override
|
||||
label_text describe_state_change (const evdesc::state_change &change) override
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_tainted)
|
||||
{
|
||||
|
@ -761,6 +767,100 @@ private:
|
|||
enum memory_space m_mem_space;
|
||||
};
|
||||
|
||||
/* Concrete taint_diagnostic subclass for reporting attacker-controlled
|
||||
value being used as part of the condition of an assertion. */
|
||||
|
||||
class tainted_assertion : public taint_diagnostic
|
||||
{
|
||||
public:
|
||||
tainted_assertion (const taint_state_machine &sm, tree arg,
|
||||
tree assert_failure_fndecl)
|
||||
: taint_diagnostic (sm, arg, BOUNDS_NONE),
|
||||
m_assert_failure_fndecl (assert_failure_fndecl)
|
||||
{
|
||||
gcc_assert (m_assert_failure_fndecl);
|
||||
}
|
||||
|
||||
const char *get_kind () const final override
|
||||
{
|
||||
return "tainted_assertion";
|
||||
}
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const override
|
||||
{
|
||||
if (!taint_diagnostic::subclass_equal_p (base_other))
|
||||
return false;
|
||||
const tainted_assertion &other
|
||||
= (const tainted_assertion &)base_other;
|
||||
return m_assert_failure_fndecl == other.m_assert_failure_fndecl;
|
||||
}
|
||||
|
||||
int get_controlling_option () const final override
|
||||
{
|
||||
return OPT_Wanalyzer_tainted_assertion;
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) final override
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
/* "CWE-617: Reachable Assertion". */
|
||||
m.add_cwe (617);
|
||||
|
||||
return warning_meta (rich_loc, m, get_controlling_option (),
|
||||
"use of attacked-controlled value in"
|
||||
" condition for assertion");
|
||||
}
|
||||
|
||||
location_t fixup_location (location_t loc,
|
||||
bool primary) const final override
|
||||
{
|
||||
if (primary)
|
||||
/* For the primary location we want to avoid being in e.g. the
|
||||
<assert.h> system header, since this would suppress the
|
||||
diagnostic. */
|
||||
return expansion_point_location_if_in_system_header (loc);
|
||||
else if (in_system_header_at (loc))
|
||||
/* For events, we want to show the implemenation of the assert
|
||||
macro when we're describing them. */
|
||||
return linemap_resolve_location (line_table, loc,
|
||||
LRK_SPELLING_LOCATION,
|
||||
NULL);
|
||||
else
|
||||
return pending_diagnostic::fixup_location (loc, primary);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change) override
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_tainted_control_flow)
|
||||
return change.formatted_print
|
||||
("use of attacker-controlled value for control flow");
|
||||
return taint_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) final override
|
||||
{
|
||||
if (mention_noreturn_attribute_p ())
|
||||
return ev.formatted_print
|
||||
("treating %qE as an assertion failure handler"
|
||||
" due to %<__attribute__((__noreturn__))%>",
|
||||
m_assert_failure_fndecl);
|
||||
else
|
||||
return ev.formatted_print
|
||||
("treating %qE as an assertion failure handler",
|
||||
m_assert_failure_fndecl);
|
||||
}
|
||||
|
||||
private:
|
||||
bool mention_noreturn_attribute_p () const
|
||||
{
|
||||
if (fndecl_built_in_p (m_assert_failure_fndecl, BUILT_IN_UNREACHABLE))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
tree m_assert_failure_fndecl;
|
||||
};
|
||||
|
||||
/* taint_state_machine's ctor. */
|
||||
|
||||
taint_state_machine::taint_state_machine (logger *logger)
|
||||
|
@ -770,6 +870,7 @@ taint_state_machine::taint_state_machine (logger *logger)
|
|||
m_has_lb = add_state ("has_lb");
|
||||
m_has_ub = add_state ("has_ub");
|
||||
m_stop = add_state ("stop");
|
||||
m_tainted_control_flow = add_state ("tainted-control-flow");
|
||||
}
|
||||
|
||||
state_machine::state_t
|
||||
|
@ -810,6 +911,15 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
|
|||
{
|
||||
default:
|
||||
break;
|
||||
|
||||
case EQ_EXPR:
|
||||
case GE_EXPR:
|
||||
case LE_EXPR:
|
||||
case NE_EXPR:
|
||||
case GT_EXPR:
|
||||
case LT_EXPR:
|
||||
case UNORDERED_EXPR:
|
||||
case ORDERED_EXPR:
|
||||
case PLUS_EXPR:
|
||||
case MINUS_EXPR:
|
||||
case MULT_EXPR:
|
||||
|
@ -823,17 +933,6 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
|
|||
}
|
||||
break;
|
||||
|
||||
case EQ_EXPR:
|
||||
case GE_EXPR:
|
||||
case LE_EXPR:
|
||||
case NE_EXPR:
|
||||
case GT_EXPR:
|
||||
case LT_EXPR:
|
||||
case UNORDERED_EXPR:
|
||||
case ORDERED_EXPR:
|
||||
/* Comparisons are just booleans. */
|
||||
return m_start;
|
||||
|
||||
case BIT_AND_EXPR:
|
||||
case RSHIFT_EXPR:
|
||||
return NULL;
|
||||
|
@ -844,6 +943,19 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Return true iff FNDECL should be considered to be an assertion failure
|
||||
handler by -Wanalyzer-tainted-assertion. */
|
||||
|
||||
static bool
|
||||
is_assertion_failure_handler_p (tree fndecl)
|
||||
{
|
||||
// i.e. "noreturn"
|
||||
if (TREE_THIS_VOLATILE (fndecl))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for taint_state_machine. */
|
||||
|
||||
bool
|
||||
|
@ -871,6 +983,14 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
/* External function with "access" attribute. */
|
||||
if (sm_ctxt->unknown_side_effects_p ())
|
||||
check_for_tainted_size_arg (sm_ctxt, node, call, callee_fndecl);
|
||||
|
||||
if (is_assertion_failure_handler_p (callee_fndecl)
|
||||
&& sm_ctxt->get_global_state () == m_tainted_control_flow)
|
||||
{
|
||||
sm_ctxt->warn (node, call, NULL_TREE,
|
||||
make_unique<tainted_assertion> (*this, NULL_TREE,
|
||||
callee_fndecl));
|
||||
}
|
||||
}
|
||||
// TODO: ...etc; many other sources of untrusted data
|
||||
|
||||
|
@ -897,9 +1017,46 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
}
|
||||
}
|
||||
|
||||
if (const gcond *cond = dyn_cast <const gcond *> (stmt))
|
||||
{
|
||||
/* Reset the state of "tainted-control-flow" before each
|
||||
control flow statement, so that only the last one before
|
||||
an assertion-failure-handler counts. */
|
||||
sm_ctxt->set_global_state (m_start);
|
||||
check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_lhs (cond));
|
||||
check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_rhs (cond));
|
||||
}
|
||||
|
||||
if (const gswitch *switch_ = dyn_cast <const gswitch *> (stmt))
|
||||
{
|
||||
/* Reset the state of "tainted-control-flow" before each
|
||||
control flow statement, so that only the last one before
|
||||
an assertion-failure-handler counts. */
|
||||
sm_ctxt->set_global_state (m_start);
|
||||
check_control_flow_arg_for_taint (sm_ctxt, switch_,
|
||||
gimple_switch_index (switch_));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If EXPR is tainted, mark this execution path with the
|
||||
"tainted-control-flow" global state, in case we're about
|
||||
to call an assertion-failure-handler. */
|
||||
|
||||
void
|
||||
taint_state_machine::check_control_flow_arg_for_taint (sm_context *sm_ctxt,
|
||||
const gimple *stmt,
|
||||
tree expr) const
|
||||
{
|
||||
const region_model *old_model = sm_ctxt->get_old_region_model ();
|
||||
const svalue *sval = old_model->get_rvalue (expr, NULL);
|
||||
state_t state = sm_ctxt->get_state (stmt, sval);
|
||||
enum bounds b;
|
||||
if (get_taint (state, TREE_TYPE (expr), &b))
|
||||
sm_ctxt->set_global_state (m_tainted_control_flow);
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for taint_state_machine.
|
||||
Potentially transition state 'tainted' to 'has_ub' or 'has_lb',
|
||||
and states 'has_ub' and 'has_lb' to 'stop'. */
|
||||
|
|
|
@ -309,6 +309,7 @@ in the following sections.
|
|||
:option:`-Wno-analyzer-shift-count-overflow` |gol|
|
||||
:option:`-Wno-analyzer-stale-setjmp-buffer` |gol|
|
||||
:option:`-Wno-analyzer-tainted-allocation-size` |gol|
|
||||
:option:`-Wno-analyzer-tainted-assertion` |gol|
|
||||
:option:`-Wno-analyzer-tainted-array-index` |gol|
|
||||
:option:`-Wno-analyzer-tainted-divisor` |gol|
|
||||
:option:`-Wno-analyzer-tainted-offset` |gol|
|
||||
|
|
|
@ -549,6 +549,66 @@ Options That Control Static Analysis
|
|||
|
||||
Default setting; overrides :option:`-Wno-analyzer-tainted-allocation-size`.
|
||||
|
||||
.. option:: -Wno-analyzer-tainted-assertion
|
||||
|
||||
This warning requires both :option:`-fanalyzer` and
|
||||
:option:`-fanalyzer-checker=taint` to enable it;
|
||||
use :option:`-Wno-analyzer-tainted-assertion` to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a value
|
||||
that could be under an attacker's control is used as part of a
|
||||
condition without being first sanitized, and that condition guards a
|
||||
call to a function marked with attribute :fn-attr:`noreturn`
|
||||
(such as the function ``__builtin_unreachable``). Such functions
|
||||
typically indicate abnormal termination of the program, such as for
|
||||
assertion failure handlers. For example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
assert (some_tainted_value < SOME_LIMIT);
|
||||
|
||||
In such cases:
|
||||
|
||||
* when assertion-checking is enabled: an attacker could trigger
|
||||
a denial of service by injecting an assertion failure
|
||||
|
||||
* when assertion-checking is disabled, such as by defining ``NDEBUG``,
|
||||
an attacker could inject data that subverts the process, since it
|
||||
presumably violates a precondition that is being assumed by the code.
|
||||
|
||||
Note that when assertion-checking is disabled, the assertions are
|
||||
typically removed by the preprocessor before the analyzer has a chance
|
||||
to "see" them, so this diagnostic can only generate warnings on builds
|
||||
in which assertion-checking is enabled.
|
||||
|
||||
For the purpose of this warning, any function marked with attribute
|
||||
:fn-attr:`noreturn` is considered as a possible assertion failure
|
||||
handler, including ``__builtin_unreachable``. Note that these functions
|
||||
are sometimes removed by the optimizer before the analyzer "sees" them.
|
||||
Hence optimization should be disabled when attempting to trigger this
|
||||
diagnostic.
|
||||
|
||||
See `CWE-617: Reachable Assertion <https://cwe.mitre.org/data/definitions/617.html>`_.
|
||||
|
||||
The warning can also report problematic constructions such as
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
switch (some_tainted_value) {
|
||||
case 0:
|
||||
/* [...etc; various valid cases omitted...] */
|
||||
break;
|
||||
|
||||
default:
|
||||
__builtin_unreachable (); /* BUG: attacker can trigger this */
|
||||
}
|
||||
|
||||
despite the above not being an assertion failure, strictly speaking.
|
||||
|
||||
.. option:: -Wanalyzer-tainted-assertion
|
||||
|
||||
Default setting; overrides :option:`-Wno-analyzer-tainted-assertion`.
|
||||
|
||||
.. option:: -Wno-analyzer-tainted-array-index
|
||||
|
||||
This warning requires both :option:`-fanalyzer` and
|
||||
|
|
76
gcc/testsuite/gcc.dg/analyzer/taint-assert-BUG_ON.c
Normal file
76
gcc/testsuite/gcc.dg/analyzer/taint-assert-BUG_ON.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
// TODO: remove need for this option
|
||||
/* { dg-additional-options "-fanalyzer-checker=taint" } */
|
||||
|
||||
/* We need this, otherwise the warnings are emitted inside the macros, which
|
||||
makes it hard to write the DejaGnu directives. */
|
||||
/* { dg-additional-options " -ftrack-macro-expansion=0" } */
|
||||
|
||||
/* Adapted from code in the Linux kernel, which has this: */
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#define __noreturn __attribute__ ((__noreturn__))
|
||||
|
||||
void panic(const char *fmt, ...) __noreturn;
|
||||
|
||||
int _printk(const char *fmt, ...);
|
||||
#define __printk_index_emit(...) do {} while (0)
|
||||
#define printk_index_wrap(_p_func, _fmt, ...) \
|
||||
({ \
|
||||
__printk_index_emit(_fmt, NULL, NULL); \
|
||||
_p_func(_fmt, ##__VA_ARGS__); \
|
||||
})
|
||||
#define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__)
|
||||
#define barrier_before_unreachable() do { } while (0)
|
||||
|
||||
#define BUG() do { \
|
||||
printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \
|
||||
barrier_before_unreachable(); \
|
||||
panic("BUG!"); \
|
||||
} while (0)
|
||||
|
||||
#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_BUG(int n)
|
||||
{
|
||||
if (n > 100) /* { dg-message "use of attacker-controlled value for control flow" } */
|
||||
BUG(); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
|
||||
/* { dg-message "treating 'panic' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_BUG_ON(int n)
|
||||
{
|
||||
BUG_ON(n > 100); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
|
||||
/* { dg-message "treating 'panic' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_BUG_1(int n)
|
||||
{
|
||||
switch (n) { /* { dg-message "use of attacker-controlled value for control flow" } */
|
||||
default:
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
case 42:
|
||||
BUG (); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_BUG(int n)
|
||||
{
|
||||
switch (n) { /* { dg-message "use of attacker-controlled value for control flow" } */
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
}
|
||||
BUG (); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
96
gcc/testsuite/gcc.dg/analyzer/taint-assert-macro-expansion.c
Normal file
96
gcc/testsuite/gcc.dg/analyzer/taint-assert-macro-expansion.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
/* Integration test of how the execution path looks for
|
||||
-Wanalyzer-tainted-assertion with macro-tracking enabled
|
||||
(the default). */
|
||||
|
||||
// TODO: remove need for this option
|
||||
/* { dg-additional-options "-fanalyzer-checker=taint" } */
|
||||
|
||||
/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
|
||||
/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
|
||||
|
||||
/* An assertion macro that has a call to a __noreturn__ function. */
|
||||
|
||||
extern void my_assert_fail (const char *expr, const char *file, int line)
|
||||
__attribute__ ((__noreturn__));
|
||||
|
||||
#define MY_ASSERT_1(EXPR) \
|
||||
do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0) /* { dg-warning "use of attacked-controlled value in condition for assertion \\\[CWE-617\\\] \\\[-Wanalyzer-tainted-assertion\\\]" } */
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_MY_ASSERT_1 (int n)
|
||||
{
|
||||
MY_ASSERT_1 (n > 0);
|
||||
return n * n;
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
{ dg-end-multiline-output "" } */
|
||||
// note: in expansion of macro 'MY_ASSERT_1'
|
||||
/* { dg-begin-multiline-output "" }
|
||||
MY_ASSERT_1 (n > 0);
|
||||
^~~~~~~~~~~
|
||||
'test_tainted_MY_ASSERT_1': event 1 (depth 0)
|
||||
|
|
||||
| test_tainted_MY_ASSERT_1 (int n)
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (1) function 'test_tainted_MY_ASSERT_1' marked with '__attribute__((tainted_args))'
|
||||
|
|
||||
+--> 'test_tainted_MY_ASSERT_1': event 2 (depth 1)
|
||||
|
|
||||
| test_tainted_MY_ASSERT_1 (int n)
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (2) entry to 'test_tainted_MY_ASSERT_1'
|
||||
|
|
||||
'test_tainted_MY_ASSERT_1': event 3 (depth 1)
|
||||
|
|
||||
| do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
| ^
|
||||
| |
|
||||
| (3) use of attacker-controlled value for control flow
|
||||
{ dg-end-multiline-output "" } */
|
||||
// note: in expansion of macro 'MY_ASSERT_1'
|
||||
/* { dg-begin-multiline-output "" }
|
||||
| MY_ASSERT_1 (n > 0);
|
||||
| ^~~~~~~~~~~
|
||||
|
|
||||
'test_tainted_MY_ASSERT_1': event 4 (depth 1)
|
||||
|
|
||||
| do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
| ^
|
||||
| |
|
||||
| (4) following 'true' branch (when 'n <= 0')...
|
||||
{ dg-end-multiline-output "" } */
|
||||
// note: in expansion of macro 'MY_ASSERT_1'
|
||||
/* { dg-begin-multiline-output "" }
|
||||
| MY_ASSERT_1 (n > 0);
|
||||
| ^~~~~~~~~~~
|
||||
|
|
||||
'test_tainted_MY_ASSERT_1': event 5 (depth 1)
|
||||
|
|
||||
| do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (5) ...to here
|
||||
{ dg-end-multiline-output "" } */
|
||||
// note: in expansion of macro 'MY_ASSERT_1'
|
||||
/* { dg-begin-multiline-output "" }
|
||||
| MY_ASSERT_1 (n > 0);
|
||||
| ^~~~~~~~~~~
|
||||
|
|
||||
'test_tainted_MY_ASSERT_1': event 6 (depth 1)
|
||||
|
|
||||
| do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (6) treating 'my_assert_fail' as an assertion failure handler due to '__attribute__((__noreturn__))'
|
||||
{ dg-end-multiline-output "" } */
|
||||
// note: in expansion of macro 'MY_ASSERT_1'
|
||||
/* { dg-begin-multiline-output "" }
|
||||
| MY_ASSERT_1 (n > 0);
|
||||
| ^~~~~~~~~~~
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
52
gcc/testsuite/gcc.dg/analyzer/taint-assert-system-header.c
Normal file
52
gcc/testsuite/gcc.dg/analyzer/taint-assert-system-header.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* Integration test of how the execution path looks for
|
||||
-Wanalyzer-tainted-assertion with macro-tracking enabled
|
||||
(the default), where the assertion macro is defined in
|
||||
a system header. */
|
||||
|
||||
// TODO: remove need for this option
|
||||
/* { dg-additional-options "-fanalyzer-checker=taint" } */
|
||||
|
||||
/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
|
||||
/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
|
||||
|
||||
/* An assertion macro that has a call to a __noreturn__ function. */
|
||||
|
||||
/* This is marked as a system header. */
|
||||
#include "test-assert.h"
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_assert (int n)
|
||||
{
|
||||
assert (n > 0); /* { dg-warning "use of attacked-controlled value in condition for assertion \\\[CWE-617\\\] \\\[-Wanalyzer-tainted-assertion\\\]" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
assert (n > 0);
|
||||
^~~~~~
|
||||
'test_tainted_assert': event 1 (depth 0)
|
||||
|
|
||||
| test_tainted_assert (int n)
|
||||
| ^~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (1) function 'test_tainted_assert' marked with '__attribute__((tainted_args))'
|
||||
|
|
||||
+--> 'test_tainted_assert': event 2 (depth 1)
|
||||
|
|
||||
| test_tainted_assert (int n)
|
||||
| ^~~~~~~~~~~~~~~~~~~
|
||||
| |
|
||||
| (2) entry to 'test_tainted_assert'
|
||||
|
|
||||
'test_tainted_assert': events 3-6 (depth 1)
|
||||
|
|
||||
|
|
||||
| do { if (!(EXPR)) __assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
| ^ ~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) ...to here
|
||||
| | (6) treating '__assert_fail' as an assertion failure handler due to '__attribute__((__noreturn__))'
|
||||
| (3) use of attacker-controlled value for control flow
|
||||
| (4) following 'true' branch (when 'n <= 0')...
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
346
gcc/testsuite/gcc.dg/analyzer/taint-assert.c
Normal file
346
gcc/testsuite/gcc.dg/analyzer/taint-assert.c
Normal file
|
@ -0,0 +1,346 @@
|
|||
// TODO: remove need for this option
|
||||
/* { dg-additional-options "-fanalyzer-checker=taint" } */
|
||||
|
||||
/* We need this, otherwise the warnings are emitted inside the macros, which
|
||||
makes it hard to write the DejaGnu directives. */
|
||||
/* { dg-additional-options " -ftrack-macro-expansion=0" } */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
/* An assertion macro that has a call to a __noreturn__ function. */
|
||||
|
||||
extern void my_assert_fail (const char *expr, const char *file, int line)
|
||||
__attribute__ ((__noreturn__));
|
||||
|
||||
#define MY_ASSERT_1(EXPR) \
|
||||
do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
||||
|
||||
int
|
||||
test_not_tainted_MY_ASSERT_1 (int n)
|
||||
{
|
||||
MY_ASSERT_1 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_MY_ASSERT_1 (int n)
|
||||
{
|
||||
MY_ASSERT_1 (n > 0); /* { dg-warning "use of attacked-controlled value in condition for assertion \\\[CWE-617\\\] \\\[-Wanalyzer-tainted-assertion\\\]" "warning" } */
|
||||
/* { dg-message "treating 'my_assert_fail' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
|
||||
/* An assertion macro that has a call to __builtin_unreachable. */
|
||||
|
||||
#define MY_ASSERT_2(EXPR) \
|
||||
do { if (!(EXPR)) __builtin_unreachable (); } while (0)
|
||||
|
||||
int
|
||||
test_not_tainted_MY_ASSERT_2 (int n)
|
||||
{
|
||||
MY_ASSERT_2 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_MY_ASSERT_2 (int n)
|
||||
{
|
||||
MY_ASSERT_2 (n > 0); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
|
||||
/* { dg-message "treating '__builtin_unreachable' as an assertion failure handler" "final event" { target *-*-* } .-1 } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
|
||||
/* An assertion macro that's preprocessed away.
|
||||
The analyzer doesn't see this, so can't warn. */
|
||||
|
||||
#define MY_ASSERT_3(EXPR) do { } while (0)
|
||||
|
||||
int
|
||||
test_not_tainted_MY_ASSERT_3 (int n)
|
||||
{
|
||||
MY_ASSERT_3 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_MY_ASSERT_3 (int n)
|
||||
{
|
||||
MY_ASSERT_3 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
|
||||
/* A macro that isn't an assertion. */
|
||||
|
||||
extern void do_something_benign ();
|
||||
|
||||
#define NOT_AN_ASSERT(EXPR) \
|
||||
do { if (!(EXPR)) do_something_benign (); } while (0)
|
||||
|
||||
int
|
||||
test_not_tainted_NOT_AN_ASSERT (int n)
|
||||
{
|
||||
NOT_AN_ASSERT (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_NOT_AN_ASSERT (int n)
|
||||
{
|
||||
NOT_AN_ASSERT (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return n * n;
|
||||
}
|
||||
|
||||
|
||||
/* A condition that isn't an assertion. */
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_tainted_condition (int n)
|
||||
{
|
||||
if (n > 0) /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* More complicated expressions in assertions. */
|
||||
|
||||
int g;
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_compound_condition_in_assert_1 (int n)
|
||||
{
|
||||
MY_ASSERT_1 ((n * 2) < (g + 3)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_compound_condition_in_assert_2 (int x, int y)
|
||||
{
|
||||
MY_ASSERT_1 (x < 100 && y < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_compound_condition_in_assert_3 (int x, int y)
|
||||
{
|
||||
MY_ASSERT_1 (x < 100 || y < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_sanitized_expression_in_assert (int n)
|
||||
{
|
||||
__analyzer_dump_state ("taint", n); /* { dg-warning "state: 'tainted'" } */
|
||||
if (n < 0 || n >= 100)
|
||||
return;
|
||||
__analyzer_dump_state ("taint", n); /* { dg-warning "state: 'stop'" } */
|
||||
MY_ASSERT_1 (n < 200); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_sanitization_then_ok_assertion (unsigned n)
|
||||
{
|
||||
if (n >= 100)
|
||||
return;
|
||||
|
||||
/* Shouldn't warn here, as g isn't attacker-controlled. */
|
||||
MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_good_assert_then_bad_assert (unsigned n)
|
||||
{
|
||||
/* Shouldn't warn here, as g isn't attacker-controlled. */
|
||||
MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
|
||||
/* ...but n is: */
|
||||
MY_ASSERT_1 (n < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_bad_assert_then_good_assert (unsigned n)
|
||||
{
|
||||
MY_ASSERT_1 (n < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
|
||||
/* */
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_zero_MY_ASSERT_1 (unsigned n)
|
||||
{
|
||||
if (n >= 100)
|
||||
MY_ASSERT_1 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_nonzero_MY_ASSERT_1 (unsigned n)
|
||||
{
|
||||
if (n >= 100)
|
||||
MY_ASSERT_1 (1); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_zero_MY_ASSERT_2 (unsigned n)
|
||||
{
|
||||
if (n >= 100)
|
||||
MY_ASSERT_2 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_nonzero_MY_ASSERT_2 (unsigned n)
|
||||
{
|
||||
if (n >= 100)
|
||||
MY_ASSERT_2 (1); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
|
||||
/* Assertions that call a subroutine to do validity checking. */
|
||||
|
||||
static int
|
||||
__analyzer_valid_1 (int x)
|
||||
{
|
||||
return x < 100;
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_assert_calling_valid_1 (int n)
|
||||
{
|
||||
MY_ASSERT_1 (__analyzer_valid_1 (n)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
static int
|
||||
__analyzer_valid_2 (int x)
|
||||
{
|
||||
return x < 100;
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_assert_calling_valid_2 (int n)
|
||||
{
|
||||
MY_ASSERT_1 (__analyzer_valid_2 (n)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
|
||||
static int
|
||||
__analyzer_valid_3 (int x, int y)
|
||||
{
|
||||
if (x >= 100)
|
||||
return 0;
|
||||
if (y >= 100)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void __attribute__((tainted_args))
|
||||
test_assert_calling_valid_3 (int a, int b)
|
||||
{
|
||||
MY_ASSERT_1 (__analyzer_valid_3 (a, b)); /* { dg-warning "-Wanalyzer-tainted-assertion" "TODO" { xfail *-*-* } } */
|
||||
}
|
||||
|
||||
|
||||
/* 'switch' statements with supposedly unreachable cases/defaults. */
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_default (int n)
|
||||
{
|
||||
switch (n) /* { dg-message "use of attacker-controlled value for control flow" "why" } */
|
||||
/* { dg-message "following 'default:' branch" "dest" { target *-*-* } .-1 } */
|
||||
{
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
default:
|
||||
/* The wording is rather inaccurate here. */
|
||||
__builtin_unreachable (); /* { dg-warning "use of attacked-controlled value in condition for assertion" } */
|
||||
}
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_unhandled_case (int n)
|
||||
{
|
||||
switch (n) /* { dg-message "use of attacker-controlled value for control flow" "why" } */
|
||||
/* { dg-message "following 'default:' branch" "dest" { target *-*-* } .-1 } */
|
||||
{
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The wording is rather inaccurate here. */
|
||||
__builtin_unreachable (); /* { dg-warning "use of attacked-controlled value in condition for assertion" } */
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_bogus_case_MY_ASSERT_1 (int n)
|
||||
{
|
||||
switch (n)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
case 42:
|
||||
MY_ASSERT_1 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_bogus_case_MY_ASSERT_2 (int n)
|
||||
{
|
||||
switch (n)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
case 42:
|
||||
MY_ASSERT_2 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
||||
}
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_switch_bogus_case_unreachable (int n)
|
||||
{
|
||||
switch (n)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
return 5;
|
||||
case 1:
|
||||
return 22;
|
||||
case 2:
|
||||
return -1;
|
||||
case 42:
|
||||
/* This case gets optimized away before we see it. */
|
||||
__builtin_unreachable ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Contents of a struct. */
|
||||
|
||||
struct s
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
int __attribute__((tainted_args))
|
||||
test_assert_struct (struct s *p)
|
||||
{
|
||||
MY_ASSERT_1 (p->x < p->y); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
|
||||
}
|
7
gcc/testsuite/gcc.dg/analyzer/test-assert.h
Normal file
7
gcc/testsuite/gcc.dg/analyzer/test-assert.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma GCC system_header
|
||||
|
||||
extern void __assert_fail (const char *expr, const char *file, int line)
|
||||
__attribute__ ((__noreturn__));
|
||||
|
||||
#define assert(EXPR) \
|
||||
do { if (!(EXPR)) __assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
|
|
@ -89,7 +89,8 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
location_t fixup_location (location_t loc) const final override
|
||||
location_t fixup_location (location_t loc,
|
||||
bool) const final override
|
||||
{
|
||||
/* Ideally we'd check for specific macros here, and only
|
||||
resolve certain macros. */
|
||||
|
|
Loading…
Add table
Reference in a new issue