• R/O
  • HTTP
  • SSH
  • HTTPS

コミット

タグ
未設定

よく使われているワード(クリックで追加)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

コミットメタ情報

リビジョンd777b38cde91a87f2345dcd13901862a9513562a (tree)
日時2022-11-14 07:53:23
作者David Malcolm <dmalcolm@redh...>
コミッターDavid Malcolm

ログメッセージ

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>

変更サマリ

差分

--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -174,6 +174,10 @@ Wanalyzer-tainted-array-index
174174 Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
175175 Warn about code paths in which an unsanitized value is used as an array index.
176176
177+Wanalyzer-tainted-assertion
178+Common Var(warn_analyzer_tainted_assertion) Init(1) Warning
179+Warn about code paths in which an 'assert()' is made involving an unsanitized value.
180+
177181 Wanalyzer-tainted-divisor
178182 Common Var(warn_analyzer_tainted_divisor) Init(1) Warning
179183 Warn about code paths in which an unsanitized value is used as a divisor.
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -1316,7 +1316,7 @@ void
13161316 checker_path::fixup_locations (pending_diagnostic *pd)
13171317 {
13181318 for (checker_event *e : m_events)
1319- e->set_location (pd->fixup_location (e->get_location ()));
1319+ e->set_location (pd->fixup_location (e->get_location (), false));
13201320 }
13211321
13221322 /* Return true if there is a (start_cfg_edge_event, end_cfg_edge_event) pair
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -933,7 +933,7 @@ get_emission_location (const gimple *stmt, function *fun,
933933 location_t loc = get_stmt_location (stmt, fun);
934934
935935 /* Allow the pending_diagnostic to fix up the location. */
936- loc = pd.fixup_location (loc);
936+ loc = pd.fixup_location (loc, true);
937937
938938 return loc;
939939 }
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -153,7 +153,7 @@ fixup_location_in_macro_p (cpp_hashnode *macro)
153153 Don't unwind inside macros for which fixup_location_in_macro_p is true. */
154154
155155 location_t
156-pending_diagnostic::fixup_location (location_t loc) const
156+pending_diagnostic::fixup_location (location_t loc, bool) const
157157 {
158158 if (linemap_location_from_macro_expansion_p (line_table, loc))
159159 {
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -214,10 +214,10 @@ class pending_diagnostic
214214 diagnostic deduplication. */
215215 static bool same_tree_p (tree t1, tree t2);
216216
217- /* A vfunc for fixing up locations (both the primary location for the
218- diagnostic, and for events in their paths), e.g. to avoid unwinding
219- inside specific macros. */
220- virtual location_t fixup_location (location_t loc) const;
217+ /* Vfunc for fixing up locations, e.g. to avoid unwinding
218+ inside specific macros. PRIMARY is true for the primary location
219+ for the diagnostic, and FALSE for events in their paths. */
220+ virtual location_t fixup_location (location_t loc, bool primary) const;
221221
222222 /* For greatest precision-of-wording, the various following "describe_*"
223223 virtual functions give the pending diagnostic a way to describe events
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -109,6 +109,10 @@ public:
109109 state_t combine_states (state_t s0, state_t s1) const;
110110
111111 private:
112+ void check_control_flow_arg_for_taint (sm_context *sm_ctxt,
113+ const gimple *stmt,
114+ tree expr) const;
115+
112116 void check_for_tainted_size_arg (sm_context *sm_ctxt,
113117 const supernode *node,
114118 const gcall *call,
@@ -130,6 +134,9 @@ public:
130134
131135 /* Stop state, for a value we don't want to track any more. */
132136 state_t m_stop;
137+
138+ /* Global state, for when the last condition had tainted arguments. */
139+ state_t m_tainted_control_flow;
133140 };
134141
135142 /* Class for diagnostics relating to taint_state_machine. */
@@ -149,8 +156,7 @@ public:
149156 && m_has_bounds == other.m_has_bounds);
150157 }
151158
152- label_text describe_state_change (const evdesc::state_change &change)
153- final override
159+ label_text describe_state_change (const evdesc::state_change &change) override
154160 {
155161 if (change.m_new_state == m_sm.m_tainted)
156162 {
@@ -761,6 +767,100 @@ private:
761767 enum memory_space m_mem_space;
762768 };
763769
770+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
771+ value being used as part of the condition of an assertion. */
772+
773+class tainted_assertion : public taint_diagnostic
774+{
775+public:
776+ tainted_assertion (const taint_state_machine &sm, tree arg,
777+ tree assert_failure_fndecl)
778+ : taint_diagnostic (sm, arg, BOUNDS_NONE),
779+ m_assert_failure_fndecl (assert_failure_fndecl)
780+ {
781+ gcc_assert (m_assert_failure_fndecl);
782+ }
783+
784+ const char *get_kind () const final override
785+ {
786+ return "tainted_assertion";
787+ }
788+
789+ bool subclass_equal_p (const pending_diagnostic &base_other) const override
790+ {
791+ if (!taint_diagnostic::subclass_equal_p (base_other))
792+ return false;
793+ const tainted_assertion &other
794+ = (const tainted_assertion &)base_other;
795+ return m_assert_failure_fndecl == other.m_assert_failure_fndecl;
796+ }
797+
798+ int get_controlling_option () const final override
799+ {
800+ return OPT_Wanalyzer_tainted_assertion;
801+ }
802+
803+ bool emit (rich_location *rich_loc) final override
804+ {
805+ diagnostic_metadata m;
806+ /* "CWE-617: Reachable Assertion". */
807+ m.add_cwe (617);
808+
809+ return warning_meta (rich_loc, m, get_controlling_option (),
810+ "use of attacked-controlled value in"
811+ " condition for assertion");
812+ }
813+
814+ location_t fixup_location (location_t loc,
815+ bool primary) const final override
816+ {
817+ if (primary)
818+ /* For the primary location we want to avoid being in e.g. the
819+ <assert.h> system header, since this would suppress the
820+ diagnostic. */
821+ return expansion_point_location_if_in_system_header (loc);
822+ else if (in_system_header_at (loc))
823+ /* For events, we want to show the implemenation of the assert
824+ macro when we're describing them. */
825+ return linemap_resolve_location (line_table, loc,
826+ LRK_SPELLING_LOCATION,
827+ NULL);
828+ else
829+ return pending_diagnostic::fixup_location (loc, primary);
830+ }
831+
832+ label_text describe_state_change (const evdesc::state_change &change) override
833+ {
834+ if (change.m_new_state == m_sm.m_tainted_control_flow)
835+ return change.formatted_print
836+ ("use of attacker-controlled value for control flow");
837+ return taint_diagnostic::describe_state_change (change);
838+ }
839+
840+ label_text describe_final_event (const evdesc::final_event &ev) final override
841+ {
842+ if (mention_noreturn_attribute_p ())
843+ return ev.formatted_print
844+ ("treating %qE as an assertion failure handler"
845+ " due to %<__attribute__((__noreturn__))%>",
846+ m_assert_failure_fndecl);
847+ else
848+ return ev.formatted_print
849+ ("treating %qE as an assertion failure handler",
850+ m_assert_failure_fndecl);
851+ }
852+
853+private:
854+ bool mention_noreturn_attribute_p () const
855+ {
856+ if (fndecl_built_in_p (m_assert_failure_fndecl, BUILT_IN_UNREACHABLE))
857+ return false;
858+ return true;
859+ }
860+
861+ tree m_assert_failure_fndecl;
862+};
863+
764864 /* taint_state_machine's ctor. */
765865
766866 taint_state_machine::taint_state_machine (logger *logger)
@@ -770,6 +870,7 @@ taint_state_machine::taint_state_machine (logger *logger)
770870 m_has_lb = add_state ("has_lb");
771871 m_has_ub = add_state ("has_ub");
772872 m_stop = add_state ("stop");
873+ m_tainted_control_flow = add_state ("tainted-control-flow");
773874 }
774875
775876 state_machine::state_t
@@ -810,6 +911,15 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
810911 {
811912 default:
812913 break;
914+
915+ case EQ_EXPR:
916+ case GE_EXPR:
917+ case LE_EXPR:
918+ case NE_EXPR:
919+ case GT_EXPR:
920+ case LT_EXPR:
921+ case UNORDERED_EXPR:
922+ case ORDERED_EXPR:
813923 case PLUS_EXPR:
814924 case MINUS_EXPR:
815925 case MULT_EXPR:
@@ -823,17 +933,6 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
823933 }
824934 break;
825935
826- case EQ_EXPR:
827- case GE_EXPR:
828- case LE_EXPR:
829- case NE_EXPR:
830- case GT_EXPR:
831- case LT_EXPR:
832- case UNORDERED_EXPR:
833- case ORDERED_EXPR:
834- /* Comparisons are just booleans. */
835- return m_start;
836-
837936 case BIT_AND_EXPR:
838937 case RSHIFT_EXPR:
839938 return NULL;
@@ -844,6 +943,19 @@ taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
844943 return NULL;
845944 }
846945
946+/* Return true iff FNDECL should be considered to be an assertion failure
947+ handler by -Wanalyzer-tainted-assertion. */
948+
949+static bool
950+is_assertion_failure_handler_p (tree fndecl)
951+{
952+ // i.e. "noreturn"
953+ if (TREE_THIS_VOLATILE (fndecl))
954+ return true;
955+
956+ return false;
957+}
958+
847959 /* Implementation of state_machine::on_stmt vfunc for taint_state_machine. */
848960
849961 bool
@@ -871,6 +983,14 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
871983 /* External function with "access" attribute. */
872984 if (sm_ctxt->unknown_side_effects_p ())
873985 check_for_tainted_size_arg (sm_ctxt, node, call, callee_fndecl);
986+
987+ if (is_assertion_failure_handler_p (callee_fndecl)
988+ && sm_ctxt->get_global_state () == m_tainted_control_flow)
989+ {
990+ sm_ctxt->warn (node, call, NULL_TREE,
991+ make_unique<tainted_assertion> (*this, NULL_TREE,
992+ callee_fndecl));
993+ }
874994 }
875995 // TODO: ...etc; many other sources of untrusted data
876996
@@ -897,9 +1017,46 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
8971017 }
8981018 }
8991019
1020+ if (const gcond *cond = dyn_cast <const gcond *> (stmt))
1021+ {
1022+ /* Reset the state of "tainted-control-flow" before each
1023+ control flow statement, so that only the last one before
1024+ an assertion-failure-handler counts. */
1025+ sm_ctxt->set_global_state (m_start);
1026+ check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_lhs (cond));
1027+ check_control_flow_arg_for_taint (sm_ctxt, cond, gimple_cond_rhs (cond));
1028+ }
1029+
1030+ if (const gswitch *switch_ = dyn_cast <const gswitch *> (stmt))
1031+ {
1032+ /* Reset the state of "tainted-control-flow" before each
1033+ control flow statement, so that only the last one before
1034+ an assertion-failure-handler counts. */
1035+ sm_ctxt->set_global_state (m_start);
1036+ check_control_flow_arg_for_taint (sm_ctxt, switch_,
1037+ gimple_switch_index (switch_));
1038+ }
1039+
9001040 return false;
9011041 }
9021042
1043+/* If EXPR is tainted, mark this execution path with the
1044+ "tainted-control-flow" global state, in case we're about
1045+ to call an assertion-failure-handler. */
1046+
1047+void
1048+taint_state_machine::check_control_flow_arg_for_taint (sm_context *sm_ctxt,
1049+ const gimple *stmt,
1050+ tree expr) const
1051+{
1052+ const region_model *old_model = sm_ctxt->get_old_region_model ();
1053+ const svalue *sval = old_model->get_rvalue (expr, NULL);
1054+ state_t state = sm_ctxt->get_state (stmt, sval);
1055+ enum bounds b;
1056+ if (get_taint (state, TREE_TYPE (expr), &b))
1057+ sm_ctxt->set_global_state (m_tainted_control_flow);
1058+}
1059+
9031060 /* Implementation of state_machine::on_condition vfunc for taint_state_machine.
9041061 Potentially transition state 'tainted' to 'has_ub' or 'has_lb',
9051062 and states 'has_ub' and 'has_lb' to 'stop'. */
--- a/gcc/doc/gcc/gcc-command-options/option-summary.rst
+++ b/gcc/doc/gcc/gcc-command-options/option-summary.rst
@@ -309,6 +309,7 @@ in the following sections.
309309 :option:`-Wno-analyzer-shift-count-overflow` |gol|
310310 :option:`-Wno-analyzer-stale-setjmp-buffer` |gol|
311311 :option:`-Wno-analyzer-tainted-allocation-size` |gol|
312+ :option:`-Wno-analyzer-tainted-assertion` |gol|
312313 :option:`-Wno-analyzer-tainted-array-index` |gol|
313314 :option:`-Wno-analyzer-tainted-divisor` |gol|
314315 :option:`-Wno-analyzer-tainted-offset` |gol|
--- a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
+++ b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
@@ -549,6 +549,66 @@ Options That Control Static Analysis
549549
550550 Default setting; overrides :option:`-Wno-analyzer-tainted-allocation-size`.
551551
552+.. option:: -Wno-analyzer-tainted-assertion
553+
554+ This warning requires both :option:`-fanalyzer` and
555+ :option:`-fanalyzer-checker=taint` to enable it;
556+ use :option:`-Wno-analyzer-tainted-assertion` to disable it.
557+
558+ This diagnostic warns for paths through the code in which a value
559+ that could be under an attacker's control is used as part of a
560+ condition without being first sanitized, and that condition guards a
561+ call to a function marked with attribute :fn-attr:`noreturn`
562+ (such as the function ``__builtin_unreachable``). Such functions
563+ typically indicate abnormal termination of the program, such as for
564+ assertion failure handlers. For example:
565+
566+ .. code-block:: c
567+
568+ assert (some_tainted_value < SOME_LIMIT);
569+
570+ In such cases:
571+
572+ * when assertion-checking is enabled: an attacker could trigger
573+ a denial of service by injecting an assertion failure
574+
575+ * when assertion-checking is disabled, such as by defining ``NDEBUG``,
576+ an attacker could inject data that subverts the process, since it
577+ presumably violates a precondition that is being assumed by the code.
578+
579+ Note that when assertion-checking is disabled, the assertions are
580+ typically removed by the preprocessor before the analyzer has a chance
581+ to "see" them, so this diagnostic can only generate warnings on builds
582+ in which assertion-checking is enabled.
583+
584+ For the purpose of this warning, any function marked with attribute
585+ :fn-attr:`noreturn` is considered as a possible assertion failure
586+ handler, including ``__builtin_unreachable``. Note that these functions
587+ are sometimes removed by the optimizer before the analyzer "sees" them.
588+ Hence optimization should be disabled when attempting to trigger this
589+ diagnostic.
590+
591+ See `CWE-617: Reachable Assertion <https://cwe.mitre.org/data/definitions/617.html>`_.
592+
593+ The warning can also report problematic constructions such as
594+
595+ .. code-block:: c
596+
597+ switch (some_tainted_value) {
598+ case 0:
599+ /* [...etc; various valid cases omitted...] */
600+ break;
601+
602+ default:
603+ __builtin_unreachable (); /* BUG: attacker can trigger this */
604+ }
605+
606+ despite the above not being an assertion failure, strictly speaking.
607+
608+.. option:: -Wanalyzer-tainted-assertion
609+
610+ Default setting; overrides :option:`-Wno-analyzer-tainted-assertion`.
611+
552612 .. option:: -Wno-analyzer-tainted-array-index
553613
554614 This warning requires both :option:`-fanalyzer` and
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-assert-BUG_ON.c
@@ -0,0 +1,76 @@
1+// TODO: remove need for this option
2+/* { dg-additional-options "-fanalyzer-checker=taint" } */
3+
4+/* We need this, otherwise the warnings are emitted inside the macros, which
5+ makes it hard to write the DejaGnu directives. */
6+/* { dg-additional-options " -ftrack-macro-expansion=0" } */
7+
8+/* Adapted from code in the Linux kernel, which has this: */
9+/* SPDX-License-Identifier: GPL-2.0 */
10+
11+#define __noreturn __attribute__ ((__noreturn__))
12+
13+void panic(const char *fmt, ...) __noreturn;
14+
15+int _printk(const char *fmt, ...);
16+#define __printk_index_emit(...) do {} while (0)
17+#define printk_index_wrap(_p_func, _fmt, ...) \
18+ ({ \
19+ __printk_index_emit(_fmt, NULL, NULL); \
20+ _p_func(_fmt, ##__VA_ARGS__); \
21+ })
22+#define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__)
23+#define barrier_before_unreachable() do { } while (0)
24+
25+#define BUG() do { \
26+ printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \
27+ barrier_before_unreachable(); \
28+ panic("BUG!"); \
29+} while (0)
30+
31+#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
32+
33+void __attribute__((tainted_args))
34+test_BUG(int n)
35+{
36+ if (n > 100) /* { dg-message "use of attacker-controlled value for control flow" } */
37+ BUG(); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
38+ /* { dg-message "treating 'panic' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
39+}
40+
41+void __attribute__((tainted_args))
42+test_BUG_ON(int n)
43+{
44+ BUG_ON(n > 100); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
45+ /* { dg-message "treating 'panic' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
46+}
47+
48+int __attribute__((tainted_args))
49+test_switch_BUG_1(int n)
50+{
51+ switch (n) { /* { dg-message "use of attacker-controlled value for control flow" } */
52+ default:
53+ case 0:
54+ return 5;
55+ case 1:
56+ return 22;
57+ case 2:
58+ return -1;
59+ case 42:
60+ BUG (); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
61+ }
62+}
63+
64+int __attribute__((tainted_args))
65+test_switch_BUG(int n)
66+{
67+ switch (n) { /* { dg-message "use of attacker-controlled value for control flow" } */
68+ case 0:
69+ return 5;
70+ case 1:
71+ return 22;
72+ case 2:
73+ return -1;
74+ }
75+ BUG (); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
76+}
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-assert-macro-expansion.c
@@ -0,0 +1,96 @@
1+/* Integration test of how the execution path looks for
2+ -Wanalyzer-tainted-assertion with macro-tracking enabled
3+ (the default). */
4+
5+// TODO: remove need for this option
6+/* { dg-additional-options "-fanalyzer-checker=taint" } */
7+
8+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
9+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
10+
11+/* An assertion macro that has a call to a __noreturn__ function. */
12+
13+extern void my_assert_fail (const char *expr, const char *file, int line)
14+ __attribute__ ((__noreturn__));
15+
16+#define MY_ASSERT_1(EXPR) \
17+ 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\\\]" } */
18+
19+int __attribute__((tainted_args))
20+test_tainted_MY_ASSERT_1 (int n)
21+{
22+ MY_ASSERT_1 (n > 0);
23+ return n * n;
24+}
25+
26+/* { dg-begin-multiline-output "" }
27+ do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
28+ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29+ { dg-end-multiline-output "" } */
30+// note: in expansion of macro 'MY_ASSERT_1'
31+/* { dg-begin-multiline-output "" }
32+ MY_ASSERT_1 (n > 0);
33+ ^~~~~~~~~~~
34+ 'test_tainted_MY_ASSERT_1': event 1 (depth 0)
35+ |
36+ | test_tainted_MY_ASSERT_1 (int n)
37+ | ^~~~~~~~~~~~~~~~~~~~~~~~
38+ | |
39+ | (1) function 'test_tainted_MY_ASSERT_1' marked with '__attribute__((tainted_args))'
40+ |
41+ +--> 'test_tainted_MY_ASSERT_1': event 2 (depth 1)
42+ |
43+ | test_tainted_MY_ASSERT_1 (int n)
44+ | ^~~~~~~~~~~~~~~~~~~~~~~~
45+ | |
46+ | (2) entry to 'test_tainted_MY_ASSERT_1'
47+ |
48+ 'test_tainted_MY_ASSERT_1': event 3 (depth 1)
49+ |
50+ | do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
51+ | ^
52+ | |
53+ | (3) use of attacker-controlled value for control flow
54+ { dg-end-multiline-output "" } */
55+// note: in expansion of macro 'MY_ASSERT_1'
56+/* { dg-begin-multiline-output "" }
57+ | MY_ASSERT_1 (n > 0);
58+ | ^~~~~~~~~~~
59+ |
60+ 'test_tainted_MY_ASSERT_1': event 4 (depth 1)
61+ |
62+ | do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
63+ | ^
64+ | |
65+ | (4) following 'true' branch (when 'n <= 0')...
66+ { dg-end-multiline-output "" } */
67+// note: in expansion of macro 'MY_ASSERT_1'
68+/* { dg-begin-multiline-output "" }
69+ | MY_ASSERT_1 (n > 0);
70+ | ^~~~~~~~~~~
71+ |
72+ 'test_tainted_MY_ASSERT_1': event 5 (depth 1)
73+ |
74+ | do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
75+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76+ | |
77+ | (5) ...to here
78+ { dg-end-multiline-output "" } */
79+// note: in expansion of macro 'MY_ASSERT_1'
80+/* { dg-begin-multiline-output "" }
81+ | MY_ASSERT_1 (n > 0);
82+ | ^~~~~~~~~~~
83+ |
84+ 'test_tainted_MY_ASSERT_1': event 6 (depth 1)
85+ |
86+ | do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
87+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88+ | |
89+ | (6) treating 'my_assert_fail' as an assertion failure handler due to '__attribute__((__noreturn__))'
90+ { dg-end-multiline-output "" } */
91+// note: in expansion of macro 'MY_ASSERT_1'
92+/* { dg-begin-multiline-output "" }
93+ | MY_ASSERT_1 (n > 0);
94+ | ^~~~~~~~~~~
95+ |
96+ { dg-end-multiline-output "" } */
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-assert-system-header.c
@@ -0,0 +1,52 @@
1+/* Integration test of how the execution path looks for
2+ -Wanalyzer-tainted-assertion with macro-tracking enabled
3+ (the default), where the assertion macro is defined in
4+ a system header. */
5+
6+// TODO: remove need for this option
7+/* { dg-additional-options "-fanalyzer-checker=taint" } */
8+
9+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
10+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
11+
12+/* An assertion macro that has a call to a __noreturn__ function. */
13+
14+/* This is marked as a system header. */
15+#include "test-assert.h"
16+
17+int __attribute__((tainted_args))
18+test_tainted_assert (int n)
19+{
20+ assert (n > 0); /* { dg-warning "use of attacked-controlled value in condition for assertion \\\[CWE-617\\\] \\\[-Wanalyzer-tainted-assertion\\\]" } */
21+ return n * n;
22+}
23+
24+/* { dg-begin-multiline-output "" }
25+ assert (n > 0);
26+ ^~~~~~
27+ 'test_tainted_assert': event 1 (depth 0)
28+ |
29+ | test_tainted_assert (int n)
30+ | ^~~~~~~~~~~~~~~~~~~
31+ | |
32+ | (1) function 'test_tainted_assert' marked with '__attribute__((tainted_args))'
33+ |
34+ +--> 'test_tainted_assert': event 2 (depth 1)
35+ |
36+ | test_tainted_assert (int n)
37+ | ^~~~~~~~~~~~~~~~~~~
38+ | |
39+ | (2) entry to 'test_tainted_assert'
40+ |
41+ 'test_tainted_assert': events 3-6 (depth 1)
42+ |
43+ |
44+ | do { if (!(EXPR)) __assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
45+ | ^ ~~~~~~~~~~~~~
46+ | | |
47+ | | (5) ...to here
48+ | | (6) treating '__assert_fail' as an assertion failure handler due to '__attribute__((__noreturn__))'
49+ | (3) use of attacker-controlled value for control flow
50+ | (4) following 'true' branch (when 'n <= 0')...
51+ |
52+ { dg-end-multiline-output "" } */
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-assert.c
@@ -0,0 +1,346 @@
1+// TODO: remove need for this option
2+/* { dg-additional-options "-fanalyzer-checker=taint" } */
3+
4+/* We need this, otherwise the warnings are emitted inside the macros, which
5+ makes it hard to write the DejaGnu directives. */
6+/* { dg-additional-options " -ftrack-macro-expansion=0" } */
7+
8+#include "analyzer-decls.h"
9+
10+/* An assertion macro that has a call to a __noreturn__ function. */
11+
12+extern void my_assert_fail (const char *expr, const char *file, int line)
13+ __attribute__ ((__noreturn__));
14+
15+#define MY_ASSERT_1(EXPR) \
16+ do { if (!(EXPR)) my_assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
17+
18+int
19+test_not_tainted_MY_ASSERT_1 (int n)
20+{
21+ MY_ASSERT_1 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
22+ return n * n;
23+}
24+
25+int __attribute__((tainted_args))
26+test_tainted_MY_ASSERT_1 (int n)
27+{
28+ MY_ASSERT_1 (n > 0); /* { dg-warning "use of attacked-controlled value in condition for assertion \\\[CWE-617\\\] \\\[-Wanalyzer-tainted-assertion\\\]" "warning" } */
29+ /* { dg-message "treating 'my_assert_fail' as an assertion failure handler due to '__attribute__\\(\\(__noreturn__\\)\\)'" "final event" { target *-*-* } .-1 } */
30+ return n * n;
31+}
32+
33+
34+/* An assertion macro that has a call to __builtin_unreachable. */
35+
36+#define MY_ASSERT_2(EXPR) \
37+ do { if (!(EXPR)) __builtin_unreachable (); } while (0)
38+
39+int
40+test_not_tainted_MY_ASSERT_2 (int n)
41+{
42+ MY_ASSERT_2 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
43+ return n * n;
44+}
45+
46+int __attribute__((tainted_args))
47+test_tainted_MY_ASSERT_2 (int n)
48+{
49+ MY_ASSERT_2 (n > 0); /* { dg-warning "-Wanalyzer-tainted-assertion" "warning" } */
50+ /* { dg-message "treating '__builtin_unreachable' as an assertion failure handler" "final event" { target *-*-* } .-1 } */
51+ return n * n;
52+}
53+
54+
55+/* An assertion macro that's preprocessed away.
56+ The analyzer doesn't see this, so can't warn. */
57+
58+#define MY_ASSERT_3(EXPR) do { } while (0)
59+
60+int
61+test_not_tainted_MY_ASSERT_3 (int n)
62+{
63+ MY_ASSERT_3 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
64+ return n * n;
65+}
66+
67+int __attribute__((tainted_args))
68+test_tainted_MY_ASSERT_3 (int n)
69+{
70+ MY_ASSERT_3 (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
71+ return n * n;
72+}
73+
74+
75+/* A macro that isn't an assertion. */
76+
77+extern void do_something_benign ();
78+
79+#define NOT_AN_ASSERT(EXPR) \
80+ do { if (!(EXPR)) do_something_benign (); } while (0)
81+
82+int
83+test_not_tainted_NOT_AN_ASSERT (int n)
84+{
85+ NOT_AN_ASSERT (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
86+ return n * n;
87+}
88+
89+int __attribute__((tainted_args))
90+test_tainted_NOT_AN_ASSERT (int n)
91+{
92+ NOT_AN_ASSERT (n > 0); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
93+ return n * n;
94+}
95+
96+
97+/* A condition that isn't an assertion. */
98+
99+int __attribute__((tainted_args))
100+test_tainted_condition (int n)
101+{
102+ if (n > 0) /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
103+ return 1;
104+ else
105+ return -1;
106+}
107+
108+
109+/* More complicated expressions in assertions. */
110+
111+int g;
112+
113+void __attribute__((tainted_args))
114+test_compound_condition_in_assert_1 (int n)
115+{
116+ MY_ASSERT_1 ((n * 2) < (g + 3)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
117+}
118+
119+void __attribute__((tainted_args))
120+test_compound_condition_in_assert_2 (int x, int y)
121+{
122+ MY_ASSERT_1 (x < 100 && y < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
123+}
124+
125+void __attribute__((tainted_args))
126+test_compound_condition_in_assert_3 (int x, int y)
127+{
128+ MY_ASSERT_1 (x < 100 || y < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
129+}
130+
131+void __attribute__((tainted_args))
132+test_sanitized_expression_in_assert (int n)
133+{
134+ __analyzer_dump_state ("taint", n); /* { dg-warning "state: 'tainted'" } */
135+ if (n < 0 || n >= 100)
136+ return;
137+ __analyzer_dump_state ("taint", n); /* { dg-warning "state: 'stop'" } */
138+ MY_ASSERT_1 (n < 200); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
139+}
140+
141+void __attribute__((tainted_args))
142+test_sanitization_then_ok_assertion (unsigned n)
143+{
144+ if (n >= 100)
145+ return;
146+
147+ /* Shouldn't warn here, as g isn't attacker-controlled. */
148+ MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
149+}
150+
151+void __attribute__((tainted_args))
152+test_good_assert_then_bad_assert (unsigned n)
153+{
154+ /* Shouldn't warn here, as g isn't attacker-controlled. */
155+ MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
156+
157+ /* ...but n is: */
158+ MY_ASSERT_1 (n < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
159+}
160+
161+void __attribute__((tainted_args))
162+test_bad_assert_then_good_assert (unsigned n)
163+{
164+ MY_ASSERT_1 (n < 100); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
165+ MY_ASSERT_1 (g > 42); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
166+}
167+
168+
169+/* */
170+
171+void __attribute__((tainted_args))
172+test_zero_MY_ASSERT_1 (unsigned n)
173+{
174+ if (n >= 100)
175+ MY_ASSERT_1 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
176+}
177+
178+void __attribute__((tainted_args))
179+test_nonzero_MY_ASSERT_1 (unsigned n)
180+{
181+ if (n >= 100)
182+ MY_ASSERT_1 (1); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
183+}
184+
185+void __attribute__((tainted_args))
186+test_zero_MY_ASSERT_2 (unsigned n)
187+{
188+ if (n >= 100)
189+ MY_ASSERT_2 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
190+}
191+
192+void __attribute__((tainted_args))
193+test_nonzero_MY_ASSERT_2 (unsigned n)
194+{
195+ if (n >= 100)
196+ MY_ASSERT_2 (1); /* { dg-bogus "-Wanalyzer-tainted-assertion" } */
197+}
198+
199+
200+/* Assertions that call a subroutine to do validity checking. */
201+
202+static int
203+__analyzer_valid_1 (int x)
204+{
205+ return x < 100;
206+}
207+
208+void __attribute__((tainted_args))
209+test_assert_calling_valid_1 (int n)
210+{
211+ MY_ASSERT_1 (__analyzer_valid_1 (n)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
212+}
213+
214+static int
215+__analyzer_valid_2 (int x)
216+{
217+ return x < 100;
218+}
219+
220+void __attribute__((tainted_args))
221+test_assert_calling_valid_2 (int n)
222+{
223+ MY_ASSERT_1 (__analyzer_valid_2 (n)); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
224+}
225+
226+static int
227+__analyzer_valid_3 (int x, int y)
228+{
229+ if (x >= 100)
230+ return 0;
231+ if (y >= 100)
232+ return 0;
233+ return 1;
234+}
235+
236+void __attribute__((tainted_args))
237+test_assert_calling_valid_3 (int a, int b)
238+{
239+ MY_ASSERT_1 (__analyzer_valid_3 (a, b)); /* { dg-warning "-Wanalyzer-tainted-assertion" "TODO" { xfail *-*-* } } */
240+}
241+
242+
243+/* 'switch' statements with supposedly unreachable cases/defaults. */
244+
245+int __attribute__((tainted_args))
246+test_switch_default (int n)
247+{
248+ switch (n) /* { dg-message "use of attacker-controlled value for control flow" "why" } */
249+ /* { dg-message "following 'default:' branch" "dest" { target *-*-* } .-1 } */
250+ {
251+ case 0:
252+ return 5;
253+ case 1:
254+ return 22;
255+ case 2:
256+ return -1;
257+ default:
258+ /* The wording is rather inaccurate here. */
259+ __builtin_unreachable (); /* { dg-warning "use of attacked-controlled value in condition for assertion" } */
260+ }
261+}
262+
263+int __attribute__((tainted_args))
264+test_switch_unhandled_case (int n)
265+{
266+ switch (n) /* { dg-message "use of attacker-controlled value for control flow" "why" } */
267+ /* { dg-message "following 'default:' branch" "dest" { target *-*-* } .-1 } */
268+ {
269+ case 0:
270+ return 5;
271+ case 1:
272+ return 22;
273+ case 2:
274+ return -1;
275+ }
276+
277+ /* The wording is rather inaccurate here. */
278+ __builtin_unreachable (); /* { dg-warning "use of attacked-controlled value in condition for assertion" } */
279+}
280+
281+int __attribute__((tainted_args))
282+test_switch_bogus_case_MY_ASSERT_1 (int n)
283+{
284+ switch (n)
285+ {
286+ default:
287+ case 0:
288+ return 5;
289+ case 1:
290+ return 22;
291+ case 2:
292+ return -1;
293+ case 42:
294+ MY_ASSERT_1 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
295+ }
296+}
297+
298+int __attribute__((tainted_args))
299+test_switch_bogus_case_MY_ASSERT_2 (int n)
300+{
301+ switch (n)
302+ {
303+ default:
304+ case 0:
305+ return 5;
306+ case 1:
307+ return 22;
308+ case 2:
309+ return -1;
310+ case 42:
311+ MY_ASSERT_2 (0); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
312+ }
313+}
314+
315+int __attribute__((tainted_args))
316+test_switch_bogus_case_unreachable (int n)
317+{
318+ switch (n)
319+ {
320+ default:
321+ case 0:
322+ return 5;
323+ case 1:
324+ return 22;
325+ case 2:
326+ return -1;
327+ case 42:
328+ /* This case gets optimized away before we see it. */
329+ __builtin_unreachable ();
330+ }
331+}
332+
333+
334+/* Contents of a struct. */
335+
336+struct s
337+{
338+ int x;
339+ int y;
340+};
341+
342+int __attribute__((tainted_args))
343+test_assert_struct (struct s *p)
344+{
345+ MY_ASSERT_1 (p->x < p->y); /* { dg-warning "-Wanalyzer-tainted-assertion" } */
346+}
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/test-assert.h
@@ -0,0 +1,7 @@
1+#pragma GCC system_header
2+
3+extern void __assert_fail (const char *expr, const char *file, int line)
4+ __attribute__ ((__noreturn__));
5+
6+#define assert(EXPR) \
7+ do { if (!(EXPR)) __assert_fail (#EXPR, __FILE__, __LINE__); } while (0)
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
@@ -89,7 +89,8 @@ public:
8989 return 0;
9090 }
9191
92- location_t fixup_location (location_t loc) const final override
92+ location_t fixup_location (location_t loc,
93+ bool) const final override
9394 {
9495 /* Ideally we'd check for specific macros here, and only
9596 resolve certain macros. */