[committed] analyzer: add four new taint-based warnings

Message ID 20211113143202.2073099-1-dmalcolm@redhat.com
State Committed
Headers
Series [committed] analyzer: add four new taint-based warnings |

Commit Message

David Malcolm Nov. 13, 2021, 2:32 p.m. UTC
  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
  

Patch

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 6ddb6e3abb3..c85e30f8b11 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -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.
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index b29a21cce30..096e219392d 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -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
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index b9c17672aec..9b18b487999 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -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;
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 8230140cec6..1c87af0f00b 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -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);
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index ff2ae9ca77d..90d4cf9c2db 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -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 ())
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index a14d107709c..416a5ac7249 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -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);
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 5fabf7881e2..13e8109aa51 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -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
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 721d3eabb8f..0a51a1fe2ea 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -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 */
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 02faffbff99..b8570e8a1ac 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -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;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a7c4d24a762..518a5216c73 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -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
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93382.c b/gcc/testsuite/gcc.dg/analyzer/pr93382.c
index 210b97d1a47..1e6612ddc05 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr93382.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr93382.c
@@ -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 *-*-* } } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-alloc-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-1.c
new file mode 100644
index 00000000000..102aa4b9220
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-1.c
@@ -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" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-alloc-2.c b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-2.c
new file mode 100644
index 00000000000..72dbca5cbf0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-2.c
@@ -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
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-divisor-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-divisor-1.c
new file mode 100644
index 00000000000..5a5a0b93ce0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-divisor-1.c
@@ -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" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-read-index-1.c
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
index cd46dd5fc14..71c0816fd1f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/taint-1.c
+++ b/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
     }
   }
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-read-offset-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-read-offset-1.c
new file mode 100644
index 00000000000..6db59bcc615
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-read-offset-1.c
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c
new file mode 100644
index 00000000000..64c9c26aa2d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c
@@ -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
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-write-index-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-write-index-1.c
new file mode 100644
index 00000000000..cc7ab1ca4f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-write-index-1.c
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c
new file mode 100644
index 00000000000..d0df6220315
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-write-offset-1.c
@@ -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;
+}