@@ -114,10 +114,26 @@ Wanalyzer-stale-setjmp-buffer
Common Var(warn_analyzer_stale_setjmp_buffer) Init(1) Warning
Warn about code paths in which a longjmp rewinds to a jmp_buf saved in a stack frame that has returned.
+Wanalyzer-tainted-allocation-size
+Common Var(warn_analyzer_tainted_allocation_size) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as an allocation size.
+
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-divisor
+Common Var(warn_analyzer_tainted_divisor) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as a divisor.
+
+Wanalyzer-tainted-offset
+Common Var(warn_analyzer_tainted_offset) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as a pointer offset.
+
+Wanalyzer-tainted-size
+Common Var(warn_analyzer_tainted_size) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as a size.
+
Wanalyzer-use-after-free
Common Var(warn_analyzer_use_after_free) Init(1) Warning
Warn about code paths in which a freed value is used.
@@ -220,6 +220,24 @@ impl_region_model_context::get_malloc_map (sm_state_map **out_smap,
return true;
}
+bool
+impl_region_model_context::get_taint_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx)
+{
+ if (!m_new_state)
+ return false;
+
+ unsigned taint_sm_idx;
+ if (!m_ext_state.get_sm_idx_by_name ("taint", &taint_sm_idx))
+ return false;
+
+ *out_smap = m_new_state->m_checker_states[taint_sm_idx];
+ *out_sm = &m_ext_state.get_sm (taint_sm_idx);
+ *out_sm_idx = taint_sm_idx;
+ return true;
+}
+
/* struct setjmp_record. */
int
@@ -86,6 +86,9 @@ class impl_region_model_context : public region_model_context
bool get_malloc_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx) FINAL OVERRIDE;
+ bool get_taint_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx) FINAL OVERRIDE;
exploded_graph *m_eg;
log_user m_logger;
@@ -420,6 +420,10 @@ sm_state_map::get_state (const svalue *sval,
}
}
+ if (state_machine::state_t state
+ = m_sm.alt_get_inherited_state (*this, sval, ext_state))
+ return state;
+
return m_sm.get_default_state (sval);
}
@@ -495,6 +499,18 @@ sm_state_map::impl_set_state (const svalue *sval,
gcc_assert (sval->can_have_associated_state_p ());
+ if (m_sm.inherited_state_p ())
+ {
+ if (const compound_svalue *compound_sval
+ = sval->dyn_cast_compound_svalue ())
+ for (auto iter : *compound_sval)
+ {
+ const svalue *inner_sval = iter.second;
+ if (inner_sval->can_have_associated_state_p ())
+ impl_set_state (inner_sval, state, origin, ext_state);
+ }
+ }
+
/* Special-case state 0 as the default value. */
if (state == 0)
{
@@ -1384,6 +1400,10 @@ program_state::impl_call_analyzer_dump_state (const gcall *call,
const svalue *sval = cd.get_arg_svalue (1);
+ /* Strip off cast to int (due to variadic args). */
+ if (const svalue *cast = sval->maybe_undo_cast ())
+ sval = cast;
+
state_machine::state_t state = smap->get_state (sval, ext_state);
warning_at (call->location, 0, "state: %qs", state->get_name ());
}
@@ -1543,7 +1563,8 @@ test_program_state_1 ()
region_model *model = s.m_region_model;
const svalue *size_in_bytes
= mgr->get_or_create_unknown_svalue (size_type_node);
- const region *new_reg = model->create_region_for_heap_alloc (size_in_bytes);
+ const region *new_reg
+ = model->create_region_for_heap_alloc (size_in_bytes, NULL);
const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
model->set_value (model->get_lvalue (p, NULL),
ptr_sval, NULL);
@@ -1599,7 +1620,8 @@ test_program_state_merging ()
region_model *model0 = s0.m_region_model;
const svalue *size_in_bytes
= mgr->get_or_create_unknown_svalue (size_type_node);
- const region *new_reg = model0->create_region_for_heap_alloc (size_in_bytes);
+ const region *new_reg
+ = model0->create_region_for_heap_alloc (size_in_bytes, NULL);
const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
model0->set_value (model0->get_lvalue (p, &ctxt),
ptr_sval, &ctxt);
@@ -221,7 +221,7 @@ void
region_model::impl_call_alloca (const call_details &cd)
{
const svalue *size_sval = cd.get_arg_svalue (0);
- const region *new_reg = create_region_for_alloca (size_sval);
+ const region *new_reg = create_region_for_alloca (size_sval, cd.get_ctxt ());
const svalue *ptr_sval
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
cd.maybe_set_lhs (ptr_sval);
@@ -302,7 +302,8 @@ region_model::impl_call_calloc (const call_details &cd)
const svalue *prod_sval
= m_mgr->get_or_create_binop (size_type_node, MULT_EXPR,
nmemb_sval, size_sval);
- const region *new_reg = create_region_for_heap_alloc (prod_sval);
+ const region *new_reg
+ = create_region_for_heap_alloc (prod_sval, cd.get_ctxt ());
zero_fill_region (new_reg);
if (cd.get_lhs_type ())
{
@@ -408,7 +409,8 @@ void
region_model::impl_call_malloc (const call_details &cd)
{
const svalue *size_sval = cd.get_arg_svalue (0);
- const region *new_reg = create_region_for_heap_alloc (size_sval);
+ const region *new_reg
+ = create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
if (cd.get_lhs_type ())
{
const svalue *ptr_sval
@@ -471,7 +473,8 @@ void
region_model::impl_call_operator_new (const call_details &cd)
{
const svalue *size_sval = cd.get_arg_svalue (0);
- const region *new_reg = create_region_for_heap_alloc (size_sval);
+ const region *new_reg
+ = create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
if (cd.get_lhs_type ())
{
const svalue *ptr_sval
@@ -579,7 +582,7 @@ region_model::impl_call_realloc (const call_details &cd)
const svalue *size_sval = cd.get_arg_svalue (1);
if (const region *buffer_reg = ptr_sval->maybe_get_region ())
if (compat_types_p (size_sval->get_type (), size_type_node))
- model->set_dynamic_extents (buffer_reg, size_sval);
+ model->set_dynamic_extents (buffer_reg, size_sval, ctxt);
if (cd.get_lhs_region ())
{
model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ());
@@ -619,7 +622,7 @@ region_model::impl_call_realloc (const call_details &cd)
/* Create the new region. */
const region *new_reg
- = model->create_region_for_heap_alloc (new_size_sval);
+ = model->create_region_for_heap_alloc (new_size_sval, ctxt);
const svalue *new_ptr_sval
= model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
if (cd.get_lhs_type ())
@@ -2301,6 +2301,8 @@ region_model::check_region_access (const region *reg,
if (!ctxt)
return;
+ check_region_for_taint (reg, dir, ctxt);
+
switch (dir)
{
default:
@@ -2862,6 +2864,17 @@ region_model::get_representative_path_var_1 (const svalue *sval,
parent_pv.m_stack_depth);
}
+ /* Handle binops. */
+ if (const binop_svalue *binop_sval = sval->dyn_cast_binop_svalue ())
+ if (path_var lhs_pv
+ = get_representative_path_var (binop_sval->get_arg0 (), visited))
+ if (path_var rhs_pv
+ = get_representative_path_var (binop_sval->get_arg1 (), visited))
+ return path_var (build2 (binop_sval->get_op (),
+ sval->get_type (),
+ lhs_pv.m_tree, rhs_pv.m_tree),
+ lhs_pv.m_stack_depth);
+
if (pvs.length () < 1)
return path_var (NULL_TREE, 0);
@@ -3720,36 +3733,45 @@ region_model::append_ssa_names_cb (const region *base_reg,
}
}
-/* Return a new region describing a heap-allocated block of memory. */
+/* Return a new region describing a heap-allocated block of memory.
+ Use CTXT to complain about tainted sizes. */
const region *
-region_model::create_region_for_heap_alloc (const svalue *size_in_bytes)
+region_model::create_region_for_heap_alloc (const svalue *size_in_bytes,
+ region_model_context *ctxt)
{
const region *reg = m_mgr->create_region_for_heap_alloc ();
if (compat_types_p (size_in_bytes->get_type (), size_type_node))
- set_dynamic_extents (reg, size_in_bytes);
+ set_dynamic_extents (reg, size_in_bytes, ctxt);
return reg;
}
/* Return a new region describing a block of memory allocated within the
- current frame. */
+ current frame.
+ Use CTXT to complain about tainted sizes. */
const region *
-region_model::create_region_for_alloca (const svalue *size_in_bytes)
+region_model::create_region_for_alloca (const svalue *size_in_bytes,
+ region_model_context *ctxt)
{
const region *reg = m_mgr->create_region_for_alloca (m_current_frame);
if (compat_types_p (size_in_bytes->get_type (), size_type_node))
- set_dynamic_extents (reg, size_in_bytes);
+ set_dynamic_extents (reg, size_in_bytes, ctxt);
return reg;
}
-/* Record that the size of REG is SIZE_IN_BYTES. */
+/* Record that the size of REG is SIZE_IN_BYTES.
+ Use CTXT to complain about tainted sizes. */
void
region_model::set_dynamic_extents (const region *reg,
- const svalue *size_in_bytes)
+ const svalue *size_in_bytes,
+ region_model_context *ctxt)
{
assert_compat_types (size_in_bytes->get_type (), size_type_node);
+ if (ctxt)
+ check_dynamic_size_for_taint (reg->get_memory_space (), size_in_bytes,
+ ctxt);
m_dynamic_extents.put (reg, size_in_bytes);
}
@@ -5096,7 +5118,8 @@ test_state_merging ()
region_model model0 (&mgr);
tree size = build_int_cst (size_type_node, 1024);
const svalue *size_sval = mgr.get_or_create_constant_svalue (size);
- const region *new_reg = model0.create_region_for_heap_alloc (size_sval);
+ const region *new_reg
+ = model0.create_region_for_heap_alloc (size_sval, &ctxt);
const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg);
model0.set_value (model0.get_lvalue (p, &ctxt),
ptr_sval, &ctxt);
@@ -5484,7 +5507,7 @@ test_malloc_constraints ()
const svalue *size_in_bytes
= mgr.get_or_create_unknown_svalue (size_type_node);
- const region *reg = model.create_region_for_heap_alloc (size_in_bytes);
+ const region *reg = model.create_region_for_heap_alloc (size_in_bytes, NULL);
const svalue *sval = mgr.get_ptr_svalue (ptr_type_node, reg);
model.set_value (model.get_lvalue (p, NULL), sval, NULL);
model.set_value (q, p, NULL);
@@ -5705,7 +5728,7 @@ test_malloc ()
/* "p = malloc (n * 4);". */
const svalue *size_sval = model.get_rvalue (n_times_4, &ctxt);
- const region *reg = model.create_region_for_heap_alloc (size_sval);
+ const region *reg = model.create_region_for_heap_alloc (size_sval, &ctxt);
const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
ASSERT_EQ (model.get_capacity (reg), size_sval);
@@ -5739,7 +5762,7 @@ test_alloca ()
NULL, &ctxt);
/* "p = alloca (n * 4);". */
const svalue *size_sval = model.get_rvalue (n_times_4, &ctxt);
- const region *reg = model.create_region_for_alloca (size_sval);
+ const region *reg = model.create_region_for_alloca (size_sval, &ctxt);
ASSERT_EQ (reg->get_parent_region (), frame_reg);
const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
@@ -676,8 +676,10 @@ class region_model
region_model_context *ctxt,
rejected_constraint **out);
- const region *create_region_for_heap_alloc (const svalue *size_in_bytes);
- const region *create_region_for_alloca (const svalue *size_in_bytes);
+ const region *create_region_for_heap_alloc (const svalue *size_in_bytes,
+ region_model_context *ctxt);
+ const region *create_region_for_alloca (const svalue *size_in_bytes,
+ region_model_context *ctxt);
tree get_representative_tree (const svalue *sval) const;
path_var
@@ -703,7 +705,8 @@ class region_model
}
const svalue *get_dynamic_extents (const region *reg) const;
void set_dynamic_extents (const region *reg,
- const svalue *size_in_bytes);
+ const svalue *size_in_bytes,
+ region_model_context *ctxt);
void unset_dynamic_extents (const region *reg);
region_model_manager *get_manager () const { return m_mgr; }
@@ -792,6 +795,14 @@ class region_model
tree expr,
region_model_context *ctxt) const;
+ void check_dynamic_size_for_taint (enum memory_space mem_space,
+ const svalue *size_in_bytes,
+ region_model_context *ctxt) const;
+
+ void check_region_for_taint (const region *reg,
+ enum access_direction dir,
+ region_model_context *ctxt) const;
+
void check_for_writable_region (const region* dest_reg,
region_model_context *ctxt) const;
void check_region_access (const region *reg,
@@ -891,6 +902,10 @@ class region_model_context
virtual bool get_malloc_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx) = 0;
+ /* Likewise for the "taint" state machine. */
+ virtual bool get_taint_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx) = 0;
};
/* A "do nothing" subclass of region_model_context. */
@@ -935,6 +950,12 @@ public:
{
return false;
}
+ bool get_taint_map (sm_state_map **,
+ const state_machine **,
+ unsigned *) OVERRIDE
+ {
+ return false;
+ }
};
/* A subclass of region_model_context for determining if operations fail
@@ -33,9 +33,21 @@ along with GCC; see the file COPYING3. If not see
#include "function.h"
#include "json.h"
#include "analyzer/analyzer.h"
-#include "diagnostic-event-id.h"
#include "analyzer/analyzer-logging.h"
+#include "gimple-iterator.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "ordered-hash-map.h"
+#include "cgraph.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
#include "analyzer/sm.h"
+#include "analyzer/program-state.h"
#include "analyzer/pending-diagnostic.h"
#if ENABLE_ANALYZER
@@ -44,6 +56,20 @@ namespace ana {
namespace {
+/* An enum for describing tainted values. */
+
+enum bounds
+{
+ /* This tainted value has no upper or lower bound. */
+ BOUNDS_NONE,
+
+ /* This tainted value has an upper bound but not lower bound. */
+ BOUNDS_UPPER,
+
+ /* This tainted value has a lower bound but no upper bound. */
+ BOUNDS_LOWER
+};
+
/* An experimental state machine, for tracking "taint": unsanitized uses
of data potentially under an attacker's control. */
@@ -54,6 +80,11 @@ public:
bool inherited_state_p () const FINAL OVERRIDE { return true; }
+ state_t alt_get_inherited_state (const sm_state_map &map,
+ const svalue *sval,
+ const extrinsic_state &ext_state)
+ const FINAL OVERRIDE;
+
bool on_stmt (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt) const FINAL OVERRIDE;
@@ -67,6 +98,10 @@ public:
bool can_purge_p (state_t s) const FINAL OVERRIDE;
+ bool get_taint (state_t s, tree type, enum bounds *out) const;
+
+ state_t combine_states (state_t s0, state_t s1) const;
+
/* State for a "tainted" value: unsanitized data potentially under an
attacker's control. */
state_t m_tainted;
@@ -81,31 +116,65 @@ public:
state_t m_stop;
};
-enum bounds
+/* Class for diagnostics relating to taint_state_machine. */
+
+class taint_diagnostic : public pending_diagnostic
{
- BOUNDS_NONE,
- BOUNDS_UPPER,
- BOUNDS_LOWER
+public:
+ taint_diagnostic (const taint_state_machine &sm, tree arg,
+ enum bounds has_bounds)
+ : m_sm (sm), m_arg (arg), m_has_bounds (has_bounds)
+ {}
+
+ bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+ {
+ return same_tree_p (m_arg, ((const taint_diagnostic &)base_other).m_arg);
+ }
+
+ label_text describe_state_change (const evdesc::state_change &change)
+ FINAL OVERRIDE
+ {
+ if (change.m_new_state == m_sm.m_tainted)
+ {
+ if (change.m_origin)
+ return change.formatted_print ("%qE has an unchecked value here"
+ " (from %qE)",
+ change.m_expr, change.m_origin);
+ else
+ return change.formatted_print ("%qE gets an unchecked value here",
+ change.m_expr);
+ }
+ else if (change.m_new_state == m_sm.m_has_lb)
+ return change.formatted_print ("%qE has its lower bound checked here",
+ change.m_expr);
+ else if (change.m_new_state == m_sm.m_has_ub)
+ return change.formatted_print ("%qE has its upper bound checked here",
+ change.m_expr);
+ return label_text ();
+ }
+protected:
+ const taint_state_machine &m_sm;
+ tree m_arg;
+ enum bounds m_has_bounds;
};
-class tainted_array_index
- : public pending_diagnostic_subclass<tainted_array_index>
+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
+ array index. */
+
+class tainted_array_index : public taint_diagnostic
{
public:
tainted_array_index (const taint_state_machine &sm, tree arg,
enum bounds has_bounds)
- : m_sm (sm), m_arg (arg), m_has_bounds (has_bounds) {}
+ : taint_diagnostic (sm, arg, has_bounds)
+ {}
const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
- bool operator== (const tainted_array_index &other) const
- {
- return same_tree_p (m_arg, other.m_arg);
- }
-
bool emit (rich_location *rich_loc) FINAL OVERRIDE
{
diagnostic_metadata m;
+ /* CWE-129: "Improper Validation of Array Index". */
m.add_cwe (129);
switch (m_has_bounds)
{
@@ -113,45 +182,197 @@ public:
gcc_unreachable ();
case BOUNDS_NONE:
return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
- "use of tainted value %qE in array lookup"
- " without bounds checking",
+ "use of attacker-controlled value %qE"
+ " in array lookup without bounds checking",
m_arg);
break;
case BOUNDS_UPPER:
return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
- "use of tainted value %qE in array lookup"
- " without lower-bounds checking",
+ "use of attacker-controlled value %qE"
+ " in array lookup without checking for negative",
m_arg);
break;
case BOUNDS_LOWER:
return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
- "use of tainted value %qE in array lookup"
- " without upper-bounds checking",
+ "use of attacker-controlled value %qE"
+ " in array lookup without upper-bounds checking",
m_arg);
break;
}
}
- label_text describe_state_change (const evdesc::state_change &change)
- FINAL OVERRIDE
+ label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
- if (change.m_new_state == m_sm.m_tainted)
+ switch (m_has_bounds)
{
- if (change.m_origin)
- return change.formatted_print ("%qE has an unchecked value here"
- " (from %qE)",
- change.m_expr, change.m_origin);
- else
- return change.formatted_print ("%qE gets an unchecked value here",
- change.m_expr);
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE in array lookup"
+ " without bounds checking",
+ m_arg);
+ case BOUNDS_UPPER:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE"
+ " in array lookup without checking for negative",
+ m_arg);
+ case BOUNDS_LOWER:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE"
+ " in array lookup without upper-bounds checking",
+ m_arg);
+ }
+ }
+};
+
+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
+ pointer offset. */
+
+class tainted_offset : public taint_diagnostic
+{
+public:
+ tainted_offset (const taint_state_machine &sm, tree arg,
+ enum bounds has_bounds)
+ : taint_diagnostic (sm, arg, has_bounds)
+ {}
+
+ const char *get_kind () const FINAL OVERRIDE { return "tainted_offset"; }
+
+ bool emit (rich_location *rich_loc) FINAL OVERRIDE
+ {
+ diagnostic_metadata m;
+ /* CWE-823: "Use of Out-of-range Pointer Offset". */
+ m.add_cwe (823);
+ if (m_arg)
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value %qE as offset"
+ " without bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_UPPER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value %qE as offset"
+ " without lower-bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_LOWER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value %qE as offset"
+ " without upper-bounds checking",
+ m_arg);
+ break;
+ }
+ else
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value as offset"
+ " without bounds checking");
+ break;
+ case BOUNDS_UPPER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value as offset"
+ " without lower-bounds checking");
+ break;
+ case BOUNDS_LOWER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_offset,
+ "use of attacker-controlled value as offset"
+ " without upper-bounds checking");
+ break;
+ }
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+ {
+ if (m_arg)
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as offset without bounds checking",
+ m_arg);
+ case BOUNDS_UPPER:
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as offset without lower-bounds checking",
+ m_arg);
+ case BOUNDS_LOWER:
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as offset without upper-bounds checking",
+ m_arg);
+ }
+ else
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return ev.formatted_print ("use of attacker-controlled value"
+ " as offset without bounds checking");
+ case BOUNDS_UPPER:
+ return ev.formatted_print ("use of attacker-controlled value"
+ " as offset without lower-bounds"
+ " checking");
+ case BOUNDS_LOWER:
+ return ev.formatted_print ("use of attacker-controlled value"
+ " as offset without upper-bounds"
+ " checking");
+ }
+ }
+};
+
+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
+ size. */
+
+class tainted_size : public taint_diagnostic
+{
+public:
+ tainted_size (const taint_state_machine &sm, tree arg,
+ enum bounds has_bounds,
+ enum access_direction dir)
+ : taint_diagnostic (sm, arg, has_bounds),
+ m_dir (dir)
+ {}
+
+ const char *get_kind () const FINAL OVERRIDE { return "tainted_size"; }
+
+ bool emit (rich_location *rich_loc) FINAL OVERRIDE
+ {
+ diagnostic_metadata m;
+ m.add_cwe (129);
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_size,
+ "use of attacker-controlled value %qE as size"
+ " without bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_UPPER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_size,
+ "use of attacker-controlled value %qE as size"
+ " without lower-bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_LOWER:
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_size,
+ "use of attacker-controlled value %qE as size"
+ " without upper-bounds checking",
+ m_arg);
+ break;
}
- else if (change.m_new_state == m_sm.m_has_lb)
- return change.formatted_print ("%qE has its lower bound checked here",
- change.m_expr);
- else if (change.m_new_state == m_sm.m_has_ub)
- return change.formatted_print ("%qE has its upper bound checked here",
- change.m_expr);
- return label_text ();
}
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
@@ -161,24 +382,194 @@ public:
default:
gcc_unreachable ();
case BOUNDS_NONE:
- return ev.formatted_print ("use of tainted value %qE in array lookup"
- " without bounds checking",
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as size without bounds checking",
m_arg);
case BOUNDS_UPPER:
- return ev.formatted_print ("use of tainted value %qE in array lookup"
- " without lower-bounds checking",
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as size without lower-bounds checking",
m_arg);
case BOUNDS_LOWER:
- return ev.formatted_print ("use of tainted value %qE in array lookup"
- " without upper-bounds checking",
+ return ev.formatted_print ("use of attacker-controlled value %qE"
+ " as size without upper-bounds checking",
m_arg);
}
}
private:
- const taint_state_machine &m_sm;
- tree m_arg;
- enum bounds m_has_bounds;
+ enum access_direction m_dir;
+};
+
+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
+ divisor (so that an attacker can trigger a divide by zero). */
+
+class tainted_divisor : public taint_diagnostic
+{
+public:
+ tainted_divisor (const taint_state_machine &sm, tree arg,
+ enum bounds has_bounds)
+ : taint_diagnostic (sm, arg, has_bounds)
+ {}
+
+ const char *get_kind () const FINAL OVERRIDE { return "tainted_divisor"; }
+
+ bool emit (rich_location *rich_loc) FINAL OVERRIDE
+ {
+ diagnostic_metadata m;
+ /* CWE-369: "Divide By Zero". */
+ m.add_cwe (369);
+ if (m_arg)
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_divisor,
+ "use of attacker-controlled value %qE as divisor"
+ " without checking for zero",
+ m_arg);
+ else
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_tainted_divisor,
+ "use of attacker-controlled value as divisor"
+ " without checking for zero");
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+ {
+ if (m_arg)
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE as divisor"
+ " without checking for zero",
+ m_arg);
+ else
+ return ev.formatted_print
+ ("use of attacker-controlled value as divisor"
+ " without checking for zero");
+ }
+};
+
+/* Concrete taint_diagnostic subclass for reporting attacker-controlled
+ size of a dynamic allocation. */
+
+class tainted_allocation_size : public taint_diagnostic
+{
+public:
+ tainted_allocation_size (const taint_state_machine &sm, tree arg,
+ enum bounds has_bounds, enum memory_space mem_space)
+ : taint_diagnostic (sm, arg, has_bounds),
+ m_mem_space (mem_space)
+ {
+ gcc_assert (mem_space == MEMSPACE_STACK || mem_space == MEMSPACE_HEAP);
+ }
+
+ const char *get_kind () const FINAL OVERRIDE
+ {
+ return "tainted_allocation_size";
+ }
+
+ bool emit (rich_location *rich_loc) FINAL OVERRIDE
+ {
+ diagnostic_metadata m;
+ /* "CWE-789: Memory Allocation with Excessive Size Value". */
+ m.add_cwe (789);
+ gcc_assert (m_mem_space == MEMSPACE_STACK || m_mem_space == MEMSPACE_HEAP);
+ // TODO: make use of m_mem_space
+ if (m_arg)
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value %qE as"
+ " allocation size without bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_UPPER:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value %qE as"
+ " allocation size without lower-bounds checking",
+ m_arg);
+ break;
+ case BOUNDS_LOWER:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value %qE as"
+ " allocation size without upper-bounds checking",
+ m_arg);
+ break;
+ }
+ else
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value as"
+ " allocation size without bounds"
+ " checking");
+ break;
+ case BOUNDS_UPPER:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value as"
+ " allocation size without lower-bounds"
+ " checking");
+ break;
+ case BOUNDS_LOWER:
+ return warning_meta (rich_loc, m,
+ OPT_Wanalyzer_tainted_allocation_size,
+ "use of attacker-controlled value as"
+ " allocation size without upper-bounds"
+ " checking");
+ break;
+ }
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+ {
+ if (m_arg)
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE as allocation size"
+ " without bounds checking",
+ m_arg);
+ case BOUNDS_UPPER:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE as allocation size"
+ " without lower-bounds checking",
+ m_arg);
+ case BOUNDS_LOWER:
+ return ev.formatted_print
+ ("use of attacker-controlled value %qE as allocation size"
+ " without upper-bounds checking",
+ m_arg);
+ }
+ else
+ switch (m_has_bounds)
+ {
+ default:
+ gcc_unreachable ();
+ case BOUNDS_NONE:
+ return ev.formatted_print
+ ("use of attacker-controlled value as allocation size"
+ " without bounds checking");
+ case BOUNDS_UPPER:
+ return ev.formatted_print
+ ("use of attacker-controlled value as allocation size"
+ " without lower-bounds checking");
+ case BOUNDS_LOWER:
+ return ev.formatted_print
+ ("use of attacker-controlled value as allocation size"
+ " without upper-bounds checking");
+ }
+ }
+
+private:
+ enum memory_space m_mem_space;
};
/* taint_state_machine's ctor. */
@@ -192,6 +583,79 @@ taint_state_machine::taint_state_machine (logger *logger)
m_stop = add_state ("stop");
}
+state_machine::state_t
+taint_state_machine::alt_get_inherited_state (const sm_state_map &map,
+ const svalue *sval,
+ const extrinsic_state &ext_state)
+ const
+{
+ switch (sval->get_kind ())
+ {
+ default:
+ break;
+ case SK_UNARYOP:
+ {
+ const unaryop_svalue *unaryop_sval
+ = as_a <const unaryop_svalue *> (sval);
+ enum tree_code op = unaryop_sval->get_op ();
+ const svalue *arg = unaryop_sval->get_arg ();
+ switch (op)
+ {
+ case NOP_EXPR:
+ {
+ state_t arg_state = map.get_state (arg, ext_state);
+ return arg_state;
+ }
+ default:
+ gcc_unreachable ();
+ break;
+ }
+ }
+ break;
+ case SK_BINOP:
+ {
+ const binop_svalue *binop_sval = as_a <const binop_svalue *> (sval);
+ enum tree_code op = binop_sval->get_op ();
+ const svalue *arg0 = binop_sval->get_arg0 ();
+ const svalue *arg1 = binop_sval->get_arg1 ();
+ switch (op)
+ {
+ default:
+ break;
+ case PLUS_EXPR:
+ case MINUS_EXPR:
+ case MULT_EXPR:
+ case POINTER_PLUS_EXPR:
+ case TRUNC_DIV_EXPR:
+ case TRUNC_MOD_EXPR:
+ {
+ state_t arg0_state = map.get_state (arg0, ext_state);
+ state_t arg1_state = map.get_state (arg1, ext_state);
+ return combine_states (arg0_state, arg1_state);
+ }
+ 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;
+ }
+ }
+ break;
+ }
+ return NULL;
+}
+
/* Implementation of state_machine::on_stmt vfunc for taint_state_machine. */
bool
@@ -220,53 +684,35 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
if (const gassign *assign = dyn_cast <const gassign *> (stmt))
{
- tree rhs1 = gimple_assign_rhs1 (assign);
enum tree_code op = gimple_assign_rhs_code (assign);
- /* Check array accesses. */
- if (op == ARRAY_REF)
+ switch (op)
{
- tree arg = TREE_OPERAND (rhs1, 1);
-
- /* Unsigned types have an implicit lower bound. */
- bool is_unsigned = false;
- if (INTEGRAL_TYPE_P (TREE_TYPE (arg)))
- is_unsigned = TYPE_UNSIGNED (TREE_TYPE (arg));
-
- state_t state = sm_ctxt->get_state (stmt, arg);
- /* Can't use a switch as the states are non-const. */
- if (state == m_tainted)
- {
- /* Complain about missing bounds. */
- tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
- pending_diagnostic *d
- = new tainted_array_index (*this, diag_arg,
- is_unsigned
- ? BOUNDS_LOWER : BOUNDS_NONE);
- sm_ctxt->warn (node, stmt, arg, d);
- sm_ctxt->set_next_state (stmt, arg, m_stop);
- }
- else if (state == m_has_lb)
- {
- /* Complain about missing upper bound. */
- tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
- sm_ctxt->warn (node, stmt, arg,
- new tainted_array_index (*this, diag_arg,
- BOUNDS_LOWER));
- sm_ctxt->set_next_state (stmt, arg, m_stop);
- }
- else if (state == m_has_ub)
- {
- /* Complain about missing lower bound. */
- if (!is_unsigned)
- {
- tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
- sm_ctxt->warn (node, stmt, arg,
- new tainted_array_index (*this, diag_arg,
- BOUNDS_UPPER));
- sm_ctxt->set_next_state (stmt, arg, m_stop);
- }
- }
+ default:
+ break;
+ case TRUNC_DIV_EXPR:
+ case CEIL_DIV_EXPR:
+ case FLOOR_DIV_EXPR:
+ case ROUND_DIV_EXPR:
+ case TRUNC_MOD_EXPR:
+ case CEIL_MOD_EXPR:
+ case FLOOR_MOD_EXPR:
+ case ROUND_MOD_EXPR:
+ case RDIV_EXPR:
+ case EXACT_DIV_EXPR:
+ {
+ tree divisor = gimple_assign_rhs2 (assign);;
+ state_t state = sm_ctxt->get_state (stmt, divisor);
+ enum bounds b;
+ if (get_taint (state, TREE_TYPE (divisor), &b))
+ {
+ tree diag_divisor = sm_ctxt->get_diagnostic_tree (divisor);
+ sm_ctxt->warn (node, stmt, divisor,
+ new tainted_divisor (*this, diag_divisor, b));
+ sm_ctxt->set_next_state (stmt, divisor, m_stop);
+ }
+ }
+ break;
}
}
@@ -324,6 +770,62 @@ taint_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
return true;
}
+/* If STATE is a tainted state, write the bounds to *OUT and return true.
+ Otherwise return false.
+ Use the signedness of TYPE to determine if "has_ub" is tainted. */
+
+bool
+taint_state_machine::get_taint (state_t state, tree type,
+ enum bounds *out) const
+{
+ /* Unsigned types have an implicit lower bound. */
+ bool is_unsigned = false;
+ if (type)
+ if (INTEGRAL_TYPE_P (type))
+ is_unsigned = TYPE_UNSIGNED (type);
+
+ /* Can't use a switch as the states are non-const. */
+ if (state == m_tainted)
+ {
+ *out = is_unsigned ? BOUNDS_LOWER : BOUNDS_NONE;
+ return true;
+ }
+ else if (state == m_has_lb)
+ {
+ *out = BOUNDS_LOWER;
+ return true;
+ }
+ else if (state == m_has_ub && !is_unsigned)
+ {
+ /* Missing lower bound. */
+ *out = BOUNDS_UPPER;
+ return true;
+ }
+ return false;
+}
+
+/* Find the most tainted state of S0 and S1. */
+
+state_machine::state_t
+taint_state_machine::combine_states (state_t s0, state_t s1) const
+{
+ gcc_assert (s0);
+ gcc_assert (s1);
+ if (s0 == s1)
+ return s0;
+ if (s0 == m_tainted || s1 == m_tainted)
+ return m_tainted;
+ if (s0 == m_stop)
+ return s1;
+ if (s1 == m_stop)
+ return s0;
+ if (s0 == m_start)
+ return s1;
+ if (s1 == m_start)
+ return s0;
+ gcc_unreachable ();
+}
+
} // anonymous namespace
/* Internal interface to this file. */
@@ -334,6 +836,152 @@ make_taint_state_machine (logger *logger)
return new taint_state_machine (logger);
}
+/* Complain to CTXT if accessing REG leads could lead to arbitrary
+ memory access under an attacker's control (due to taint). */
+
+void
+region_model::check_region_for_taint (const region *reg,
+ enum access_direction dir,
+ region_model_context *ctxt) const
+{
+ gcc_assert (reg);
+ gcc_assert (ctxt);
+
+ LOG_SCOPE (ctxt->get_logger ());
+
+ sm_state_map *smap;
+ const state_machine *sm;
+ unsigned sm_idx;
+ if (!ctxt->get_taint_map (&smap, &sm, &sm_idx))
+ return;
+
+ gcc_assert (smap);
+ gcc_assert (sm);
+
+ const taint_state_machine &taint_sm = (const taint_state_machine &)*sm;
+
+ const extrinsic_state *ext_state = ctxt->get_ext_state ();
+ if (!ext_state)
+ return;
+
+ const region *iter_region = reg;
+ while (iter_region)
+ {
+ switch (iter_region->get_kind ())
+ {
+ default:
+ break;
+
+ case RK_ELEMENT:
+ {
+ const element_region *element_reg
+ = (const element_region *)iter_region;
+ const svalue *index = element_reg->get_index ();
+ const state_machine::state_t
+ state = smap->get_state (index, *ext_state);
+ gcc_assert (state);
+ enum bounds b;
+ if (taint_sm.get_taint (state, index->get_type (), &b))
+ {
+ tree arg = get_representative_tree (index);
+ ctxt->warn (new tainted_array_index (taint_sm, arg, b));
+ }
+ }
+ break;
+
+ case RK_OFFSET:
+ {
+ const offset_region *offset_reg
+ = (const offset_region *)iter_region;
+ const svalue *offset = offset_reg->get_byte_offset ();
+ const state_machine::state_t
+ state = smap->get_state (offset, *ext_state);
+ gcc_assert (state);
+ /* Handle implicit cast to sizetype. */
+ tree effective_type = offset->get_type ();
+ if (const svalue *cast = offset->maybe_undo_cast ())
+ if (cast->get_type ())
+ effective_type = cast->get_type ();
+ enum bounds b;
+ if (taint_sm.get_taint (state, effective_type, &b))
+ {
+ tree arg = get_representative_tree (offset);
+ ctxt->warn (new tainted_offset (taint_sm, arg, b));
+ }
+ }
+ break;
+
+ case RK_CAST:
+ {
+ const cast_region *cast_reg
+ = as_a <const cast_region *> (iter_region);
+ iter_region = cast_reg->get_original_region ();
+ continue;
+ }
+
+ case RK_SIZED:
+ {
+ const sized_region *sized_reg
+ = (const sized_region *)iter_region;
+ const svalue *size_sval = sized_reg->get_byte_size_sval (m_mgr);
+ const state_machine::state_t
+ state = smap->get_state (size_sval, *ext_state);
+ gcc_assert (state);
+ enum bounds b;
+ if (taint_sm.get_taint (state, size_sval->get_type (), &b))
+ {
+ tree arg = get_representative_tree (size_sval);
+ ctxt->warn (new tainted_size (taint_sm, arg, b, dir));
+ }
+ }
+ break;
+ }
+
+ iter_region = iter_region->get_parent_region ();
+ }
+}
+
+/* Complain to CTXT about a tainted allocation size if SIZE_IN_BYTES is
+ under an attacker's control (due to taint), where the allocation
+ is happening within MEM_SPACE. */
+
+void
+region_model::check_dynamic_size_for_taint (enum memory_space mem_space,
+ const svalue *size_in_bytes,
+ region_model_context *ctxt) const
+{
+ gcc_assert (mem_space == MEMSPACE_STACK || mem_space == MEMSPACE_HEAP);
+ gcc_assert (size_in_bytes);
+ gcc_assert (ctxt);
+
+ LOG_SCOPE (ctxt->get_logger ());
+
+ sm_state_map *smap;
+ const state_machine *sm;
+ unsigned sm_idx;
+ if (!ctxt->get_taint_map (&smap, &sm, &sm_idx))
+ return;
+
+ gcc_assert (smap);
+ gcc_assert (sm);
+
+ const taint_state_machine &taint_sm = (const taint_state_machine &)*sm;
+
+ const extrinsic_state *ext_state = ctxt->get_ext_state ();
+ if (!ext_state)
+ return;
+
+ const state_machine::state_t
+ state = smap->get_state (size_in_bytes, *ext_state);
+ gcc_assert (state);
+ enum bounds b;
+ if (taint_sm.get_taint (state, size_in_bytes->get_type (), &b))
+ {
+ tree arg = get_representative_tree (size_in_bytes);
+ ctxt->warn (new tainted_allocation_size (taint_sm, arg, b, mem_space));
+ }
+}
+
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
@@ -69,6 +69,15 @@ public:
within a heap-allocated struct. */
virtual bool inherited_state_p () const = 0;
+ /* A vfunc for more general handling of inheritance. */
+ virtual state_t
+ alt_get_inherited_state (const sm_state_map &,
+ const svalue *,
+ const extrinsic_state &) const
+ {
+ return NULL;
+ }
+
virtual state_machine::state_t get_default_state (const svalue *) const
{
return m_start;
@@ -449,7 +449,11 @@ Objective-C and Objective-C++ Dialects}.
-Wno-analyzer-shift-count-negative @gol
-Wno-analyzer-shift-count-overflow @gol
-Wno-analyzer-stale-setjmp-buffer @gol
+-Wno-analyzer-tainted-allocation-size @gol
-Wno-analyzer-tainted-array-index @gol
+-Wno-analyzer-tainted-divisor @gol
+-Wno-analyzer-tainted-offset @gol
+-Wno-analyzer-tainted-size @gol
-Wanalyzer-too-complex @gol
-Wno-analyzer-unsafe-call-within-signal-handler @gol
-Wno-analyzer-use-after-free @gol
@@ -9400,7 +9404,11 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-shift-count-negative @gol
-Wanalyzer-shift-count-overflow @gol
-Wanalyzer-stale-setjmp-buffer @gol
+-Wanalyzer-tainted-allocation-size @gol
-Wanalyzer-tainted-array-index @gol
+-Wanalyzer-tainted-divisor @gol
+-Wanalyzer-tainted-offset @gol
+-Wanalyzer-tainted-size @gol
-Wanalyzer-unsafe-call-within-signal-handler @gol
-Wanalyzer-use-after-free @gol
-Wanalyzer-use-of-uninitialized-value @gol
@@ -9583,6 +9591,21 @@ when the function containing the @code{setjmp} call returns. Attempting
to rewind to it via @code{longjmp} would reference a stack frame that
no longer exists, and likely lead to a crash (or worse).
+@item -Wno-analyzer-tainted-allocation-size
+@opindex Wanalyzer-tainted-allocation-size
+@opindex Wno-analyzer-tainted-allocation-size
+This warning requires both @option{-fanalyzer} and
+@option{-fanalyzer-checker=taint} to enable it;
+use @option{-Wno-analyzer-tainted-allocation-size} 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 the size
+of an allocation without being sanitized, so that an attacker could
+inject an excessively large allocation and potentially cause a denial
+of service attack.
+
+See @url{https://cwe.mitre.org/data/definitions/789.html, CWE-789: Memory Allocation with Excessive Size Value}.
+
@item -Wno-analyzer-tainted-array-index
@opindex Wanalyzer-tainted-array-index
@opindex Wno-analyzer-tainted-array-index
@@ -9592,7 +9615,48 @@ use @option{-Wno-analyzer-tainted-array-index} 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 the index
-of an array access without being sanitized.
+of an array access without being sanitized, so that an attacker
+could inject an out-of-bounds access.
+
+See @url{https://cwe.mitre.org/data/definitions/129.html, CWE-129: Improper Validation of Array Index}.
+
+@item -Wno-analyzer-tainted-divisor
+@opindex Wanalyzer-tainted-divisor
+@opindex Wno-analyzer-tainted-divisor
+This warning requires both @option{-fanalyzer} and
+@option{-fanalyzer-checker=taint} to enable it;
+use @option{-Wno-analyzer-tainted-divisor} 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 the divisor
+in a division or modulus operation without being sanitized, so that
+an attacker could inject a division-by-zero.
+
+@item -Wno-analyzer-tainted-offset
+@opindex Wanalyzer-tainted-offset
+@opindex Wno-analyzer-tainted-offset
+This warning requires both @option{-fanalyzer} and
+@option{-fanalyzer-checker=taint} to enable it;
+use @option{-Wno-analyzer-tainted-offset} 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 a pointer offset
+without being sanitized, so that an attacker could inject an out-of-bounds
+access.
+
+See @url{https://cwe.mitre.org/data/definitions/823.html, CWE-823: Use of Out-of-range Pointer Offset}.
+
+@item -Wno-analyzer-tainted-size
+@opindex Wanalyzer-tainted-size
+@opindex Wno-analyzer-tainted-size
+This warning requires both @option{-fanalyzer} and
+@option{-fanalyzer-checker=taint} to enable it;
+use @option{-Wno-analyzer-tainted-size} 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 the size of
+an operation such as @code{memset} without being sanitized, so that an
+attacker could inject an out-of-bounds access.
@item -Wno-analyzer-unsafe-call-within-signal-handler
@opindex Wanalyzer-unsafe-call-within-signal-handler
@@ -23,5 +23,5 @@ int
pl (void)
{
ql ();
- return arr[idx]; /* { dg-warning "use of tainted value 'idx' in array lookup without bounds checking" "" { xfail *-*-* } } */
+ return arr[idx]; /* { dg-warning "use of attacker-controlled value 'idx' in array lookup without bounds checking" "" { xfail *-*-* } } */
}
new file mode 100644
@@ -0,0 +1,64 @@
+// TODO: remove need for this option
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ size_t sz;
+};
+
+/* malloc with tainted size. */
+
+void *test_1 (FILE *f)
+{
+ struct foo tmp;
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ __analyzer_dump_state ("taint", tmp.sz); /* { dg-warning "state: 'tainted'" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+
+ return malloc (tmp.sz); /* { dg-warning "use of attacker-controlled value 'tmp\\.sz' as allocation size without upper-bounds checking" "warning" } */
+ /* { dg-message "23: \\(\[0-9\]+\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp\\.sz' as allocation size without upper-bounds checking" "final event" { target *-*-* } .-2 } */
+
+ // TOOD: better messages for state changes
+ }
+ return 0;
+}
+
+/* VLA with tainted size. */
+
+void *test_2 (FILE *f)
+{
+ struct foo tmp;
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ __analyzer_dump_state ("taint", tmp.sz); /* { dg-warning "state: 'tainted'" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+
+ /* VLA with tainted size. */
+ {
+ char buf[tmp.sz]; /* { dg-warning "use of attacker-controlled value 'tmp\\.sz' as allocation size without upper-bounds checking" "warning" } */
+ /* { dg-message "\\(\[0-9\]+\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp\\.sz' as allocation size without upper-bounds checking" "final event" { target *-*-* } .-2 } */
+ fread (buf, tmp.sz, 1, f);
+ }
+
+ // TOOD: better messages for state changes
+ }
+ return 0;
+}
+
+void *test_3 (FILE *f)
+{
+ int num;
+ fread (&num, sizeof (int), 1, f);
+ __analyzer_dump_state ("taint", num); /* { dg-warning "state: 'tainted'" } */
+ __analyzer_dump_state ("taint", num * 16); /* { dg-warning "state: 'tainted'" } */
+ __analyzer_dump_state ("taint", (size_t)(num * 16)); /* { dg-warning "state: 'tainted'" } */
+ return malloc (num * 16); /* { dg-warning "use of attacker-controlled value 'num \\* 16' as allocation size without upper-bounds checking" } */
+}
new file mode 100644
@@ -0,0 +1,27 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ int num;
+};
+
+/* malloc with tainted size from a field. */
+
+void *test_1 (FILE *f)
+{
+ struct foo tmp;
+ fread(&tmp, sizeof(tmp), 1, f); /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+
+ __analyzer_dump_state ("taint", tmp.num); /* { dg-warning "state: 'tainted'" } */
+ __analyzer_dump_state ("taint", tmp.num * 16); /* { dg-warning "state: 'tainted'" } */
+
+ return malloc (tmp.num * 16); /* { dg-warning "use of attacker-controlled value 'tmp\\.num \\* 16' as allocation size without upper-bounds checking" "warning" } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp\\.num \\* 16' as allocation size without upper-bounds checking" "final event with expr" { target *-*-* } .-1 } */
+ // TODO: show where tmp.num * 16 gets the bogus value
+}
new file mode 100644
@@ -0,0 +1,26 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+#include <stdio.h>
+
+struct st1
+{
+ int a;
+ int b;
+};
+
+
+int test_1 (FILE *f)
+{
+ struct st1 s;
+ fread (&s, sizeof (s), 1, f);
+ return s.a / s.b; /* { dg-warning "use of attacker-controlled value 's\\.b' as divisor without checking for zero" } */
+}
+
+int test_2 (FILE *f)
+{
+ struct st1 s;
+ fread (&s, sizeof (s), 1, f);
+ return s.a % s.b; /* { dg-warning "use of attacker-controlled value 's\\.b' as divisor without checking for zero" } */
+}
similarity index 72%
rename from gcc/testsuite/gcc.dg/analyzer/taint-1.c
rename to gcc/testsuite/gcc.dg/analyzer/taint-read-index-1.c
@@ -1,3 +1,4 @@
+// TODO: remove need for this option:
/* { dg-additional-options "-fanalyzer-checker=taint" } */
#include <stdio.h>
@@ -18,10 +19,10 @@ char test_1(FILE *f)
/* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
/* BUG: the following array lookup trusts that the input data's index is
in the range 0 <= i < 256; otherwise it's accessing the stack */
- return tmp.buf[tmp.i]; // { dg-warning "use of tainted value 'tmp.i' in array lookup without bounds checking" "warning" } */
+ return tmp.buf[tmp.i]; // { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without bounds checking" "warning" } */
/* { dg-message "23: \\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
/* { dg-message "23: \\(\[0-9\]+\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } .-2 } */
- /* { dg-message "\\(\[0-9\]+\\) use of tainted value 'tmp.i' in array lookup without bounds checking" "final event" { target *-*-* } .-3 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp.i' in array lookup without bounds checking" "final event" { target *-*-* } .-3 } */
// TOOD: better messages for state changes
}
@@ -53,8 +54,8 @@ char test_4(FILE *f)
if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
if (tmp.i >= 0) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } } */
- /* { dg-message "'tmp.i' has its lower bound checked here" "event: lower bound checked" { target *-*-* } .-1 } */
- return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" "warning" } */
+ /* { dg-message "'tmp.i' has its lower bound checked here" "event: lower bound checked" { xfail *-*-* } .-1 } */
+ return tmp.buf[tmp.i]; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" "warning" } */
}
}
return 0;
@@ -66,8 +67,8 @@ char test_5(FILE *f)
if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
if (tmp.i < 256) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } } */
- /* { dg-message "'tmp.i' has its upper bound checked here" "event: upper bound checked" { target *-*-* } .-1 } */
- return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without lower-bounds checking" "warning" } */
+ /* { dg-message "'tmp.i' has its upper bound checked here" "event: upper bound checked" { xfail *-*-* } .-1 } */
+ return tmp.buf[tmp.i]; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without checking for negative" "warning" } */
}
}
return 0;
@@ -85,7 +86,7 @@ char test_6(FILE *f)
struct bar tmp;
if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
- return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" } */
+ return tmp.buf[tmp.i]; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" } */
}
return 0;
}
@@ -96,7 +97,7 @@ char test_7(FILE *f)
if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
if (tmp.i >= 0) {
- return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" } */
+ return tmp.buf[tmp.i]; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" } */
}
}
return 0;
@@ -122,7 +123,7 @@ char test_9(FILE *f)
if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
if (tmp.i == 42) {
/* not a bug: tmp.i compared against a specific value: */
- return tmp.buf[tmp.i]; /* { dg-bogus "tainted" "" { xfail *-*-* } } */
+ return tmp.buf[tmp.i]; /* { dg-bogus "attacker-controlled" "" { xfail *-*-* } } */
// TODO: xfail
}
}
new file mode 100644
@@ -0,0 +1,128 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ ssize_t offset;
+};
+
+char *p;
+
+char test_1(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ return *(p + tmp.offset); // { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without bounds checking" "warning" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) 'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } .-2 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp.offset' as offset without bounds checking" "final event" { target *-*-* } .-3 } */
+
+ // TOOD: better messages for state changes
+ }
+ return 0;
+}
+
+char test_2(struct foo *f)
+{
+ /* not a bug: the data is not known to be tainted: */
+ return *(p + f->offset);
+}
+
+char test_3(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0 && tmp.offset < 256) {
+ /* not a bug: the access is guarded by upper and lower bounds: */
+ return *(p + tmp.offset);
+ }
+ }
+ return 0;
+}
+
+char test_4(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0) { /* { dg-message "'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.offset' has its lower bound checked here" "event: lower bound checked" { xfail *-*-* } .-1 } */
+ return *(p + tmp.offset); /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" "warning" } */
+ }
+ }
+ return 0;
+}
+
+char test_5(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset < 256) { /* { dg-message "'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.offset' has its upper bound checked here" "event: upper bound checked" { xfail *-*-* } .-1 } */
+ return *(p + tmp.offset); /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without lower-bounds checking" "warning" } */
+ }
+ }
+ return 0;
+}
+
+/* unsigned types have a natural lower bound of 0 */
+struct bar
+{
+ size_t offset;
+};
+
+char test_6(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ return *(p + tmp.offset); /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" } */
+ }
+ return 0;
+}
+
+char test_7(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0) {
+ return *(p + tmp.offset); /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" } */
+ }
+ }
+ return 0;
+}
+
+char test_8(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset < 256) {
+ /* not a bug: has an upper bound, and an implicit lower bound: */
+ return *(p + tmp.offset);
+ }
+ }
+ return 0;
+}
+
+char test_9(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset == 42) {
+ /* not a bug: tmp.offset compared against a specific value: */
+ return *(p + tmp.offset); /* { dg-bogus "attacker-controlled" "" { xfail *-*-* } } */
+ }
+ }
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,32 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ size_t sz;
+};
+
+char buf[100];
+
+/* memset with tainted size. */
+
+void test_1 (FILE *f)
+{
+ struct foo tmp;
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ __analyzer_dump_state ("taint", tmp.sz); /* { dg-warning "state: 'tainted'" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+
+ memset (buf, 0, tmp.sz); /* { dg-warning "use of attacker-controlled value 'tmp\\.sz' as size without upper-bounds checking" "warning" } */
+ /* { dg-message "23: \\(\[0-9\]+\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp\\.sz' as size without upper-bounds checking" "final event" { target *-*-* } .-2 } */
+
+ // TOOD: better messages for state changes
+ }
+}
new file mode 100644
@@ -0,0 +1,132 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ signed int i;
+ char buf[256];
+};
+
+struct foo g;
+char test_1(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ /* BUG: the following array lookup trusts that the input data's index is
+ in the range 0 <= i < 256; otherwise it's accessing the stack */
+ g.buf[tmp.i] = 42; // { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without bounds checking" "warning" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } .-2 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp.i' in array lookup without bounds checking" "final event" { target *-*-* } .-3 } */
+
+ // TOOD: better messages for state changes
+ }
+ return 0;
+}
+
+char test_2(struct foo *f, int i)
+{
+ /* not a bug: the data is not known to be tainted: */
+ return f->buf[f->i];
+}
+
+char test_3(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i >= 0 && tmp.i < 256) {
+ /* not a bug: the access is guarded by upper and lower bounds: */
+ g.buf[tmp.i] = 42;
+ }
+ }
+ return 0;
+}
+
+char test_4(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i >= 0) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.i' has its lower bound checked here" "event: lower bound checked" { xfail *-*-* } .-1 } */
+ g.buf[tmp.i] = 42; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" "warning" } */
+ }
+ }
+ return 0;
+}
+
+char test_5(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i < 256) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.i has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.i' has its upper bound checked here" "event: upper bound checked" { xfail *-*-* } .-1 } */
+ g.buf[tmp.i] = 42; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without checking for negative" "warning" } */
+ }
+ }
+ return 0;
+}
+
+/* unsigned types have a natural lower bound of 0 */
+struct bar
+{
+ unsigned int i;
+ char buf[256];
+};
+
+char test_6(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ g.buf[tmp.i] = 42; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" } */
+ }
+ return 0;
+}
+
+char test_7(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i >= 0) {
+ g.buf[tmp.i] = 42; /* { dg-warning "use of attacker-controlled value 'tmp.i' in array lookup without upper-bounds checking" } */
+ }
+ }
+ return 0;
+}
+
+char test_8(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i < 256) {
+ /* not a bug: has an upper bound, and an implicit lower bound: */
+ g.buf[tmp.i] = 42;
+ }
+ }
+ return 0;
+}
+
+char test_9(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.i == 42) {
+ /* not a bug: tmp.i compared against a specific value: */
+ g.buf[tmp.i] = 42; /* { dg-bogus "attacker-controlled" "" { xfail *-*-* } } */
+ // TODO: xfail
+ }
+ }
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,132 @@
+// TODO: remove need for this option:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+ ssize_t offset;
+};
+
+char *p;
+
+char test_1(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(\[0-9\]+\\) 'tmp' gets an unchecked value here" "event: tmp gets unchecked value" { xfail *-*-* } } */
+ /* { dg-message "\\(\[0-9\]+\\) following 'true' branch\\.\\.\\." "event: following true branch" { target *-*-* } .-1 } */
+ /* BUG: the following array lookup trusts that the input data's index is
+ in the range 0 <= i < 256; otherwise it's accessing the stack */
+ *(p + tmp.offset) = 42; // { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without bounds checking" "warning" } */
+ /* { dg-message "\\(\[0-9\]+\\) \\.\\.\\.to here" "event: to here" { target *-*-* } .-1 } */
+ /* { dg-message "\\(\[0-9\]+\\) 'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } .-2 } */
+ /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'tmp.offset' as offset without bounds checking" "final event" { target *-*-* } .-3 } */
+
+ // TOOD: better messages for state changes
+ }
+ return 0;
+}
+
+char test_2(struct foo *f)
+{
+ /* not a bug: the data is not known to be tainted: */
+ *(p + f->offset) = 42;
+}
+
+char test_3(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0 && tmp.offset < 256) {
+ /* not a bug: the access is guarded by upper and lower bounds: */
+ *(p + tmp.offset) = 42;
+ }
+ }
+ return 0;
+}
+
+char test_4(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0) { /* { dg-message "'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.offset' has its lower bound checked here" "event: lower bound checked" { xfail *-*-* } .-1 } */
+ *(p + tmp.offset) = 42; /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" "warning" } */
+ }
+ }
+ return 0;
+}
+
+char test_5(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset < 256) { /* { dg-message "'tmp.offset' has an unchecked value here \\(from 'tmp'\\)" "event: tmp.offset has an unchecked value" { xfail *-*-* } } */
+ /* { dg-message "'tmp.offset' has its upper bound checked here" "event: upper bound checked" { xfail *-*-* } .-1 } */
+ *(p + tmp.offset) = 42; /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without lower-bounds checking" "warning" } */
+ }
+ }
+ return 0;
+}
+
+/* unsigned types have a natural lower bound of 0 */
+struct bar
+{
+ unsigned int offset;
+ char buf[256];
+};
+
+char test_6(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ *(p + tmp.offset) = 42; /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" } */
+ }
+ return 0;
+}
+
+char test_7(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset >= 0) {
+ *(p + tmp.offset) = 42; /* { dg-warning "use of attacker-controlled value 'tmp.offset' as offset without upper-bounds checking" } */
+ }
+ }
+ return 0;
+}
+
+char test_8(FILE *f)
+{
+ struct bar tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset < 256) {
+ /* not a bug: has an upper bound, and an implicit lower bound: */
+ *(p + tmp.offset) = 42;
+ }
+ }
+ return 0;
+}
+
+char test_9(FILE *f)
+{
+ struct foo tmp;
+
+ if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+ if (tmp.offset == 42) {
+ /* not a bug: tmp.offset compared against a specific value: */
+ *(p + tmp.offset) = 42; /* { dg-bogus "tainted" "" { xfail *-*-* } } */
+ // TODO: xfail
+ }
+ }
+ return 0;
+}
The initial commit of the analyzer in GCC 10 had a single warning, -Wanalyzer-tainted-array-index and required manually enabling the taint checker with -fanalyzer-checker=taint (due to scaling issues). This patch extends the taint detection to add four new taint-based warnings: -Wanalyzer-tainted-allocation-size for e.g. attacker-controlled malloc/alloca -Wanalyzer-tainted-divisor for detecting where an attacker can inject a divide-by-zero -Wanalyzer-tainted-offset for attacker-controlled pointer offsets -Wanalyzer-tainted-size for e.g. attacker-controlled memset and rewords all the warnings to talk about "attacker-controlled" values rather than "tainted" values. Unfortunately I haven't yet addressed the scaling issues, so all of these still require -fanalyzer-checker=taint (in addition to -fanalyzer). Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as b9365b93212041f14a7f71ba8da5af4d82240dc6. gcc/analyzer/ChangeLog: * analyzer.opt (Wanalyzer-tainted-allocation-size): New. (Wanalyzer-tainted-divisor): New. (Wanalyzer-tainted-offset): New. (Wanalyzer-tainted-size): New. * engine.cc (impl_region_model_context::get_taint_map): New. * exploded-graph.h (impl_region_model_context::get_taint_map): New decl. * program-state.cc (sm_state_map::get_state): Call alt_get_inherited_state. (sm_state_map::impl_set_state): Modify states within compound svalues. (program_state::impl_call_analyzer_dump_state): Undo casts. (selftest::test_program_state_1): Update for new context param of create_region_for_heap_alloc. (selftest::test_program_state_merging): Likewise. * region-model-impl-calls.cc (region_model::impl_call_alloca): Likewise. (region_model::impl_call_calloc): Likewise. (region_model::impl_call_malloc): Likewise. (region_model::impl_call_operator_new): Likewise. (region_model::impl_call_realloc): Likewise. * region-model.cc (region_model::check_region_access): Call check_region_for_taint. (region_model::get_representative_path_var_1): Handle binops. (region_model::create_region_for_heap_alloc): Add "ctxt" param and pass it to set_dynamic_extents. (region_model::create_region_for_alloca): Likewise. (region_model::set_dynamic_extents): Add "ctxt" param and use it to call check_dynamic_size_for_taint. (selftest::test_state_merging): Update for new context param of create_region_for_heap_alloc. (selftest::test_malloc_constraints): Likewise. (selftest::test_malloc): Likewise. (selftest::test_alloca): Likewise for create_region_for_alloca. * region-model.h (region_model::create_region_for_heap_alloc): Add "ctxt" param. (region_model::create_region_for_alloca): Likewise. (region_model::set_dynamic_extents): Likewise. (region_model::check_dynamic_size_for_taint): New decl. (region_model::check_region_for_taint): New decl. (region_model_context::get_taint_map): New vfunc. (noop_region_model_context::get_taint_map): New. * sm-taint.cc: Remove include of "diagnostic-event-id.h"; add includes of "gimple-iterator.h", "tristate.h", "selftest.h", "ordered-hash-map.h", "cgraph.h", "cfg.h", "digraph.h", "analyzer/supergraph.h", "analyzer/call-string.h", "analyzer/program-point.h", "analyzer/store.h", "analyzer/region-model.h", and "analyzer/program-state.h". (enum bounds): Move to top of file. (class taint_diagnostic): New. (class tainted_array_index): Convert to subclass of taint_diagnostic. (tainted_array_index::emit): Add CWE-129. Reword warning to use "attacker-controlled" rather than "tainted". (tainted_array_index::describe_state_change): Move to taint_diagnostic::describe_state_change. (tainted_array_index::describe_final_event): Reword to use "attacker-controlled" rather than "tainted". (class tainted_offset): New. (class tainted_size): New. (class tainted_divisor): New. (class tainted_allocation_size): New. (taint_state_machine::alt_get_inherited_state): New. (taint_state_machine::on_stmt): In assignment handling, remove ARRAY_REF handling in favor of check_region_for_taint. Add detection of tainted divisors. (taint_state_machine::get_taint): New. (taint_state_machine::combine_states): New. (region_model::check_region_for_taint): New. (region_model::check_dynamic_size_for_taint): New. * sm.h (state_machine::alt_get_inherited_state): New. gcc/ChangeLog: * doc/invoke.texi (Static Analyzer Options): Add -Wno-analyzer-tainted-allocation-size, -Wno-analyzer-tainted-divisor, -Wno-analyzer-tainted-offset, and -Wno-analyzer-tainted-size to list. Add -Wanalyzer-tainted-allocation-size, -Wanalyzer-tainted-divisor, -Wanalyzer-tainted-offset, and -Wanalyzer-tainted-size to list of options effectively enabled by -fanalyzer. (-Wanalyzer-tainted-allocation-size): New. (-Wanalyzer-tainted-array-index): Tweak wording; add link to CWE. (-Wanalyzer-tainted-divisor): New. (-Wanalyzer-tainted-offset): New. (-Wanalyzer-tainted-size): New. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/pr93382.c: Tweak expected wording. * gcc.dg/analyzer/taint-alloc-1.c: New test. * gcc.dg/analyzer/taint-alloc-2.c: New test. * gcc.dg/analyzer/taint-divisor-1.c: New test. * gcc.dg/analyzer/taint-1.c: Rename to... * gcc.dg/analyzer/taint-read-index-1.c: ...this. Tweak expected wording. Mark some events as xfail. * gcc.dg/analyzer/taint-read-offset-1.c: New test. * gcc.dg/analyzer/taint-size-1.c: New test. * gcc.dg/analyzer/taint-write-index-1.c: New test. * gcc.dg/analyzer/taint-write-offset-1.c: New test. Signed-off-by: David Malcolm <dmalcolm@redhat.com> --- gcc/analyzer/analyzer.opt | 16 + gcc/analyzer/engine.cc | 18 + gcc/analyzer/exploded-graph.h | 3 + gcc/analyzer/program-state.cc | 26 +- gcc/analyzer/region-model-impl-calls.cc | 15 +- gcc/analyzer/region-model.cc | 47 +- gcc/analyzer/region-model.h | 27 +- gcc/analyzer/sm-taint.cc | 826 ++++++++++++++++-- gcc/analyzer/sm.h | 9 + gcc/doc/invoke.texi | 66 +- gcc/testsuite/gcc.dg/analyzer/pr93382.c | 2 +- gcc/testsuite/gcc.dg/analyzer/taint-alloc-1.c | 64 ++ gcc/testsuite/gcc.dg/analyzer/taint-alloc-2.c | 27 + .../gcc.dg/analyzer/taint-divisor-1.c | 26 + .../{taint-1.c => taint-read-index-1.c} | 19 +- .../gcc.dg/analyzer/taint-read-offset-1.c | 128 +++ gcc/testsuite/gcc.dg/analyzer/taint-size-1.c | 32 + .../gcc.dg/analyzer/taint-write-index-1.c | 132 +++ .../gcc.dg/analyzer/taint-write-offset-1.c | 132 +++ 19 files changed, 1492 insertions(+), 123 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-alloc-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-alloc-2.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-divisor-1.c rename gcc/testsuite/gcc.dg/analyzer/{taint-1.c => taint-read-index-1.c} (72%) create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-read-offset-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-size-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-write-index-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c