public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r13-2573] analyzer: implement trust boundaries via a plugin for Linux kernel
@ 2022-09-09 21:16 David Malcolm
  0 siblings, 0 replies; only message in thread
From: David Malcolm @ 2022-09-09 21:16 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:c81b60b8c6ff3d4db2e395a628e114df812cfc48

commit r13-2573-gc81b60b8c6ff3d4db2e395a628e114df812cfc48
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Fri Sep 9 17:13:04 2022 -0400

    analyzer: implement trust boundaries via a plugin for Linux kernel
    
    This is a less ambitious version of:
      [PATCH 0/6] RFC: adding support to GCC for detecting trust boundaries
        https://gcc.gnu.org/pipermail/gcc-patches/2021-November/584372.html
    
    Earlier versions of this patch attempted:
    (a) various ways of identifying "untrusted" memory regions
    (b) providing a way to support the Linux kernel's "__user" annotation,
        either via type attributes, or via custom address spaces
    (c) enough attributes to identify "copy_from_user" and "copy_to_user",
    (d) wiring all of the above together to detect infoleaks and taint
    
    This patch adds a new -Wanalyzer-exposure-through-uninit-copy, emitted
    by -fanalyzer if it detects copying of uninitialized data through
    a pointer to an untrusted region, but requires a plugin to tell it when
    a copy crosses a trust boundary.
    
    This patch adds a proof-of-concept gcc plugin for the analyzer for use
    with the Linux kernel that special-cases calls to "copy_from_user" and
    calls to "copy_to_user": calls to copy_to_user are checked for
    -Wanalyzer-exposure-through-uninit-copy, and data copied via
    copy_from_user is marked as tainted when -fanalyzer-checker=taint is
    active.
    
    This is very much just a proof-of-concept.  A big limitation is that the
    copy_{from,to}_user special-casing only happens if these functions have
    no body in the TU being analyzed, which isn't the case for a normal
    kernel build.  I'd much prefer to provide a more general mechanism for
    handling such behavior without resorting to plugins (e.g. via attributes
    or custom address spaces), but in the interest of not "letting perfect
    be the enemy of the good" this patch at least allows parts of this
    "trust boundaries" code to be merged for experimentation with the idea.
    
    The -Wanalyzer-exposure-through-uninit-copy diagnostic uses notes to
    express what fields and padding within a struct have not been initialized.
    For example:
    
    infoleak-CVE-2011-1078-2.c: In function 'test_1':
    infoleak-CVE-2011-1078-2.c:32:9: warning: potential exposure of sensitive
      information by copying uninitialized data from stack across trust
      boundary [CWE-200] [-Wanalyzer-exposure-through-uninit-copy]
       32 |         copy_to_user(optval, &cinfo, sizeof(cinfo));
          |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      'test_1': events 1-3
        |
        |   25 |         struct sco_conninfo cinfo;
        |      |                             ^~~~~
        |      |                             |
        |      |                             (1) region created on stack here
        |      |                             (2) capacity: 6 bytes
        |......
        |   32 |         copy_to_user(optval, &cinfo, sizeof(cinfo));
        |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        |      |         |
        |      |         (3) uninitialized data copied from stack here
        |
    infoleak-CVE-2011-1078-2.c:32:9: note: 1 byte is uninitialized
       32 |         copy_to_user(optval, &cinfo, sizeof(cinfo));
          |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    infoleak-CVE-2011-1078-2.c:18:15: note: padding after field 'dev_class'
      is uninitialized (1 byte)
       18 |         __u8  dev_class[3];
          |               ^~~~~~~~~
    infoleak-CVE-2011-1078-2.c:25:29: note: suggest forcing
      zero-initialization by providing a '{0}' initializer
       25 |         struct sco_conninfo cinfo;
          |                             ^~~~~
          |                                   = {0}
    
    For taint-detection, the patch includes a series of reproducers for
    detecting CVE-2011-0521.  Unfortunately the analyzer doesn't yet detect
    the issue until the code has been significantly simplified from its
    original form: currently only in -5.c and -6.c in the series of test
    (see notes in the individual cases), such as:
    
    taint-CVE-2011-0521-6.c:33:48: warning: use of attacker-controlled value
      '*info.num' in array lookup without bounds checking [CWE-129]
      [-Wanalyzer-tainted-array-index]
       33 |             av7110->ci_slot[info->num].num = info->num;
          |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
      'test_1': events 1-3
        |
        |   19 |    if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0)
        |      |        ^
        |      |        |
        |      |        (1) following 'false' branch...
        |......
        |   23 |             struct dvb_device *dvbdev = file->private_data;
        |      |                                ~~~~~~
        |      |                                |
        |      |                                (2) ...to here
        |......
        |   33 |             av7110->ci_slot[info->num].num = info->num;
        |      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        |      |                                            |
        |      |                                            (3) use of attacker-controlled value '*info.num' in array lookup without bounds checking
        |
    
    The patch also includes various infoleak and taint cases from my
    antipatterns.ko kernel module:
      https://github.com/davidmalcolm/antipatterns.ko
    
    gcc/analyzer/ChangeLog:
            * analyzer.opt (Wanalyzer-exposure-through-uninit-copy): New.
            * checker-path.cc (region_creation_event::region_creation_event):
            Add "capacity" and "kind" params.
            (region_creation_event::get_desc): Generalize to different kinds
            of event.
            (checker_path::add_region_creation_event): Convert to...
            (checker_path::add_region_creation_events): ...this.
            * checker-path.h (enum rce_kind): New.
            (region_creation_event::region_creation_event): Add "capacity" and
            "kind" params.
            (region_creation_event::m_capacity): New field.
            (region_creation_event::m_rce_kind): New field.
            (checker_path::add_region_creation_event): Convert to...
            (checker_path::add_region_creation_events): ...this.
            * diagnostic-manager.cc (diagnostic_manager::build_emission_path):
            Update for multiple region creation events.
            (diagnostic_manager::add_event_on_final_node): Likewise.
            (diagnostic_manager::add_events_for_eedge): Likewise.
            * region-model-impl-calls.cc (call_details::get_logger): New.
            * region-model.cc: Define INCLUDE_MEMORY before including
            "system.h".  Include "gcc-rich-location.h".
            (class record_layout): New.
            (class exposure_through_uninit_copy): New.
            (contains_uninit_p): New.
            (region_model::maybe_complain_about_infoleak): New.
            * region-model.h (call_details::get_logger): New decl.
            (region_model::maybe_complain_about_infoleak): New decl.
            (region_model::mark_as_tainted): New decl.
            * sm-taint.cc (region_model::mark_as_tainted): New.
    
    gcc/ChangeLog:
            * doc/invoke.texi (Static Analyzer Options): Add
            -Wanalyzer-exposure-through-uninit-copy.
    
    gcc/testsuite/ChangeLog:
            * gcc.dg/plugin/analyzer_kernel_plugin.c: New test.
            * gcc.dg/plugin/copy_from_user-1.c: New test.
            * gcc.dg/plugin/infoleak-1.c: New test.
            * gcc.dg/plugin/infoleak-2.c: New test.
            * gcc.dg/plugin/infoleak-3.c: New test.
            * gcc.dg/plugin/infoleak-CVE-2011-1078-1.c: New test.
            * gcc.dg/plugin/infoleak-CVE-2011-1078-2.c: New test.
            * gcc.dg/plugin/infoleak-CVE-2014-1446-1.c: New test.
            * gcc.dg/plugin/infoleak-CVE-2017-18549-1.c: New test.
            * gcc.dg/plugin/infoleak-CVE-2017-18550-1.c: New test.
            * gcc.dg/plugin/infoleak-antipatterns-1.c: New test.
            * gcc.dg/plugin/infoleak-fixit-1.c: New test.
            * gcc.dg/plugin/infoleak-net-ethtool-ioctl.c: New test.
            * gcc.dg/plugin/infoleak-vfio_iommu_type1.c: New test.
            * gcc.dg/plugin/plugin.exp (plugin_test_list): Add
            analyzer_kernel_plugin.c and the new test cases.
            * gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-1.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-2.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-3.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-4.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-5.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521-6.c: New test.
            * gcc.dg/plugin/taint-CVE-2011-0521.h: New test.
            * gcc.dg/plugin/taint-antipatterns-1.c: New test.
            * gcc.dg/plugin/test-uaccess.h: New header for tests.
    
    Signed-off-by: David Malcolm <dmalcolm@redhat.com>

Diff:
---
 gcc/analyzer/analyzer.opt                          |   4 +
 gcc/analyzer/checker-path.cc                       |  91 +++-
 gcc/analyzer/checker-path.h                        |  34 +-
 gcc/analyzer/diagnostic-manager.cc                 |  31 +-
 gcc/analyzer/region-model-impl-calls.cc            |  11 +
 gcc/analyzer/region-model.cc                       | 568 +++++++++++++++++++++
 gcc/analyzer/region-model.h                        |  11 +
 gcc/analyzer/sm-taint.cc                           |  27 +
 gcc/doc/invoke.texi                                |  16 +
 .../gcc.dg/plugin/analyzer_kernel_plugin.c         | 237 +++++++++
 gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c     |  45 ++
 gcc/testsuite/gcc.dg/plugin/infoleak-1.c           | 185 +++++++
 gcc/testsuite/gcc.dg/plugin/infoleak-2.c           |  33 ++
 gcc/testsuite/gcc.dg/plugin/infoleak-3.c           | 145 ++++++
 .../gcc.dg/plugin/infoleak-CVE-2011-1078-1.c       | 138 +++++
 .../gcc.dg/plugin/infoleak-CVE-2011-1078-2.c       |  46 ++
 .../gcc.dg/plugin/infoleak-CVE-2014-1446-1.c       | 117 +++++
 .../gcc.dg/plugin/infoleak-CVE-2017-18549-1.c      | 105 ++++
 .../gcc.dg/plugin/infoleak-CVE-2017-18550-1.c      | 175 +++++++
 .../gcc.dg/plugin/infoleak-antipatterns-1.c        | 166 ++++++
 gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c     |  26 +
 .../gcc.dg/plugin/infoleak-net-ethtool-ioctl.c     |  82 +++
 .../gcc.dg/plugin/infoleak-vfio_iommu_type1.c      |  44 ++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |  24 +
 .../gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c    | 115 +++++
 .../gcc.dg/plugin/taint-CVE-2011-0521-1.c          | 115 +++++
 .../gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c    |  98 ++++
 .../gcc.dg/plugin/taint-CVE-2011-0521-2.c          |  95 ++++
 .../gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c    |  61 +++
 .../gcc.dg/plugin/taint-CVE-2011-0521-3.c          |  59 +++
 .../gcc.dg/plugin/taint-CVE-2011-0521-4.c          |  45 ++
 .../gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c    |  46 ++
 .../gcc.dg/plugin/taint-CVE-2011-0521-5.c          |  45 ++
 .../gcc.dg/plugin/taint-CVE-2011-0521-6.c          |  42 ++
 gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h  | 136 +++++
 gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c | 139 +++++
 gcc/testsuite/gcc.dg/plugin/test-uaccess.h         |  10 +
 37 files changed, 3336 insertions(+), 31 deletions(-)

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 437ea92e130..dbab3b82deb 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -70,6 +70,10 @@ Wanalyzer-exposure-through-output-file
 Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
 Warn about code paths in which sensitive data is written to a file.
 
+Wanalyzer-exposure-through-uninit-copy
+Common Var(warn_analyzer_exposure_through_uninit_copy) Init(1) Warning
+Warn about code paths in which sensitive data is copied across a security boundary.
+
 Wanalyzer-fd-access-mode-mismatch
 Common Var(warn_analyzer_fd_mode_mismatch) Init(1) Warning
 Warn about code paths in which read on a write-only file descriptor is attempted, or vice versa.
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 273f40d3a57..22bae2f34b1 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -288,16 +288,25 @@ statement_event::get_desc (bool) const
 /* class region_creation_event : public checker_event.  */
 
 region_creation_event::region_creation_event (const region *reg,
+					      tree capacity,
+					      enum rce_kind kind,
 					      location_t loc,
 					      tree fndecl,
 					      int depth)
 : checker_event (EK_REGION_CREATION, loc, fndecl, depth),
-  m_reg (reg)
+  m_reg (reg),
+  m_capacity (capacity),
+  m_rce_kind (kind)
 {
+  if (m_rce_kind == RCE_CAPACITY)
+    gcc_assert (capacity);
 }
 
 /* Implementation of diagnostic_event::get_desc vfunc for
-   region_creation_event.  */
+   region_creation_event.
+   There are effectively 3 kinds of region_region_event, to
+   avoid combinatorial explosion by trying to convy the
+   information in a single message.  */
 
 label_text
 region_creation_event::get_desc (bool can_colorize) const
@@ -311,14 +320,50 @@ region_creation_event::get_desc (bool can_colorize) const
 	return custom_desc;
     }
 
-  switch (m_reg->get_memory_space ())
+  switch (m_rce_kind)
     {
     default:
-      return label_text::borrow ("region created here");
-    case MEMSPACE_STACK:
-      return label_text::borrow ("region created on stack here");
-    case MEMSPACE_HEAP:
-      return label_text::borrow ("region created on heap here");
+      gcc_unreachable ();
+
+    case RCE_MEM_SPACE:
+      switch (m_reg->get_memory_space ())
+	{
+	default:
+	  return label_text::borrow ("region created here");
+	case MEMSPACE_STACK:
+	  return label_text::borrow ("region created on stack here");
+	case MEMSPACE_HEAP:
+	  return label_text::borrow ("region created on heap here");
+	}
+      break;
+
+    case RCE_CAPACITY:
+      gcc_assert (m_capacity);
+      if (TREE_CODE (m_capacity) == INTEGER_CST)
+	{
+	  unsigned HOST_WIDE_INT hwi = tree_to_uhwi (m_capacity);
+	  if (hwi == 1)
+	    return make_label_text (can_colorize,
+				    "capacity: %wu byte", hwi);
+	  else
+	    return make_label_text (can_colorize,
+				    "capacity: %wu bytes", hwi);
+	}
+      else
+	return make_label_text (can_colorize,
+				"capacity: %qE bytes", m_capacity);
+
+    case RCE_DEBUG:
+      {
+	pretty_printer pp;
+	pp_format_decoder (&pp) = default_tree_printer;
+	pp_string (&pp, "region creation: ");
+	m_reg->dump_to_pp (&pp, true);
+	if (m_capacity)
+	  pp_printf (&pp, " capacity: %qE", m_capacity);
+	return label_text::take (xstrdup (pp_formatted_text (&pp)));
+      }
+      break;
     }
 }
 
@@ -1207,15 +1252,33 @@ checker_path::debug () const
     }
 }
 
-/* Add region_creation_event instance to this path for REG,
-   describing whether REG is on the stack or heap.  */
+/* Add region_creation_event instances to this path for REG,
+   describing whether REG is on the stack or heap and what
+   its capacity is (if known).
+   If DEBUG is true, also create an RCE_DEBUG event.  */
 
 void
-checker_path::add_region_creation_event (const region *reg,
-					 location_t loc,
-					 tree fndecl, int depth)
+checker_path::add_region_creation_events (const region *reg,
+					  const region_model *model,
+					  location_t loc,
+					  tree fndecl, int depth,
+					  bool debug)
 {
-  add_event (new region_creation_event (reg, loc, fndecl, depth));
+  tree capacity = NULL_TREE;
+  if (model)
+    if (const svalue *capacity_sval = model->get_capacity (reg))
+      capacity = model->get_representative_tree (capacity_sval);
+
+  add_event (new region_creation_event (reg, capacity, RCE_MEM_SPACE,
+					loc, fndecl, depth));
+
+  if (capacity)
+    add_event (new region_creation_event (reg, capacity, RCE_CAPACITY,
+					  loc, fndecl, depth));
+
+  if (debug)
+    add_event (new region_creation_event (reg, capacity, RCE_DEBUG,
+					  loc, fndecl, depth));
 }
 
 /* Add a warning_event to the end of this path.  */
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 8e48d8a07ab..5d009340189 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -210,19 +210,43 @@ public:
   const program_state m_dst_state;
 };
 
+/* There are too many combinations to express region creation in one message,
+   so we emit multiple region_creation_event instances when each pertinent
+   region is created.
+
+   This enum distinguishes between the different messages.  */
+
+enum rce_kind
+{
+  /* Generate a message based on the memory space of the region
+     e.g. "region created on stack here".  */
+  RCE_MEM_SPACE,
+
+  /* Generate a message based on the capacity of the region
+     e.g. "capacity: 100 bytes".  */
+  RCE_CAPACITY,
+
+  /* Generate a debug message.  */
+  RCE_DEBUG
+};
+
 /* A concrete event subclass describing the creation of a region that
-   is significant for a diagnostic  e.g. "region created on stack here".  */
+   is significant for a diagnostic.  */
 
 class region_creation_event : public checker_event
 {
 public:
   region_creation_event (const region *reg,
+			 tree capacity,
+			 enum rce_kind kind,
 			 location_t loc, tree fndecl, int depth);
 
   label_text get_desc (bool can_colorize) const final override;
 
 private:
   const region *m_reg;
+  tree m_capacity;
+  enum rce_kind m_rce_kind;
 };
 
 /* An event subclass describing the entry to a function.  */
@@ -632,9 +656,11 @@ public:
     m_events[idx] = new_event;
   }
 
-  void add_region_creation_event (const region *reg,
-				  location_t loc,
-				  tree fndecl, int depth);
+  void add_region_creation_events (const region *reg,
+				   const region_model *model,
+				   location_t loc,
+				   tree fndecl, int depth,
+				   bool debug);
 
   void add_final_event (const state_machine *sm,
 			const exploded_node *enode, const gimple *stmt,
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index fded8281e57..2d185a1f3e6 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1460,11 +1460,12 @@ diagnostic_manager::build_emission_path (const path_builder &pb,
 	      if (DECL_P (decl)
 		  && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION)
 		{
-		  emission_path->add_region_creation_event
-		    (reg,
+		  emission_path->add_region_creation_events
+		    (reg, NULL,
 		     DECL_SOURCE_LOCATION (decl),
 		     NULL_TREE,
-		     0);
+		     0,
+		     m_verbosity > 3);
 		}
 	  }
 	}
@@ -1524,11 +1525,13 @@ diagnostic_manager::add_event_on_final_node (const exploded_node *final_enode,
 		  break;
 		case RK_HEAP_ALLOCATED:
 		case RK_ALLOCA:
-		  emission_path->add_region_creation_event
+		  emission_path->add_region_creation_events
 		    (reg,
-		    src_point.get_location (),
-		    src_point.get_fndecl (),
-		    src_stack_depth);
+		     dst_model,
+		     src_point.get_location (),
+		     src_point.get_fndecl (),
+		     src_stack_depth,
+		     false);
 		  emitted = true;
 		  break;
 		}
@@ -1939,11 +1942,12 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
 			if (DECL_P (decl)
 			    && DECL_SOURCE_LOCATION (decl) != UNKNOWN_LOCATION)
 			  {
-			    emission_path->add_region_creation_event
-			      (reg,
+			    emission_path->add_region_creation_events
+			      (reg, dst_state.m_region_model,
 			       DECL_SOURCE_LOCATION (decl),
 			       dst_point.get_fndecl (),
-			       dst_stack_depth);
+			       dst_stack_depth,
+			       m_verbosity > 3);
 			  }
 		    }
 	    }
@@ -2033,11 +2037,12 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
 		    break;
 		  case RK_HEAP_ALLOCATED:
 		  case RK_ALLOCA:
-		    emission_path->add_region_creation_event
-		      (reg,
+		    emission_path->add_region_creation_events
+		      (reg, dst_model,
 		       src_point.get_location (),
 		       src_point.get_fndecl (),
-		       src_stack_depth);
+		       src_stack_depth,
+		       m_verbosity > 3);
 		    break;
 		  }
 	    }
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 617491be306..71fb2770143 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -91,6 +91,17 @@ call_details::get_manager () const
   return m_model->get_manager ();
 }
 
+/* Get any logger associated with this object.  */
+
+logger *
+call_details::get_logger () const
+{
+  if (m_ctxt)
+    return m_ctxt->get_logger ();
+  else
+    return NULL;
+}
+
 /* Get any uncertainty_t associated with the region_model_context.  */
 
 uncertainty_t *
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index bc9db69315f..6eeb684844d 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
+#define INCLUDE_MEMORY
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -74,6 +75,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa-iterators.h"
 #include "calls.h"
 #include "is-a.h"
+#include "gcc-rich-location.h"
 
 #if ENABLE_ANALYZER
 
@@ -5784,6 +5786,572 @@ region_model::unset_dynamic_extents (const region *reg)
   m_dynamic_extents.remove (reg);
 }
 
+/* Information of the layout of a RECORD_TYPE, capturing it as a vector
+   of items, where each item is either a field or padding.  */
+
+class record_layout
+{
+public:
+  /* An item within a record; either a field, or padding after a field.  */
+  struct item
+  {
+  public:
+    item (const bit_range &br,
+	  tree field,
+	  bool is_padding)
+    : m_bit_range (br),
+      m_field (field),
+      m_is_padding (is_padding)
+    {
+    }
+
+    bit_offset_t get_start_bit_offset () const
+    {
+      return m_bit_range.get_start_bit_offset ();
+    }
+    bit_offset_t get_next_bit_offset () const
+    {
+      return m_bit_range.get_next_bit_offset ();
+    }
+
+    bool contains_p (bit_offset_t offset) const
+    {
+      return m_bit_range.contains_p (offset);
+    }
+
+    void dump_to_pp (pretty_printer *pp) const
+    {
+      if (m_is_padding)
+	pp_printf (pp, "padding after %qD", m_field);
+      else
+	pp_printf (pp, "%qD", m_field);
+      pp_string (pp, ", ");
+      m_bit_range.dump_to_pp (pp);
+    }
+
+    bit_range m_bit_range;
+    tree m_field;
+    bool m_is_padding;
+  };
+
+  record_layout (tree record_type)
+  : m_record_type (record_type)
+  {
+    gcc_assert (TREE_CODE (record_type) == RECORD_TYPE);
+
+    for (tree iter = TYPE_FIELDS (record_type); iter != NULL_TREE;
+	 iter = DECL_CHAIN (iter))
+      {
+	if (TREE_CODE (iter) == FIELD_DECL)
+	  {
+	    int iter_field_offset = int_bit_position (iter);
+	    bit_size_t size_in_bits;
+	    if (!int_size_in_bits (TREE_TYPE (iter), &size_in_bits))
+	      size_in_bits = 0;
+
+	    maybe_pad_to (iter_field_offset);
+
+	    /* Add field.  */
+	    m_items.safe_push (item (bit_range (iter_field_offset,
+						size_in_bits),
+				     iter, false));
+	  }
+      }
+
+    /* Add any trailing padding.  */
+    bit_size_t size_in_bits;
+    if (int_size_in_bits (record_type, &size_in_bits))
+      maybe_pad_to (size_in_bits);
+  }
+
+  void dump_to_pp (pretty_printer *pp) const
+  {
+    unsigned i;
+    item *it;
+    FOR_EACH_VEC_ELT (m_items, i, it)
+      {
+	it->dump_to_pp (pp);
+	pp_newline (pp);
+      }
+  }
+
+  DEBUG_FUNCTION void dump () const
+  {
+    pretty_printer pp;
+    pp_format_decoder (&pp) = default_tree_printer;
+    pp.buffer->stream = stderr;
+    dump_to_pp (&pp);
+    pp_flush (&pp);
+  }
+
+  const record_layout::item *get_item_at (bit_offset_t offset) const
+  {
+    unsigned i;
+    item *it;
+    FOR_EACH_VEC_ELT (m_items, i, it)
+      if (it->contains_p (offset))
+	return it;
+    return NULL;
+  }
+
+private:
+  /* Subroutine of ctor.  Add padding item to NEXT_OFFSET if necessary.  */
+
+  void maybe_pad_to (bit_offset_t next_offset)
+  {
+    if (m_items.length () > 0)
+      {
+	const item &last_item = m_items[m_items.length () - 1];
+	bit_offset_t offset_after_last_item
+	  = last_item.get_next_bit_offset ();
+	if (next_offset > offset_after_last_item)
+	  {
+	    bit_size_t padding_size
+	      = next_offset - offset_after_last_item;
+	    m_items.safe_push (item (bit_range (offset_after_last_item,
+						padding_size),
+				     last_item.m_field, true));
+	  }
+      }
+  }
+
+  tree m_record_type;
+  auto_vec<item> m_items;
+};
+
+/* A subclass of pending_diagnostic for complaining about uninitialized data
+   being copied across a trust boundary to an untrusted output
+   (e.g. copy_to_user infoleaks in the Linux kernel).  */
+
+class exposure_through_uninit_copy
+  : public pending_diagnostic_subclass<exposure_through_uninit_copy>
+{
+public:
+  exposure_through_uninit_copy (const region *src_region,
+				const region *dest_region,
+				const svalue *copied_sval,
+				region_model_manager *mgr)
+  : m_src_region (src_region),
+    m_dest_region (dest_region),
+    m_copied_sval (copied_sval),
+    m_mgr (mgr)
+  {
+    gcc_assert (m_copied_sval->get_kind () == SK_POISONED
+		|| m_copied_sval->get_kind () == SK_COMPOUND);
+  }
+
+  const char *get_kind () const final override
+  {
+    return "exposure_through_uninit_copy";
+  }
+
+  bool operator== (const exposure_through_uninit_copy &other) const
+  {
+    return (m_src_region == other.m_src_region
+	    && m_dest_region == other.m_dest_region
+	    && m_copied_sval == other.m_copied_sval);
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_exposure_through_uninit_copy;
+  }
+
+  bool emit (rich_location *rich_loc) final override
+  {
+    diagnostic_metadata m;
+    /* CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.  */
+    m.add_cwe (200);
+    enum memory_space mem_space = get_src_memory_space ();
+    bool warned;
+    switch (mem_space)
+      {
+      default:
+	warned = warning_meta
+	  (rich_loc, m, get_controlling_option (),
+	   "potential exposure of sensitive information"
+	   " by copying uninitialized data across trust boundary");
+	break;
+      case MEMSPACE_STACK:
+	warned = warning_meta
+	  (rich_loc, m, get_controlling_option (),
+	   "potential exposure of sensitive information"
+	   " by copying uninitialized data from stack across trust boundary");
+	break;
+      case MEMSPACE_HEAP:
+	warned = warning_meta
+	  (rich_loc, m, get_controlling_option (),
+	   "potential exposure of sensitive information"
+	   " by copying uninitialized data from heap across trust boundary");
+	break;
+      }
+    if (warned)
+      {
+	location_t loc = rich_loc->get_loc ();
+	inform_number_of_uninit_bits (loc);
+	complain_about_uninit_ranges (loc);
+
+	if (mem_space == MEMSPACE_STACK)
+	  maybe_emit_fixit_hint ();
+      }
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &) final override
+  {
+    enum memory_space mem_space = get_src_memory_space ();
+    switch (mem_space)
+      {
+      default:
+	return label_text::borrow ("uninitialized data copied here");
+
+      case MEMSPACE_STACK:
+	return label_text::borrow ("uninitialized data copied from stack here");
+
+      case MEMSPACE_HEAP:
+	return label_text::borrow ("uninitialized data copied from heap here");
+      }
+  }
+
+  void mark_interesting_stuff (interesting_t *interest) final override
+  {
+    if (m_src_region)
+      interest->add_region_creation (m_src_region);
+  }
+
+private:
+  enum memory_space get_src_memory_space () const
+  {
+    return m_src_region ? m_src_region->get_memory_space () : MEMSPACE_UNKNOWN;
+  }
+
+  bit_size_t calc_num_uninit_bits () const
+  {
+    switch (m_copied_sval->get_kind ())
+      {
+      default:
+	gcc_unreachable ();
+	break;
+      case SK_POISONED:
+	{
+	  const poisoned_svalue *poisoned_sval
+	    = as_a <const poisoned_svalue *> (m_copied_sval);
+	  gcc_assert (poisoned_sval->get_poison_kind () == POISON_KIND_UNINIT);
+
+	  /* Give up if don't have type information.  */
+	  if (m_copied_sval->get_type () == NULL_TREE)
+	    return 0;
+
+	  bit_size_t size_in_bits;
+	  if (int_size_in_bits (m_copied_sval->get_type (), &size_in_bits))
+	    return size_in_bits;
+
+	  /* Give up if we can't get the size of the type.  */
+	  return 0;
+	}
+	break;
+      case SK_COMPOUND:
+	{
+	  const compound_svalue *compound_sval
+	    = as_a <const compound_svalue *> (m_copied_sval);
+	  bit_size_t result = 0;
+	  /* Find keys for uninit svals.  */
+	  for (auto iter : *compound_sval)
+	    {
+	      const svalue *sval = iter.second;
+	      if (const poisoned_svalue *psval
+		  = sval->dyn_cast_poisoned_svalue ())
+		if (psval->get_poison_kind () == POISON_KIND_UNINIT)
+		  {
+		    const binding_key *key = iter.first;
+		    const concrete_binding *ckey
+		      = key->dyn_cast_concrete_binding ();
+		    gcc_assert (ckey);
+		    result += ckey->get_size_in_bits ();
+		  }
+	    }
+	  return result;
+	}
+      }
+  }
+
+  void inform_number_of_uninit_bits (location_t loc) const
+  {
+    bit_size_t num_uninit_bits = calc_num_uninit_bits ();
+    if (num_uninit_bits <= 0)
+      return;
+    if (num_uninit_bits % BITS_PER_UNIT == 0)
+      {
+	/* Express in bytes.  */
+	byte_size_t num_uninit_bytes = num_uninit_bits / BITS_PER_UNIT;
+	if (num_uninit_bytes == 1)
+	  inform (loc, "1 byte is uninitialized");
+	else
+	  inform (loc,
+		  "%wu bytes are uninitialized", num_uninit_bytes.to_uhwi ());
+      }
+    else
+      {
+	/* Express in bits.  */
+	if (num_uninit_bits == 1)
+	  inform (loc, "1 bit is uninitialized");
+	else
+	  inform (loc,
+		  "%wu bits are uninitialized", num_uninit_bits.to_uhwi ());
+      }
+  }
+
+  void complain_about_uninit_ranges (location_t loc) const
+  {
+    if (const compound_svalue *compound_sval
+	= m_copied_sval->dyn_cast_compound_svalue ())
+      {
+	/* Find keys for uninit svals.  */
+	auto_vec<const concrete_binding *> uninit_keys;
+	for (auto iter : *compound_sval)
+	  {
+	    const svalue *sval = iter.second;
+	    if (const poisoned_svalue *psval
+		= sval->dyn_cast_poisoned_svalue ())
+	      if (psval->get_poison_kind () == POISON_KIND_UNINIT)
+		{
+		  const binding_key *key = iter.first;
+		  const concrete_binding *ckey
+		    = key->dyn_cast_concrete_binding ();
+		  gcc_assert (ckey);
+		  uninit_keys.safe_push (ckey);
+		}
+	  }
+	/* Complain about them in sorted order.  */
+	uninit_keys.qsort (concrete_binding::cmp_ptr_ptr);
+
+	std::unique_ptr<record_layout> layout;
+
+	tree type = m_copied_sval->get_type ();
+	if (type && TREE_CODE (type) == RECORD_TYPE)
+	  {
+	    // (std::make_unique is C++14)
+	    layout = std::unique_ptr<record_layout> (new record_layout (type));
+
+	    if (0)
+	      layout->dump ();
+	  }
+
+	unsigned i;
+	const concrete_binding *ckey;
+	FOR_EACH_VEC_ELT (uninit_keys, i, ckey)
+	  {
+	    bit_offset_t start_bit = ckey->get_start_bit_offset ();
+	    bit_offset_t next_bit = ckey->get_next_bit_offset ();
+	    complain_about_uninit_range (loc, start_bit, next_bit,
+					 layout.get ());
+	  }
+      }
+  }
+
+  void complain_about_uninit_range (location_t loc,
+				    bit_offset_t start_bit,
+				    bit_offset_t next_bit,
+				    const record_layout *layout) const
+  {
+    if (layout)
+      {
+	while (start_bit < next_bit)
+	  {
+	    if (const record_layout::item *item
+		  = layout->get_item_at (start_bit))
+	      {
+		gcc_assert (start_bit >= item->get_start_bit_offset ());
+		gcc_assert (start_bit < item->get_next_bit_offset ());
+		if (item->get_start_bit_offset () == start_bit
+		    && item->get_next_bit_offset () <= next_bit)
+		  complain_about_fully_uninit_item (*item);
+		else
+		  complain_about_partially_uninit_item (*item);
+		start_bit = item->get_next_bit_offset ();
+		continue;
+	      }
+	    else
+	      break;
+	  }
+      }
+
+    if (start_bit >= next_bit)
+      return;
+
+    if (start_bit % 8 == 0 && next_bit % 8 == 0)
+      {
+	/* Express in bytes.  */
+	byte_offset_t start_byte = start_bit / 8;
+	byte_offset_t last_byte = (next_bit / 8) - 1;
+	if (last_byte == start_byte)
+	  inform (loc,
+		  "byte %wu is uninitialized",
+		  start_byte.to_uhwi ());
+	else
+	  inform (loc,
+		  "bytes %wu - %wu are uninitialized",
+		  start_byte.to_uhwi (),
+		  last_byte.to_uhwi ());
+      }
+    else
+      {
+	/* Express in bits.  */
+	bit_offset_t last_bit = next_bit - 1;
+	if (last_bit == start_bit)
+	  inform (loc,
+		  "bit %wu is uninitialized",
+		  start_bit.to_uhwi ());
+	else
+	  inform (loc,
+		  "bits %wu - %wu are uninitialized",
+		  start_bit.to_uhwi (),
+		  last_bit.to_uhwi ());
+      }
+  }
+
+  static void
+  complain_about_fully_uninit_item (const record_layout::item &item)
+  {
+    tree field = item.m_field;
+    bit_size_t num_bits = item.m_bit_range.m_size_in_bits;
+    if (item.m_is_padding)
+      {
+	if (num_bits % 8 == 0)
+	  {
+	    /* Express in bytes.  */
+	    byte_size_t num_bytes = num_bits / BITS_PER_UNIT;
+	    if (num_bytes == 1)
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "padding after field %qD is uninitialized (1 byte)",
+		      field);
+	    else
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "padding after field %qD is uninitialized (%wu bytes)",
+		      field, num_bytes.to_uhwi ());
+	  }
+	else
+	  {
+	    /* Express in bits.  */
+	    if (num_bits == 1)
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "padding after field %qD is uninitialized (1 bit)",
+		      field);
+	    else
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "padding after field %qD is uninitialized (%wu bits)",
+		      field, num_bits.to_uhwi ());
+	  }
+      }
+    else
+      {
+	if (num_bits % 8 == 0)
+	  {
+	    /* Express in bytes.  */
+	    byte_size_t num_bytes = num_bits / BITS_PER_UNIT;
+	    if (num_bytes == 1)
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "field %qD is uninitialized (1 byte)", field);
+	    else
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "field %qD is uninitialized (%wu bytes)",
+		      field, num_bytes.to_uhwi ());
+	  }
+	else
+	  {
+	    /* Express in bits.  */
+	    if (num_bits == 1)
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "field %qD is uninitialized (1 bit)", field);
+	    else
+	      inform (DECL_SOURCE_LOCATION (field),
+		      "field %qD is uninitialized (%wu bits)",
+		      field, num_bits.to_uhwi ());
+	  }
+      }
+  }
+
+  static void
+  complain_about_partially_uninit_item (const record_layout::item &item)
+  {
+    tree field = item.m_field;
+    if (item.m_is_padding)
+      inform (DECL_SOURCE_LOCATION (field),
+	      "padding after field %qD is partially uninitialized",
+	      field);
+    else
+      inform (DECL_SOURCE_LOCATION (field),
+	      "field %qD is partially uninitialized",
+	      field);
+    /* TODO: ideally we'd describe what parts are uninitialized.  */
+  }
+
+  void maybe_emit_fixit_hint () const
+  {
+    if (tree decl = m_src_region->maybe_get_decl ())
+      {
+	gcc_rich_location hint_richloc (DECL_SOURCE_LOCATION (decl));
+	hint_richloc.add_fixit_insert_after (" = {0}");
+	inform (&hint_richloc,
+		"suggest forcing zero-initialization by"
+		" providing a %<{0}%> initializer");
+      }
+  }
+
+private:
+  const region *m_src_region;
+  const region *m_dest_region;
+  const svalue *m_copied_sval;
+  region_model_manager *m_mgr;
+};
+
+/* Return true if any part of SVAL is uninitialized.  */
+
+static bool
+contains_uninit_p (const svalue *sval)
+{
+  struct uninit_finder : public visitor
+  {
+  public:
+    uninit_finder () : m_found_uninit (false) {}
+    void visit_poisoned_svalue (const poisoned_svalue *sval)
+    {
+      if (sval->get_poison_kind () == POISON_KIND_UNINIT)
+	m_found_uninit = true;
+    }
+    bool m_found_uninit;
+  };
+
+  uninit_finder v;
+  sval->accept (&v);
+
+  return v.m_found_uninit;
+}
+
+/* Function for use by plugins when simulating writing data through a
+   pointer to an "untrusted" region DST_REG (and thus crossing a security
+   boundary), such as copying data to user space in an OS kernel.
+
+   Check that COPIED_SVAL is fully initialized.  If not, complain about
+   an infoleak to CTXT.
+
+   SRC_REG can be NULL; if non-NULL it is used as a hint in the diagnostic
+   as to where COPIED_SVAL came from.  */
+
+void
+region_model::maybe_complain_about_infoleak (const region *dst_reg,
+					     const svalue *copied_sval,
+					     const region *src_reg,
+					     region_model_context *ctxt)
+{
+  /* Check for exposure.  */
+  if (contains_uninit_p (copied_sval))
+    ctxt->warn (new exposure_through_uninit_copy (src_reg,
+						  dst_reg,
+						  copied_sval,
+						  m_mgr));
+}
+
 /* class noop_region_model_context : public region_model_context.  */
 
 void
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 8d2e3daec28..e86720a645c 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -532,6 +532,8 @@ public:
   region_model *get_model () const { return m_model; }
   region_model_manager *get_manager () const;
   region_model_context *get_ctxt () const { return m_ctxt; }
+  logger *get_logger () const;
+
   uncertainty_t *get_uncertainty () const;
   tree get_lhs_type () const { return m_lhs_type; }
   const region *get_lhs_region () const { return m_lhs_region; }
@@ -814,11 +816,20 @@ class region_model
   const svalue *get_string_size (const svalue *sval) const;
   const svalue *get_string_size (const region *reg) const;
 
+  void maybe_complain_about_infoleak (const region *dst_reg,
+				      const svalue *copied_sval,
+				      const region *src_reg,
+				      region_model_context *ctxt);
+
   /* Implemented in sm-malloc.cc  */
   void on_realloc_with_move (const call_details &cd,
 			     const svalue *old_ptr_sval,
 			     const svalue *new_ptr_sval);
 
+  /* Implemented in sm-taint.cc.  */
+  void mark_as_tainted (const svalue *sval,
+			region_model_context *ctxt);
+
  private:
   const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
   const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 549373b322d..f5c0cc13a2f 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -1365,6 +1365,33 @@ region_model::check_dynamic_size_for_taint (enum memory_space mem_space,
     }
 }
 
+/* Mark SVAL as TAINTED.  CTXT must be non-NULL.  */
+
+void
+region_model::mark_as_tainted (const svalue *sval,
+			       region_model_context *ctxt)
+{
+  gcc_assert (sval);
+  gcc_assert (ctxt);
+
+  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;
+
+  smap->set_state (this, sval, taint_sm.m_tainted, NULL, *ext_state);
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 5c066219a7d..eaee5166822 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -447,6 +447,7 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-double-fclose @gol
 -Wno-analyzer-double-free @gol
 -Wno-analyzer-exposure-through-output-file @gol
+-Wno-analyzer-exposure-through-uninit-copy @gol
 -Wno-analyzer-fd-access-mode-mismatch @gol
 -Wno-analyzer-fd-double-close @gol
 -Wno-analyzer-fd-leak @gol
@@ -9797,6 +9798,7 @@ Enabling this option effectively enables the following warnings:
 -Wanalyzer-double-fclose @gol
 -Wanalyzer-double-free @gol
 -Wanalyzer-exposure-through-output-file @gol
+-Wanalyzer-exposure-through-uninit-copy @gol
 -Wanalyzer-fd-access-mode-mismatch @gol
 -Wanalyzer-fd-double-close @gol
 -Wanalyzer-fd-leak @gol
@@ -9899,6 +9901,20 @@ security-sensitive value is written to an output file
 
 See @uref{https://cwe.mitre.org/data/definitions/532.html, CWE-532: Information Exposure Through Log Files}.
 
+@item Wanalyzer-exposure-through-uninit-copy
+@opindex Wanalyzer-exposure-through-uninit-copy
+@opindex Wno-analyzer-exposure-through-uninit-copy
+This warning requires both @option{-fanalyzer} and the use of a plugin
+to specify a function that copies across a ``trust boundary''.  Use
+@option{-Wno-analyzer-exposure-through-uninit-copy} to disable it.
+
+This diagnostic warns for ``infoleaks'' - paths through the code in which
+uninitialized values are copied across a security boundary
+(such as code within an OS kernel that copies a partially-initialized
+struct on the stack to user space).
+
+See @uref{https://cwe.mitre.org/data/definitions/200.html, CWE-200: Exposure of Sensitive Information to an Unauthorized Actor}.
+
 @item -Wno-analyzer-fd-access-mode-mismatch
 @opindex Wanalyzer-fd-access-mode-mismatch
 @opindex Wno-analyzer-fd-access-mode-mismatch
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
new file mode 100644
index 00000000000..6ec08bff73c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
@@ -0,0 +1,237 @@
+/* Proof-of-concept of a -fanalyzer plugin for the Linux kernel.  */
+/* { dg-options "-g" } */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "diagnostic-core.h"
+#include "graphviz.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "stringpool.h"
+#include "convert.h"
+#include "target.h"
+#include "fold-const.h"
+#include "tree-pretty-print.h"
+#include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
+#include "tristate.h"
+#include "bitmap.h"
+#include "selftest.h"
+#include "function.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/call-info.h"
+
+int plugin_is_GPL_compatible;
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Implementation of "copy_from_user" and "copy_to_user".  */
+  
+class copy_across_boundary_fn : public known_function
+{
+ public:
+  virtual bool untrusted_source_p () const = 0;
+  virtual bool untrusted_destination_p () const = 0;
+
+  void impl_call_pre (const call_details &cd) const final override
+  {
+    region_model_manager *mgr = cd.get_manager ();
+    region_model *model = cd.get_model ();
+    region_model_context *ctxt = cd.get_ctxt ();
+
+    const svalue *dest_sval = cd.get_arg_svalue (0);
+    const svalue *src_sval = cd.get_arg_svalue (1);
+    const svalue *num_bytes_sval = cd.get_arg_svalue (2);
+
+    const region *dest_reg = model->deref_rvalue (dest_sval,
+						  cd.get_arg_tree (0),
+						  ctxt);
+    const region *src_reg = model->deref_rvalue (src_sval,
+						 cd.get_arg_tree (1),
+						 ctxt);
+    if (const svalue *bounded_sval
+	  = model->maybe_get_copy_bounds (src_reg, num_bytes_sval))
+      num_bytes_sval = bounded_sval;
+
+    if (tree cst = num_bytes_sval->maybe_get_constant ())
+      if (zerop (cst))
+	/* No-op.  */
+	return;
+
+    const region *sized_src_reg = mgr->get_sized_region (src_reg,
+							 NULL_TREE,
+							 num_bytes_sval);
+
+    const svalue *copied_sval
+      = model->get_store_value (sized_src_reg, ctxt);
+    const region *sized_dest_reg = mgr->get_sized_region (dest_reg,
+							  NULL_TREE,
+							  num_bytes_sval);
+
+    if (ctxt)
+      {
+	/* Bifurcate state, creating a "failure" out-edge.  */
+	ctxt->bifurcate (new copy_failure (cd));
+
+	/* The "unbifurcated" state is the "success" case.  */
+	copy_success success (cd,
+			      sized_dest_reg,
+			      copied_sval,
+			      sized_src_reg,
+			      untrusted_source_p (),
+			      untrusted_destination_p ());
+	success.update_model (model, NULL, ctxt);
+      }
+  }
+
+ private:
+  class copy_success : public success_call_info
+  {
+  public:
+    copy_success (const call_details &cd,
+		  const region *sized_dest_reg,
+		  const svalue *copied_sval,
+		  const region *sized_src_reg,
+		  bool untrusted_source,
+		  bool untrusted_destination)
+    : success_call_info (cd),
+      m_sized_dest_reg (sized_dest_reg),
+      m_copied_sval (copied_sval),
+      m_sized_src_reg (sized_src_reg),
+      m_untrusted_source (untrusted_source),
+      m_untrusted_destination (untrusted_destination)
+    {}
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const final override
+    {
+      call_details cd (get_call_details (model, ctxt));
+      model->update_for_zero_return (cd, true);
+      model->set_value (m_sized_dest_reg, m_copied_sval, ctxt);
+      if (ctxt && m_untrusted_source)
+	model->mark_as_tainted (m_copied_sval, ctxt);
+      if (m_untrusted_destination)
+	model->maybe_complain_about_infoleak (m_sized_dest_reg,
+					      m_copied_sval,
+					      m_sized_src_reg,
+					      ctxt);
+      return true;
+    }
+
+    const region *m_sized_dest_reg;
+    const svalue *m_copied_sval;
+    const region *m_sized_src_reg;
+    bool m_untrusted_source;
+    bool m_untrusted_destination;
+  };
+
+  class copy_failure : public failed_call_info
+  {
+  public:
+    copy_failure (const call_details &cd)
+    : failed_call_info (cd)
+    {}
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const final override
+    {
+      call_details cd (get_call_details (model, ctxt));
+      model->update_for_nonzero_return (cd);
+      /* Leave the destination region untouched.  */
+      return true;
+    }
+  };
+};
+
+/* "copy_from_user".  */
+
+class known_function_copy_from_user : public copy_across_boundary_fn
+{
+public:
+  bool untrusted_source_p () const final override
+  {
+    return true;
+  }
+  bool untrusted_destination_p () const final override
+  {
+    return false;
+  }
+};
+
+/* "copy_to_user".  */
+
+class known_function_copy_to_user : public copy_across_boundary_fn
+{
+public:
+  bool untrusted_source_p () const final override
+  {
+    return false;
+  }
+  bool untrusted_destination_p () const final override
+  {
+    return true;
+  }
+};
+
+/* Callback handler for the PLUGIN_ANALYZER_INIT event.  */
+
+static void
+kernel_analyzer_init_cb (void *gcc_data, void */*user_data*/)
+{
+  ana::plugin_analyzer_init_iface *iface
+    = (ana::plugin_analyzer_init_iface *)gcc_data;
+  LOG_SCOPE (iface->get_logger ());
+  if (0)
+    inform (input_location, "got here: kernel_analyzer_init_cb");
+  iface->register_known_function ("copy_from_user",
+				  new known_function_copy_from_user ());
+  iface->register_known_function ("copy_to_user",
+				  new known_function_copy_to_user ());
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+#if ENABLE_ANALYZER
+  const char *plugin_name = plugin_info->base_name;
+  if (0)
+    inform (input_location, "got here; %qs", plugin_name);
+  register_callback (plugin_info->base_name,
+		     PLUGIN_ANALYZER_INIT,
+		     ana::kernel_analyzer_init_cb,
+		     NULL); /* void *user_data */
+#else
+  sorry_no_analyzer ();
+#endif
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c b/gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c
new file mode 100644
index 00000000000..a1415f38aa6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/copy_from_user-1.c
@@ -0,0 +1,45 @@
+typedef __SIZE_TYPE__ size_t;
+
+#define __user
+
+extern int copy_from_user(void *to, const void __user *from, long n)
+  __attribute__((access (write_only, 1, 3),
+		 access (read_only, 2, 3)
+		 ));
+
+#define   EFAULT          14
+#define   EINVAL          22
+
+/* Taken from Linux: fs/binfmt_misc.c (GPL-2.0-only).  */
+
+int parse_command(const char __user *buffer, size_t count)
+{
+	char s[4];
+
+	if (count > 3)
+		return -EINVAL;
+	if (copy_from_user(s, buffer, count))
+		return -EFAULT;
+	if (!count)
+		return 0;
+	if (s[count - 1] == '\n') /* { dg-bogus "uninit" } */
+		count--;
+	if (count == 1 && s[0] == '0') /* { dg-bogus "uninit" } */
+		return 1;
+	if (count == 1 && s[0] == '1') /* { dg-bogus "uninit" } */
+		return 2;
+	if (count == 2 && s[0] == '-' && s[1] == '1') /* { dg-bogus "uninit" } */
+		return 3;
+	return -EINVAL;
+}
+
+/* Not using return value from copy_from_user.  */
+
+int test_2 (const char __user *buffer, size_t count)
+{
+  char s[4];
+  if (count > 3)
+    return -EINVAL;
+  copy_from_user(s, buffer, count);
+  return 0;  
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-1.c
new file mode 100644
index 00000000000..b4958e7bbb6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-1.c
@@ -0,0 +1,185 @@
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+#include "test-uaccess.h"
+
+typedef unsigned char u8;
+typedef unsigned __INT16_TYPE__ u16;
+typedef unsigned __INT32_TYPE__ u32;
+
+struct s1
+{
+  u32 i;
+};
+
+void test_1a (void __user *dst, u32 a)
+{
+  struct s1 s;
+  s.i = a;
+  copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */
+}
+
+void test_1b (void __user *dst, u32 a)
+{
+  struct s1 s;
+  copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+}
+
+void test_1c (void __user *dst, u32 a)
+{
+  struct s1 s;
+  memset (&s, 0, sizeof (struct s1));
+  copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */
+}
+
+void test_1d (void __user *dst, u32 a)
+{
+  struct s1 s = {0};
+  copy_to_user(dst, &s, sizeof (struct s1)); /* { dg-bogus "" } */
+}
+
+struct s2
+{
+  u32 i;
+  u32 j; /* { dg-message "field 'j' is uninitialized \\(4 bytes\\)" } */
+};
+
+void test_2a (void __user *dst, u32 a)
+{
+  struct s2 s; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */
+  s.i = a;
+  copy_to_user(dst, &s, sizeof (struct s2)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+}
+
+void test_2b (void __user *dst, u32 a)
+{
+  struct s2 s;
+  s.i = a;
+  /* Copy with wrong size (only part of s2).  */
+  copy_to_user(dst, &s, sizeof (struct s1));
+}
+
+void test_2d (void __user *dst, u32 a)
+{
+  struct s2 s = {0};
+  s.i = a;
+  copy_to_user(dst, &s, sizeof (struct s2)); /* { dg-bogus" } */
+}
+
+struct empty {};
+
+void test_empty (void __user *dst)
+{
+  struct empty e;
+  copy_to_user(dst, &e, sizeof (struct empty));
+}
+
+union un_a
+{
+  u32 i;
+  u8  j;
+};
+
+/* As above, but in a different order.  */
+
+union un_b
+{
+  u8  j;
+  u32 i;
+};
+
+void test_union_1a (void __user *dst, u8 v)
+{
+  union un_a u; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */
+  u.j = v;
+  copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+  /* { dg-message "bytes 1 - 3 are uninitialized" "note how much" { target *-*-* } .-2 } */
+}
+
+void test_union_1b (void __user *dst, u8 v)
+{
+  union un_b u; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */
+  u.j = v;
+  copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+  /* { dg-message "bytes 1 - 3 are uninitialized" "note how much" { target *-*-* } .-2 } */
+}
+
+void test_union_2a (void __user *dst, u8 v)
+{
+  union un_a u = {0};
+  u.j = v;
+  copy_to_user(dst, &u, sizeof (union un_a));
+}
+
+void test_union_2b (void __user *dst, u8 v)
+{
+  union un_b u = {0};
+  u.j = v;
+  copy_to_user(dst, &u, sizeof (union un_b));
+}
+
+void test_union_3a (void __user *dst, u32 v)
+{
+  union un_a u;
+  u.i = v;
+  copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-bogus "" } */
+}
+
+void test_union_3b (void __user *dst, u32 v)
+{
+  union un_b u;
+  u.i = v;
+  copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-bogus "" } */
+}
+
+void test_union_4a (void __user *dst, u8 v)
+{
+  union un_a u = {0};
+  copy_to_user(dst, &u, sizeof (union un_a)); /* { dg-bogus "" } */
+}
+
+void test_union_4b (void __user *dst, u8 v)
+{
+  union un_b u = {0};
+  copy_to_user(dst, &u, sizeof (union un_b)); /* { dg-bogus "" } */
+}
+
+struct st_union_5
+{
+  union {
+    u8 f1;
+    u32 f2;
+  } u; /* { dg-message "field 'u' is partially uninitialized" } */
+};
+
+void test_union_5 (void __user *dst, u8 v)
+{
+  struct st_union_5 st; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 4 bytes" "capacity" { target *-*-* } .-1 } */
+
+  /* This write only initializes the u8 within the union "u",
+     leaving the remaining 3 bytes uninitialized.  */
+  st.u.f1 = v;
+
+  copy_to_user (dst, &st, sizeof(st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+}
+
+void test_one_byte (void __user *dst)
+{
+  char src;  /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 1 byte" "capacity" { target *-*-* } .-1 } */
+
+  copy_to_user (dst, &src, sizeof(src)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "1 byte is uninitialized" "note how much" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-2.c b/gcc/testsuite/gcc.dg/plugin/infoleak-2.c
new file mode 100644
index 00000000000..252f8f25918
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-2.c
@@ -0,0 +1,33 @@
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+#include "test-uaccess.h"
+
+typedef unsigned char u8;
+typedef unsigned __INT16_TYPE__ u16;
+typedef unsigned __INT32_TYPE__ u32;
+
+/* Coverage for the various singular and plural forms of bits, bytes, and fields vs padding.  */
+
+struct st
+{
+  u32 a;   /* { dg-message "field 'a' is uninitialized \\(4 bytes\\)" } */
+  int b:1; /* { dg-message "field 'b' is uninitialized \\(1 bit\\)" "field" } */
+           /* { dg-message "padding after field 'b' is uninitialized \\(7 bits\\)" "padding" { target *-*-* } .-1 } */
+  u8 d;    /* { dg-message "field 'd' is uninitialized \\(1 byte\\)" } */
+  int c:7; /* { dg-message "padding after field 'c' is uninitialized \\(9 bits\\)" } */
+  u16 e;   /* { dg-message "padding after field 'e' is uninitialized \\(2 bytes\\)" } */  
+};
+
+void test (void __user *dst, u16 v)
+{
+  struct st s; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 12 bytes" "capacity" { target *-*-* } .-1 } */
+  /* { dg-message "suggest forcing zero-initialization by providing a '\\{0\\}' initializer" "fix-it" { target *-*-* } .-2 } */  
+  s.e = v;
+  copy_to_user(dst, &s, sizeof (struct st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "10 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-3.c b/gcc/testsuite/gcc.dg/plugin/infoleak-3.c
new file mode 100644
index 00000000000..097a0d8d33b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-3.c
@@ -0,0 +1,145 @@
+/* Verify that -Wanalyzer-exposure-through-uninit-copy doesn't get confused
+   if size argument to copy_to_user is an upper bound, rather than a
+   constant.  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include "../analyzer/analyzer-decls.h"
+
+typedef __SIZE_TYPE__ size_t;
+
+#include "test-uaccess.h"
+
+typedef unsigned __INT32_TYPE__ u32;
+
+/* min_t adapted from include/linux/kernel.h.  */
+
+#define min_t(type, x, y) ({			\
+	type __min1 = (x);			\
+	type __min2 = (y);			\
+	__min1 < __min2 ? __min1: __min2; })
+
+struct st
+{
+  u32 a;
+  u32 b;
+};
+
+/* Verify that we cope with min_t.  */
+
+void test_1_full_init (void __user *dst, u32 x, u32 y, unsigned long in_sz)
+{
+  struct st s;
+  s.a = x;
+  s.b = y;
+  unsigned long copy_sz = min_t(unsigned long, in_sz, sizeof(s));
+  copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */
+}
+
+void test_1_partial_init (void __user *dst, u32 x, u32 y, unsigned long in_sz)
+{
+  struct st s;
+  s.a = x;
+  /* s.y not initialized.  */
+  unsigned long copy_sz = min_t(unsigned long, in_sz, sizeof(s));
+  copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */
+}
+
+/* Constant on LHS rather than RHS.  */
+
+void test_2_full_init (void __user *dst, u32 x, u32 y, unsigned long in_sz)
+{
+  struct st s;
+  s.a = x;
+  s.b = y;
+  unsigned long copy_sz = min_t(unsigned long, sizeof(s), in_sz);
+  copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */
+}
+
+void test_2_partial_init (void __user *dst, u32 x, u32 y, unsigned long in_sz)
+{
+  struct st s;
+  s.a = x;
+  /* s.y not initialized.  */
+  unsigned long copy_sz = min_t(unsigned long, sizeof(s), in_sz);
+  copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */
+}
+
+/* min_t with various casts.  */
+
+void test_3_full_init (void __user *dst, u32 x, u32 y, int in_sz)
+{
+  struct st s;
+  s.a = x;
+  s.b = y;
+  int copy_sz = min_t(unsigned int, in_sz, sizeof(s));
+  copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */
+}
+
+void test_3_partial_init (void __user *dst, u32 x, u32 y, int in_sz)
+{
+  struct st s;
+  s.a = x;
+  /* s.y not initialized.  */
+  int copy_sz = min_t(unsigned int, in_sz, sizeof(s));
+  copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */
+}
+
+/* Comparison against an upper bound.  */
+
+void test_4_full_init (void __user *dst, u32 x, u32 y, size_t in_sz)
+{
+  struct st s;
+  s.a = x;
+  s.b = y;
+  
+  size_t copy_sz = in_sz;
+  if (copy_sz > sizeof(s))
+    copy_sz = sizeof(s);
+
+  copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */
+}
+
+void test_4_partial_init (void __user *dst, u32 x, u32 y, size_t in_sz)
+{
+  struct st s;
+  s.a = x;
+  /* s.y not initialized.  */
+  
+  size_t copy_sz = in_sz;
+  if (copy_sz > sizeof(s))
+    copy_sz = sizeof(s);
+
+  copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" } */
+}
+
+/* Comparison against an upper bound with casts.  */
+
+void test_5_full_init (void __user *dst, u32 x, u32 y, int in_sz)
+{
+  struct st s;
+  s.a = x;
+  s.b = y;
+  
+  int copy_sz = in_sz;
+  if (copy_sz > sizeof(s))
+    copy_sz = sizeof(s);
+  copy_to_user(dst, &s, copy_sz); /* { dg-bogus "exposure" } */
+}
+
+/* Comparison against an upper bound with casts.  */
+
+void test_5_partial_init (void __user *dst, u32 x, u32 y, int in_sz)
+{
+  struct st s;
+  s.a = x;
+  /* s.y not initialized.  */
+  
+  int copy_sz = in_sz;
+  if (copy_sz > sizeof(s))
+    copy_sz = sizeof(s);
+
+  copy_to_user(dst, &s, copy_sz); /* { dg-warning "exposure" "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c
new file mode 100644
index 00000000000..3616fbe176b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-1.c
@@ -0,0 +1,138 @@
+/* "The sco_sock_getsockopt_old function in net/bluetooth/sco.c in the
+   Linux kernel before 2.6.39 does not initialize a certain structure,
+   which allows local users to obtain potentially sensitive information
+   from kernel stack memory via the SCO_CONNINFO option."
+
+   Fixed e.g. by c4c896e1471aec3b004a693c689f60be3b17ac86 on linux-2.6.39.y
+   in linux-stable.  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+typedef unsigned char __u8;
+typedef unsigned short __u16;
+
+#include "test-uaccess.h"
+
+/* Adapted from include/asm-generic/uaccess.h.  */
+
+#define get_user(x, ptr)					\
+({								\
+	/* [...snip...] */					\
+	__get_user_fn(sizeof (*(ptr)), ptr, &(x));		\
+	/* [...snip...] */					\
+})
+
+static inline int __get_user_fn(size_t size, const void __user *ptr, void *x)
+{
+	size = copy_from_user(x, ptr, size);
+	return size ? -1 : size;
+}
+
+/* Adapted from include/linux/kernel.h.  */
+
+#define min_t(type, x, y) ({			\
+	type __min1 = (x);			\
+	type __min2 = (y);			\
+	__min1 < __min2 ? __min1: __min2; })
+
+/* Adapted from include/linux/net.h.  */
+
+struct socket {
+	/* [...snip...] */
+	struct sock		*sk;
+	/* [...snip...] */
+};
+
+/* Adapted from include/net/bluetooth/sco.h.  */
+
+struct sco_conninfo {
+	__u16 hci_handle;
+	__u8  dev_class[3]; /* { dg-message "padding after field 'dev_class' is uninitialized \\(1 byte\\)" } */
+};
+
+struct sco_conn {
+
+	struct hci_conn	*hcon;
+	/* [...snip...] */
+};
+
+#define sco_pi(sk) ((struct sco_pinfo *) sk)
+
+struct sco_pinfo {
+	/* [...snip...] */
+	struct sco_conn	*conn;
+};
+
+/* Adapted from include/net/bluetooth/hci_core.h.  */
+
+struct hci_conn {
+	/* [...snip...] */
+	__u16		handle;
+	/* [...snip...] */
+	__u8		dev_class[3];
+	/* [...snip...] */
+};
+
+/* Adapted from sco_sock_getsockopt_old in net/bluetooth/sco.c.  */
+
+static int sco_sock_getsockopt_old_broken(struct socket *sock, int optname, char __user *optval, int __user *optlen)
+{
+	struct sock *sk = sock->sk;
+	/* [...snip...] */
+	struct sco_conninfo cinfo; /* { dg-message "region created on stack here" "where" } */
+				   /* { dg-message "capacity: 6 bytes" "capacity" { target *-*-* } .-1 } */
+	/* Note: 40 bits of fields, padded to 48.  */
+
+	int len, err = 0;
+
+	/* [...snip...] */
+
+	if (get_user(len, optlen))
+		return -1;
+
+	/* [...snip...] */
+
+	/* case SCO_CONNINFO: */
+		cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle;
+		memcpy(cinfo.dev_class, sco_pi(sk)->conn->hcon->dev_class, 3);
+
+		len = min_t(unsigned int, len, sizeof(cinfo));
+		if (copy_to_user(optval, (char *)&cinfo, len)) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" { target *-*-* } } */
+			/* { dg-message "1 byte is uninitialized" "how much note" { target *-*-* } .-1 } */
+			err = -1;
+
+	/* [...snip...] */
+}
+
+static int sco_sock_getsockopt_fixed(struct socket *sock, int optname, char __user *optval, int __user *optlen)
+{
+	struct sock *sk = sock->sk;
+	/* [...snip...] */
+	struct sco_conninfo cinfo;
+	/* Note: 40 bits of fields, padded to 48.  */
+
+	int len, err = 0;
+
+	/* [...snip...] */
+
+	if (get_user(len, optlen))
+		return -1;
+
+	/* [...snip...] */
+
+	/* case SCO_CONNINFO: */
+		/* Infoleak fixed by this memset call.  */
+		memset(&cinfo, 0, sizeof(cinfo));
+		cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle;
+		memcpy(cinfo.dev_class, sco_pi(sk)->conn->hcon->dev_class, 3);
+
+		len = min_t(unsigned int, len, sizeof(cinfo));
+		if (copy_to_user(optval, (char *)&cinfo, len)) /* { dg-bogus "exposure" } */
+			err = -1;
+
+	/* [...snip...] */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c
new file mode 100644
index 00000000000..2096bda7179
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2011-1078-2.c
@@ -0,0 +1,46 @@
+/* Simplified versions of infoleak-CVE-2011-1078-1.c.  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+typedef unsigned char __u8;
+typedef unsigned short __u16;
+
+#include "test-uaccess.h"
+
+/* Adapted from include/net/bluetooth/sco.h.  */
+
+struct sco_conninfo {
+	__u16 hci_handle;
+	__u8  dev_class[3]; /* { dg-message "padding after field 'dev_class' is uninitialized \\(1 byte\\)" } */
+};
+
+/* Adapted from sco_sock_getsockopt_old in net/bluetooth/sco.c.  */
+
+int test_1 (char __user *optval, const struct sco_conninfo *in)
+{
+	struct sco_conninfo cinfo; /* { dg-message "region created on stack here" "where" } */
+				   /* { dg-message "capacity: 6 bytes" "capacity" { target *-*-* } .-1 } */
+	/* Note: 40 bits of fields, padded to 48.  */
+
+	cinfo.hci_handle = in->hci_handle;
+	memcpy(cinfo.dev_class, in->dev_class, 3);
+
+	copy_to_user(optval, &cinfo, sizeof(cinfo)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+	/* { dg-message "1 byte is uninitialized" "how much note" { target *-*-* } .-1 } */
+}
+
+int test_2 (char __user *optval, const struct sco_conninfo *in)
+{
+	struct sco_conninfo cinfo;
+	/* Note: 40 bits of fields, padded to 48.  */
+
+	memset(&cinfo, 0, sizeof(cinfo));
+	cinfo.hci_handle = in->hci_handle;
+	memcpy(cinfo.dev_class, in->dev_class, 3);
+
+	copy_to_user(optval, &cinfo, sizeof(cinfo)); /* { dg-bogus "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2014-1446-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2014-1446-1.c
new file mode 100644
index 00000000000..2726a9c0f38
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2014-1446-1.c
@@ -0,0 +1,117 @@
+/* "The yam_ioctl function in drivers/net/hamradio/yam.c in the Linux kernel
+   before 3.12.8 does not initialize a certain structure member, which allows
+   local users to obtain sensitive information from kernel memory by
+   leveraging the CAP_NET_ADMIN capability for an SIOCYAMGCFG ioctl call."
+
+   Fixed e.g. by e7834c71c2cacc621ddc64bd71f83ef2054f6539 on linux-3.12.y
+   in linux-stable.  */
+
+#include <string.h>
+
+#include "test-uaccess.h"
+
+/* Adapted from include/linux/yam.h  */
+
+struct yamcfg {
+	unsigned int mask;		/* Mask of commands */
+	unsigned int iobase;	/* IO Base of COM port */
+	unsigned int irq;		/* IRQ of COM port */
+	unsigned int bitrate;	/* Bit rate of radio port */
+	unsigned int baudrate;	/* Baud rate of the RS232 port */
+	unsigned int txdelay;	/* TxDelay */
+	unsigned int txtail;	/* TxTail */
+	unsigned int persist;	/* Persistence */
+	unsigned int slottime;	/* Slottime */
+	unsigned int mode;		/* mode 0 (simp), 1(Dupl), 2(Dupl+delay) */
+	unsigned int holddly;	/* PTT delay in FullDuplex 2 mode */
+};
+
+struct yamdrv_ioctl_cfg {
+	int cmd; /* { dg-message "field 'cmd' is uninitialized \\(4 bytes\\)" } */
+	struct yamcfg cfg;
+};
+
+/* Adapted from include/asm-generic/errno-base.h  */
+
+#define	EFAULT		14	/* Bad address */
+
+/* Adapted from drivers/net/hamradio/yam.c  */
+
+struct yam_port {
+	/* [...snip...] */
+
+	int bitrate;
+	int baudrate;
+	int iobase;
+	int irq;
+	int dupmode;
+
+	/* [...snip...] */
+
+	int txd;				/* tx delay */
+	int holdd;				/* duplex ptt delay */
+	int txtail;				/* txtail delay */
+	int slot;				/* slottime */
+	int pers;				/* persistence */
+
+	/* [...snip...] */
+};
+
+/* Broken version, leaving yi.cmd uninitialized.  */
+
+static int yam_ioctl(/* [...snip...] */
+		     void __user *dst, struct yam_port *yp)
+{
+	struct yamdrv_ioctl_cfg yi; /* { dg-message "region created on stack here" "memspace event" } */
+	/* { dg-message "capacity: 48 bytes" "capacity event" { target *-*-* } .-1 } */
+
+	/* [...snip...] */
+
+	/* case SIOCYAMGCFG: */
+		yi.cfg.mask = 0xffffffff;
+		yi.cfg.iobase = yp->iobase;
+		yi.cfg.irq = yp->irq;
+		yi.cfg.bitrate = yp->bitrate;
+		yi.cfg.baudrate = yp->baudrate;
+		yi.cfg.mode = yp->dupmode;
+		yi.cfg.txdelay = yp->txd;
+		yi.cfg.holddly = yp->holdd;
+		yi.cfg.txtail = yp->txtail;
+		yi.cfg.persist = yp->pers;
+		yi.cfg.slottime = yp->slot;
+		if (copy_to_user(dst, &yi, sizeof(struct yamdrv_ioctl_cfg))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+			/* { dg-message "4 bytes are uninitialized" "how much note" { target *-*-* } .-1 } */
+			 return -EFAULT;
+	/* [...snip...] */
+
+	return 0;
+}
+
+/* Fixed version, with a memset.  */
+
+static int yam_ioctl_fixed(/* [...snip...] */
+			   void __user *dst, struct yam_port *yp)
+{
+	struct yamdrv_ioctl_cfg yi;
+
+	/* [...snip...] */
+
+	/* case SIOCYAMGCFG: */
+		memset(&yi, 0, sizeof(yi));
+		yi.cfg.mask = 0xffffffff;
+		yi.cfg.iobase = yp->iobase;
+		yi.cfg.irq = yp->irq;
+		yi.cfg.bitrate = yp->bitrate;
+		yi.cfg.baudrate = yp->baudrate;
+		yi.cfg.mode = yp->dupmode;
+		yi.cfg.txdelay = yp->txd;
+		yi.cfg.holddly = yp->holdd;
+		yi.cfg.txtail = yp->txtail;
+		yi.cfg.persist = yp->pers;
+		yi.cfg.slottime = yp->slot;
+		if (copy_to_user(dst, &yi, sizeof(struct yamdrv_ioctl_cfg))) /* { dg-bogus "" } */
+			 return -EFAULT;
+	/* [...snip...] */
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c
new file mode 100644
index 00000000000..8a1c816cc1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18549-1.c
@@ -0,0 +1,105 @@
+/* "An issue was discovered in drivers/scsi/aacraid/commctrl.c in the
+   Linux kernel before 4.13. There is potential exposure of kernel stack
+   memory because aac_send_raw_srb does not initialize the reply structure."
+
+   Fixed e.g. by 342ffc26693b528648bdc9377e51e4f2450b4860 on linux-4.13.y 
+   in linux-stable.
+
+   This is a very simplified version of that code (before and after the fix). */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+typedef unsigned int __u32;
+typedef unsigned int u32;
+typedef unsigned char u8;
+
+#include "test-uaccess.h"
+
+/* Adapted from include/uapi/linux/types.h  */
+
+#define __bitwise
+typedef __u32 __bitwise __le32;
+
+/* Adapted from drivers/scsi/aacraid/aacraid.h  */
+
+#define		AAC_SENSE_BUFFERSIZE	 30
+
+struct aac_srb_reply
+{
+	__le32		status;
+	__le32		srb_status;
+	__le32		scsi_status;
+	__le32		data_xfer_length;
+	__le32		sense_data_size;
+	u8		sense_data[AAC_SENSE_BUFFERSIZE]; /* { dg-message "padding after field 'sense_data' is uninitialized \\(2 bytes\\)" } */
+};
+
+#define		ST_OK		0
+#define SRB_STATUS_SUCCESS                  0x01
+
+/* Adapted from drivers/scsi/aacraid/commctrl.c  */
+
+static int aac_send_raw_srb(/* [...snip...] */
+			    void __user *user_reply)
+{
+	u32 byte_count = 0;
+
+	/* [...snip...] */
+
+	struct aac_srb_reply reply; /* { dg-message "region created on stack here" "memspace message" } */
+	/* { dg-message "capacity: 52 bytes" "capacity message" { target *-*-* } .-1 } */
+
+	reply.status = ST_OK;
+		
+	/* [...snip...] */
+
+	reply.srb_status = SRB_STATUS_SUCCESS;
+	reply.scsi_status = 0;
+	reply.data_xfer_length = byte_count;
+	reply.sense_data_size = 0;
+	memset(reply.sense_data, 0, AAC_SENSE_BUFFERSIZE);
+
+	/* [...snip...] */
+
+	if (copy_to_user(user_reply, &reply, /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" } */
+					     /* { dg-message "2 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+			 sizeof(struct aac_srb_reply))) {
+		/* [...snip...] */
+	}
+	/* [...snip...] */
+}
+
+static int aac_send_raw_srb_fixed(/* [...snip...] */
+				  void __user *user_reply)
+{
+	u32 byte_count = 0;
+
+	/* [...snip...] */
+
+	struct aac_srb_reply reply;
+
+	/* This is the fix.  */
+	memset(&reply, 0, sizeof(reply));
+
+	reply.status = ST_OK;
+		
+	/* [...snip...] */
+
+	reply.srb_status = SRB_STATUS_SUCCESS;
+	reply.scsi_status = 0;
+	reply.data_xfer_length = byte_count;
+	reply.sense_data_size = 0;
+	memset(reply.sense_data, 0, AAC_SENSE_BUFFERSIZE);
+
+	/* [...snip...] */
+
+	if (copy_to_user(user_reply, &reply, /* { dg-bogus "" } */
+			 sizeof(struct aac_srb_reply))) {
+		/* [...snip...] */
+	}
+	/* [...snip...] */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18550-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18550-1.c
new file mode 100644
index 00000000000..4272da96bab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-CVE-2017-18550-1.c
@@ -0,0 +1,175 @@
+/* "An issue was discovered in drivers/scsi/aacraid/commctrl.c in the 
+   Linux kernel before 4.13. There is potential exposure of kernel stack
+   memory because aac_get_hba_info does not initialize the hbainfo structure."
+
+   Fixed e.g. by 342ffc26693b528648bdc9377e51e4f2450b4860 on linux-4.13.y
+   in linux-stable.
+
+   This is a simplified version of that code (before and after the fix). */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+typedef unsigned int __u32;
+typedef unsigned int u32;
+typedef unsigned char u8;
+
+#include "test-uaccess.h"
+
+/* Adapted from include/uapi/linux/types.h  */
+
+#define __bitwise
+typedef __u32 __bitwise __le32;
+
+/* Adapted from drivers/scsi/aacraid/aacraid.h  */
+
+struct aac_hba_info {
+
+	u8	driver_name[50]; /* { dg-message "field 'driver_name' is uninitialized \\(50 bytes\\)" } */
+	u8	adapter_number;
+	u8	system_io_bus_number;
+	u8	device_number; /* { dg-message "padding after field 'device_number' is uninitialized \\(3 bytes\\)" } */
+	u32	function_number;
+	u32	vendor_id;
+	u32	device_id;
+	u32	sub_vendor_id;
+	u32	sub_system_id;
+	u32	mapped_base_address_size; /* { dg-message "field 'mapped_base_address_size' is uninitialized \\(4 bytes\\)"  } */
+	u32	base_physical_address_high_part;
+	u32	base_physical_address_low_part;
+
+	u32	max_command_size;
+	u32	max_fib_size;
+	u32	max_scatter_gather_from_os;
+	u32	max_scatter_gather_to_fw;
+	u32	max_outstanding_fibs;
+
+	u32	queue_start_threshold;
+	u32	queue_dump_threshold;
+	u32	max_io_size_queued;
+	u32	outstanding_io;
+
+	u32	firmware_build_number;
+	u32	bios_build_number;
+	u32	driver_build_number;
+	u32	serial_number_high_part;
+	u32	serial_number_low_part;
+	u32	supported_options;
+	u32	feature_bits;
+	u32	currentnumber_ports;
+
+	u8	new_comm_interface:1; /* { dg-message "field 'new_comm_interface' is uninitialized \\(1 bit\\)" } */
+	u8	new_commands_supported:1;
+	u8	disable_passthrough:1;
+	u8	expose_non_dasd:1;
+	u8	queue_allowed:1;
+	u8	bled_check_enabled:1;
+	u8	reserved1:1;
+	u8	reserted2:1;
+
+	u32	reserved3[10]; /* { dg-message "field 'reserved3' is uninitialized \\(40 bytes\\)" } */
+
+};
+
+struct aac_dev
+{
+	/* [...snip...] */
+	int			id;
+	/* [...snip...] */
+	struct pci_dev		*pdev;		/* Our PCI interface */
+	/* [...snip...] */
+};
+
+/* Adapted from include/linux/pci.h  */
+
+struct pci_dev {
+	/* [...snip...] */
+	struct pci_bus	*bus;		/* bus this device is on */
+	/* [...snip...] */
+	unsigned int	devfn;		/* encoded device & function index */
+	unsigned short	vendor;
+	unsigned short	device;
+	unsigned short	subsystem_vendor;
+	unsigned short	subsystem_device;
+	/* [...snip...] */
+};
+
+struct pci_bus {
+	/* [...snip...] */
+	unsigned char	number;		/* bus number */
+	/* [...snip...] */
+};
+
+/* Adapted from drivers/scsi/aacraid/commctrl.c  */
+
+static int aac_get_hba_info(struct aac_dev *dev, void __user *arg)
+{
+	struct aac_hba_info hbainfo; /* { dg-message "region created on stack here" "memspace message" } */
+	/* { dg-message "capacity: 200 bytes" "capacity message" { target *-*-* } .-1 } */
+
+	hbainfo.adapter_number		= (u8) dev->id;
+	hbainfo.system_io_bus_number	= dev->pdev->bus->number;
+	hbainfo.device_number		= (dev->pdev->devfn >> 3);
+	hbainfo.function_number		= (dev->pdev->devfn & 0x0007);
+
+	hbainfo.vendor_id		= dev->pdev->vendor;
+	hbainfo.device_id		= dev->pdev->device;
+	hbainfo.sub_vendor_id		= dev->pdev->subsystem_vendor;
+	hbainfo.sub_system_id		= dev->pdev->subsystem_device;
+
+	if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+		/* { dg-message "177 bytes are uninitialized" "how much" { target *-*-* } .-1 } */
+		/* [...snip...] */
+	}
+
+	return 0;
+}
+
+static int aac_get_hba_info_fixed(struct aac_dev *dev, void __user *arg)
+{
+	struct aac_hba_info hbainfo;
+
+	memset(&hbainfo, 0, sizeof(hbainfo));
+	hbainfo.adapter_number		= (u8) dev->id;
+	hbainfo.system_io_bus_number	= dev->pdev->bus->number;
+	hbainfo.device_number		= (dev->pdev->devfn >> 3);
+	hbainfo.function_number		= (dev->pdev->devfn & 0x0007);
+
+	hbainfo.vendor_id		= dev->pdev->vendor;
+	hbainfo.device_id		= dev->pdev->device;
+	hbainfo.sub_vendor_id		= dev->pdev->subsystem_vendor;
+	hbainfo.sub_system_id		= dev->pdev->subsystem_device;
+
+	if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-bogus "" } */
+		/* [...snip...] */
+	}
+
+	return 0;
+}
+
+/* An alternate fix using "= {0}" rather than memset.  */
+
+static int aac_get_hba_info_fixed_alt(struct aac_dev *dev, void __user *arg)
+{
+	struct aac_hba_info hbainfo = {0};
+
+	memset(&hbainfo, 0, sizeof(hbainfo));
+	hbainfo.adapter_number		= (u8) dev->id;
+	hbainfo.system_io_bus_number	= dev->pdev->bus->number;
+	hbainfo.device_number		= (dev->pdev->devfn >> 3);
+	hbainfo.function_number		= (dev->pdev->devfn & 0x0007);
+
+	hbainfo.vendor_id		= dev->pdev->vendor;
+	hbainfo.device_id		= dev->pdev->device;
+	hbainfo.sub_vendor_id		= dev->pdev->subsystem_vendor;
+	hbainfo.sub_system_id		= dev->pdev->subsystem_device;
+
+	if (copy_to_user(arg, &hbainfo, sizeof(struct aac_hba_info))) { /* { dg-bogus "" } */
+		/* [...snip...] */
+	}
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-antipatterns-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-antipatterns-1.c
new file mode 100644
index 00000000000..50084536438
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-antipatterns-1.c
@@ -0,0 +1,166 @@
+/* Adapted and simplified decls from linux kernel headers.  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+typedef unsigned char u8;
+typedef unsigned __INT16_TYPE__ u16;
+typedef unsigned __INT32_TYPE__ u32;
+typedef __SIZE_TYPE__ size_t;
+
+#define   EFAULT          14
+
+#include "test-uaccess.h"
+
+typedef unsigned int gfp_t;
+#define GFP_KERNEL 0
+
+void kfree(const void *);
+void *kmalloc(size_t size, gfp_t flags)
+  __attribute__((malloc (kfree)));
+
+/* Adapted from antipatterns.ko:infoleak.c (GPL-v2.0).   */
+
+struct infoleak_buf
+{
+  char buf[256];
+};
+
+int infoleak_stack_no_init(void __user *dst)
+{
+  struct infoleak_buf st; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 256 bytes" "capacity" { target *-*-* } .-1 } */
+  
+  /* No initialization of "st" at all.  */
+  if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+    /* { dg-message "256 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+    return -EFAULT;
+  return 0;
+}
+
+int infoleak_heap_no_init(void __user *dst)
+{
+  struct infoleak_buf *heapbuf = kmalloc(sizeof(*heapbuf), GFP_KERNEL);
+  /* No initialization of "heapbuf" at all.  */
+
+  /* TODO: we also don't check that heapbuf could be NULL when copying
+     from it.  */
+  if (copy_to_user(dst, heapbuf, sizeof(*heapbuf))) /* { dg-warning "exposure" "warning" { xfail *-*-* } } */
+    /* TODO(xfail).  */
+    return -EFAULT; /* { dg-warning "leak of 'heapbuf'" } */
+
+  kfree(heapbuf);
+  return 0;
+}
+
+struct infoleak_2
+{
+  u32 a;
+  u32 b; /* { dg-message "field 'b' is uninitialized \\(4 bytes\\)" } */
+};
+
+int infoleak_stack_missing_a_field(void __user *dst, u32 v)
+{
+  struct infoleak_2 st; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */
+  
+  st.a = v;
+  /* No initialization of "st.b".  */
+  if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+    /* { dg-message "4 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+    return -EFAULT;
+  return 0;
+}
+
+int infoleak_heap_missing_a_field(void __user *dst, u32 v)
+{
+  struct infoleak_2 *heapbuf = kmalloc(sizeof(*heapbuf), GFP_KERNEL);
+  heapbuf->a = v; /* { dg-warning "dereference of possibly-NULL 'heapbuf'" } */
+  /* No initialization of "heapbuf->b".  */
+  if (copy_to_user(dst, heapbuf, sizeof(*heapbuf))) /* { dg-warning "exposure" "warning" { xfail *-*-* } } */
+    /* TODO(xfail).  */
+    {
+      kfree(heapbuf);
+      return -EFAULT;
+    }
+  kfree(heapbuf);
+  return 0;
+}
+
+struct infoleak_3
+{
+  u8 a; /* { dg-message "padding after field 'a' is uninitialized \\(3 bytes\\)" } */
+  /* padding here */
+  u32 b;
+};
+
+int infoleak_stack_padding(void __user *dst, u8 p, u32 q)
+{
+  struct infoleak_3 st; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */
+
+  st.a = p;
+  st.b = q;
+  /* No initialization of padding.  */
+  if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+    /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+    return -EFAULT;
+  return 0;
+}
+
+int infoleak_stack_unchecked_err(void __user *dst, void __user *src)
+{
+  struct infoleak_buf st;  /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 256 bytes" "capacity" { target *-*-* } .-1 } */
+
+  /*
+   * If the copy_from_user call fails, then st is still uninitialized,
+   * and if the copy_to_user call succeds, we have an infoleak.
+   */
+  int err = copy_from_user (&st, src, sizeof(st)); /* { dg-message "when 'copy_from_user' fails" } */
+  err |= copy_to_user (dst, &st, sizeof(st)); /* { dg-warning "exposure" "warning" } */
+  /* { dg-message "256 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+  /* Actually, it's *up to* 256 bytes.  */
+
+  if (err)
+    return -EFAULT;
+  return 0;
+}
+
+struct infoleak_4
+{
+  union {
+    u8 f1;
+    u32 f2;
+  } u;
+};
+
+int infoleak_stack_union(void __user *dst, u8 v)
+{
+  struct infoleak_4 st;
+  /*
+   * This write only initializes the u8 within the union "u",
+   * leaving the remaining 3 bytes uninitialized.
+   */
+  st.u.f1 = v;
+  if (copy_to_user(dst, &st, sizeof(st))) /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+    /* { dg-message "3 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+    return -EFAULT;
+  return 0;
+}
+
+struct infoleak_5
+{
+  void *ptr;
+};
+
+int infoleak_stack_kernel_ptr(void __user *dst, void *kp)
+{
+  struct infoleak_5 st;
+  /* This writes a kernel-space pointer into a user space buffer.  */
+  st.ptr = kp;
+  if (copy_to_user(dst, &st, sizeof(st))) // TODO: we don't complain about this yet
+    return -EFAULT;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c
new file mode 100644
index 00000000000..6961b44f76b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-fixit-1.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+#include <string.h>
+
+#include "test-uaccess.h"
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+struct st
+{
+  u8 i;  /* { dg-message "padding after field 'i' is uninitialized \\(3 bytes\\)" } */
+  u32 j; /* { dg-message "field 'j' is uninitialized \\(4 bytes\\)" } */
+};
+
+void test (void __user *dst, u8 a)
+{
+  struct st s; /* { dg-message "region created on stack here" "where" } */
+  /* { dg-message "capacity: 8 bytes" "capacity" { target *-*-* } .-1 } */
+  /* { dg-message "suggest forcing zero-initialization by providing a '.0.' initializer" "fix-it hint" { target *-*-* } .-2 } */  
+  s.i = a;
+  copy_to_user(dst, &s, sizeof (struct st)); /* { dg-warning "potential exposure of sensitive information by copying uninitialized data from stack" "warning" } */
+  /* { dg-message "7 bytes are uninitialized" "note how much" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-net-ethtool-ioctl.c b/gcc/testsuite/gcc.dg/plugin/infoleak-net-ethtool-ioctl.c
new file mode 100644
index 00000000000..dce6e44ae13
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-net-ethtool-ioctl.c
@@ -0,0 +1,82 @@
+/* Reduced from infoleak false positive seen on Linux kernel with
+   net/ethtool/ioctl.c  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+typedef signed char __s8;
+typedef unsigned char __u8;
+typedef unsigned int __u32;
+typedef __s8 s8;
+typedef __u32 u32;
+enum { false = 0, true = 1 };
+typedef unsigned long __kernel_ulong_t;
+typedef __kernel_ulong_t __kernel_size_t;
+typedef _Bool bool;
+typedef __kernel_size_t size_t;
+
+void *memset(void *s, int c, size_t n);
+
+extern bool
+check_copy_size(const void *addr, size_t bytes, bool is_source);
+extern unsigned long
+_copy_from_user(void *, const void *, unsigned long);
+extern unsigned long
+_copy_to_user(void *, const void *, unsigned long);
+
+static inline
+__attribute__((__always_inline__)) unsigned long
+copy_from_user(void *to, const void *from, unsigned long n) {
+  if (__builtin_expect(!!(check_copy_size(to, n, false)), 1))
+    n = _copy_from_user(to, from, n);
+  return n;
+}
+static inline
+__attribute__((__always_inline__)) unsigned long
+copy_to_user(void *to, const void *from, unsigned long n) {
+  if (__builtin_expect(!!(check_copy_size(from, n, true)), 1))
+    n = _copy_to_user(to, from, n);
+  return n;
+}
+enum ethtool_link_mode_bit_indices {
+  __ETHTOOL_LINK_MODE_MASK_NBITS = 92
+};
+struct ethtool_link_settings {
+  __u32 cmd;
+  /* [...snip...] */
+  __s8 link_mode_masks_nwords;
+  /* [...snip...] */
+};
+
+struct ethtool_link_ksettings {
+  struct ethtool_link_settings base;
+  u32 lanes;
+};
+
+int ethtool_get_link_ksettings(void *useraddr) {
+  int err = 0;
+  struct ethtool_link_ksettings link_ksettings;
+
+  if (copy_from_user(&link_ksettings.base, useraddr,
+                     sizeof(link_ksettings.base)))
+    return -14;
+
+  if ((((__ETHTOOL_LINK_MODE_MASK_NBITS) + (32) - 1) / (32)) !=
+      link_ksettings.base.link_mode_masks_nwords) {
+
+    memset(&link_ksettings, 0, sizeof(link_ksettings));
+    link_ksettings.base.cmd = 0x0000004c;
+
+    link_ksettings.base.link_mode_masks_nwords =
+        -((s8)(((__ETHTOOL_LINK_MODE_MASK_NBITS) + (32) - 1) / (32)));
+
+    if (copy_to_user(useraddr, &link_ksettings.base,
+                     sizeof(link_ksettings.base)))
+      return -14;
+
+    return 0;
+  }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/infoleak-vfio_iommu_type1.c b/gcc/testsuite/gcc.dg/plugin/infoleak-vfio_iommu_type1.c
new file mode 100644
index 00000000000..51ad5db2bab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/infoleak-vfio_iommu_type1.c
@@ -0,0 +1,44 @@
+/* Reduced from infoleak false positive in drivers/vfio/vfio_iommu_type1.c  */
+
+/* { dg-do compile } */
+/* { dg-options "-fanalyzer" } */
+/* { dg-require-effective-target analyzer } */
+
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+unsigned long
+copy_from_user(void *to, const void *from, unsigned long n);
+
+unsigned long
+copy_to_user(void *to, const void *from, unsigned long n);
+
+struct vfio_iommu_type1_info {
+  u32 argsz;
+  u32 flags;
+  u64 iova_pgsizes;
+  u32 cap_offset;
+  /* bytes 20-23 are padding.  */
+};
+
+int vfio_iommu_type1_get_info(unsigned long arg)
+{
+  struct vfio_iommu_type1_info info;
+  unsigned long minsz = 16;
+
+  if (copy_from_user(&info, (void *)arg, 16))
+    return -14;
+
+  if (info.argsz < 16)
+    return -22;
+
+  if (info.argsz >= 20) {
+    minsz = 20;
+    info.cap_offset = 0;
+  }
+
+  /* The padding bytes (20-23) are uninitialized, but can't be written
+     back, since minsz is either 16 or 20.  */
+  return copy_to_user((void *)arg, &info, minsz) ? -14 : 0; /* { dg-bogus "exposure" "" { xfail *-*-* } } */
+  // TODO: false +ve due to not handling minsz being either 16 or 20
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index c05366f3f85..5b7efa4afb6 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -125,6 +125,30 @@ set plugin_test_list [list \
 	  gil-1.c } \
     { analyzer_known_fns_plugin.c \
 	  known-fns-1.c } \
+    { analyzer_kernel_plugin.c \
+	  copy_from_user-1.c \
+	  infoleak-1.c \
+	  infoleak-2.c \
+	  infoleak-3.c \
+	  infoleak-CVE-2011-1078-1.c \
+	  infoleak-CVE-2011-1078-2.c \
+	  infoleak-CVE-2017-18549-1.c \
+	  infoleak-CVE-2017-18550-1.c \
+	  infoleak-antipatterns-1.c \
+	  infoleak-fixit-1.c \
+	  infoleak-net-ethtool-ioctl.c \
+	  infoleak-vfio_iommu_type1.c \
+	  taint-CVE-2011-0521-1-fixed.c \
+	  taint-CVE-2011-0521-1.c \
+	  taint-CVE-2011-0521-2-fixed.c \
+	  taint-CVE-2011-0521-2.c \
+	  taint-CVE-2011-0521-3-fixed.c \
+	  taint-CVE-2011-0521-3.c \
+	  taint-CVE-2011-0521-4.c \
+	  taint-CVE-2011-0521-5.c \
+	  taint-CVE-2011-0521-5-fixed.c \
+	  taint-CVE-2011-0521-6.c \
+	  taint-antipatterns-1.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c
new file mode 100644
index 00000000000..0ca8137c3ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1-fixed.c
@@ -0,0 +1,115 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num < 0 || info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+static struct dvb_device dvbdev_ca = {
+	.priv		= NULL,
+	/* [...snip...] */
+	.kernel_ioctl	= dvb_ca_ioctl,
+};
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		     unsigned int cmd, unsigned long arg,
+		     int (*func)(struct file *file,
+		     unsigned int cmd, void *arg))
+{
+	char    sbuf[128];
+	void    *mbuf = NULL;
+	void    *parg = NULL;
+	int     err  = -1;
+
+	/*  Copy arguments into temp kernel buffer  */
+	switch (_IOC_DIR(cmd)) {
+	case _IOC_NONE:
+		/*
+		 * For this command, the pointer is actually an integer
+		 * argument.
+		 */
+		parg = (void *) arg;
+		break;
+	case _IOC_READ: /* some v4l ioctls are marked wrong ... */
+	case _IOC_WRITE:
+	case (_IOC_WRITE | _IOC_READ):
+		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
+			parg = sbuf;
+		} else {
+			/* too big to allocate from stack */
+			mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL);
+			if (NULL == mbuf)
+				return -ENOMEM;
+			parg = mbuf;
+		}
+
+		err = -EFAULT;
+		if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
+			goto out;
+		break;
+	}
+
+	/* call driver */
+	mutex_lock(&dvbdev_mutex);
+	if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	/*  Copy results into user buffer  */
+	switch (_IOC_DIR(cmd))
+	{
+	case _IOC_READ:
+	case (_IOC_WRITE | _IOC_READ):
+		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
+			err = -EFAULT;
+		break;
+	}
+
+out:
+	kfree(mbuf);
+	return err;
+}
+
+long dvb_generic_ioctl(struct file *file,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+
+	if (!dvbdev)
+		return -ENODEV;
+
+	if (!dvbdev->kernel_ioctl)
+		return -EINVAL;
+
+	return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl);
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c
new file mode 100644
index 00000000000..cde12b3b761
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-1.c
@@ -0,0 +1,115 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+static struct dvb_device dvbdev_ca = {
+	.priv		= NULL,
+	/* [...snip...] */
+	.kernel_ioctl	= dvb_ca_ioctl,
+};
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		     unsigned int cmd, unsigned long arg,
+		     int (*func)(struct file *file,
+		     unsigned int cmd, void *arg))
+{
+	char    sbuf[128];
+	void    *mbuf = NULL;
+	void    *parg = NULL;
+	int     err  = -1;
+
+	/*  Copy arguments into temp kernel buffer  */
+	switch (_IOC_DIR(cmd)) {
+	case _IOC_NONE:
+		/*
+		 * For this command, the pointer is actually an integer
+		 * argument.
+		 */
+		parg = (void *) arg;
+		break;
+	case _IOC_READ: /* some v4l ioctls are marked wrong ... */
+	case _IOC_WRITE:
+	case (_IOC_WRITE | _IOC_READ):
+		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
+			parg = sbuf;
+		} else {
+			/* too big to allocate from stack */
+			mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL);
+			if (NULL == mbuf)
+				return -ENOMEM;
+			parg = mbuf;
+		}
+
+		err = -EFAULT;
+		if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
+			goto out;
+		break;
+	}
+
+	/* call driver */
+	mutex_lock(&dvbdev_mutex);
+	if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	/*  Copy results into user buffer  */
+	switch (_IOC_DIR(cmd))
+	{
+	case _IOC_READ:
+	case (_IOC_WRITE | _IOC_READ):
+		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
+			err = -EFAULT;
+		break;
+	}
+
+out:
+	kfree(mbuf);
+	return err;
+}
+
+long dvb_generic_ioctl(struct file *file,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+
+	if (!dvbdev)
+		return -ENODEV;
+
+	if (!dvbdev->kernel_ioctl)
+		return -EINVAL;
+
+	return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl);
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c
new file mode 100644
index 00000000000..8a211cefe4e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2-fixed.c
@@ -0,0 +1,98 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+// TODO: remove need for this option
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num < 0 || info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c
+   Somewhat simplified: rather than pass in a callback that can
+   be dvb_ca_ioctl, call dvb_ca_ioctl directly.  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		 unsigned int cmd, unsigned long arg)
+{
+	char    sbuf[128];
+	void    *mbuf = NULL;
+	void    *parg = NULL;
+	int     err  = -1;
+
+	/*  Copy arguments into temp kernel buffer  */
+	switch (_IOC_DIR(cmd)) {
+	case _IOC_NONE:
+		/*
+		 * For this command, the pointer is actually an integer
+		 * argument.
+		 */
+		parg = (void *) arg;
+		break;
+	case _IOC_READ: /* some v4l ioctls are marked wrong ... */
+	case _IOC_WRITE:
+	case (_IOC_WRITE | _IOC_READ):
+		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
+			parg = sbuf;
+		} else {
+			/* too big to allocate from stack */
+			mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL);
+			if (NULL == mbuf)
+				return -ENOMEM;
+			parg = mbuf;
+		}
+
+		err = -EFAULT;
+		if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
+			goto out;
+		break;
+	}
+
+	/* call driver */
+	mutex_lock(&dvbdev_mutex);
+	if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	/*  Copy results into user buffer  */
+	switch (_IOC_DIR(cmd))
+	{
+	case _IOC_READ:
+	case (_IOC_WRITE | _IOC_READ):
+		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
+			err = -EFAULT;
+		break;
+	}
+
+out:
+	kfree(mbuf);
+	return err;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c
new file mode 100644
index 00000000000..30cab38e002
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-2.c
@@ -0,0 +1,95 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c
+   Somewhat simplified: rather than pass in a callback that can
+   be dvb_ca_ioctl, call dvb_ca_ioctl directly.  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		 unsigned int cmd, unsigned long arg)
+{
+	char    sbuf[128];
+	void    *mbuf = NULL;
+	void    *parg = NULL;
+	int     err  = -1;
+
+	/*  Copy arguments into temp kernel buffer  */
+	switch (_IOC_DIR(cmd)) {
+	case _IOC_NONE:
+		/*
+		 * For this command, the pointer is actually an integer
+		 * argument.
+		 */
+		parg = (void *) arg;
+		break;
+	case _IOC_READ: /* some v4l ioctls are marked wrong ... */
+	case _IOC_WRITE:
+	case (_IOC_WRITE | _IOC_READ):
+		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
+			parg = sbuf;
+		} else {
+			/* too big to allocate from stack */
+			mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL);
+			if (NULL == mbuf)
+				return -ENOMEM;
+			parg = mbuf;
+		}
+
+		err = -EFAULT;
+		if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
+			goto out;
+		break;
+	}
+
+	/* call driver */
+	mutex_lock(&dvbdev_mutex);
+	if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	/*  Copy results into user buffer  */
+	switch (_IOC_DIR(cmd))
+	{
+	case _IOC_READ:
+	case (_IOC_WRITE | _IOC_READ):
+		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
+			err = -EFAULT;
+		break;
+	}
+
+out:
+	kfree(mbuf);
+	return err;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c
new file mode 100644
index 00000000000..b7852b40dcf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3-fixed.c
@@ -0,0 +1,61 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+// TODO: remove need for this option
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num < 0 || info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-bogus "attacker-controlled value" } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c
+   Further simplified from -2; always use an on-stack buffer.  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		 unsigned int cmd, unsigned long arg)
+{
+	char    sbuf[128];
+	void    *parg = sbuf;
+	int     err = -EFAULT;
+	if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf)))
+	  goto out;
+
+	mutex_lock(&dvbdev_mutex);
+	if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	if (copy_to_user((void __user *)arg, parg, sizeof(sbuf)))
+	  err = -EFAULT;
+
+out:
+	return err;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c
new file mode 100644
index 00000000000..6b9e034eea7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-3.c
@@ -0,0 +1,59 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_ca.c  */
+
+int dvb_ca_ioctl(struct file *file, unsigned int cmd, void *parg)
+{
+	struct dvb_device *dvbdev = file->private_data;
+	struct av7110 *av7110 = dvbdev->priv;
+	unsigned long arg = (unsigned long) parg;
+
+	/* case CA_GET_SLOT_INFO:  */
+	{
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */
+		// TODO(xfail)
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+	return 0;
+}
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.c
+   Further simplified from -2; always use an on-stack buffer.  */
+
+static DEFINE_MUTEX(dvbdev_mutex);
+
+int dvb_usercopy(struct file *file,
+		 unsigned int cmd, unsigned long arg)
+{
+	char    sbuf[128];
+	void    *parg = sbuf;
+	int     err = -EFAULT;
+	if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf)))
+	  goto out;
+
+	mutex_lock(&dvbdev_mutex);
+	if ((err = dvb_ca_ioctl(file, cmd, parg)) == -ENOIOCTLCMD)
+		err = -EINVAL;
+	mutex_unlock(&dvbdev_mutex);
+
+	if (err < 0)
+		goto out;
+
+	if (copy_to_user((void __user *)arg, parg, sizeof(sbuf)))
+	  err = -EFAULT;
+
+out:
+	return err;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c
new file mode 100644
index 00000000000..f314c64ce70
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-4.c
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+// TODO: remove need for --param=analyzer-max-svalue-depth=25 here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and
+   dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c
+
+   Further simplified from -3; merge into a single function; drop the mutex,
+   remove control flow.  */
+
+int test_1(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	char    sbuf[128];
+	void    *parg = sbuf;
+
+	if (copy_from_user(parg, (void __user *)arg, sizeof(sbuf)))
+	  return -1;
+
+	{
+		struct dvb_device *dvbdev = file->private_data;
+		struct av7110 *av7110 = dvbdev->priv;
+		unsigned long arg = (unsigned long) parg;
+
+		/* case CA_GET_SLOT_INFO:  */
+		ca_slot_info_t *info=(ca_slot_info_t *)parg;
+
+		if (info->num > 1)
+			return -EINVAL;
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "attacker-controlled value" "" { xfail *-*-* } } */
+		// TODO(xfail)
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+
+	copy_to_user((void __user *)arg, parg, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c
new file mode 100644
index 00000000000..8cb067c6542
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5-fixed.c
@@ -0,0 +1,46 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+// TODO: remove need for --param=analyzer-max-svalue-depth=25 here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and
+   dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c
+
+   Further simplified from -4; avoid parg and the cast to char[128].  */
+
+int test_1(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	ca_slot_info_t sbuf;
+
+	if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0)
+		return -1;
+
+	{
+		struct dvb_device *dvbdev = file->private_data;
+		struct av7110 *av7110 = dvbdev->priv;
+
+		/* case CA_GET_SLOT_INFO:  */
+		ca_slot_info_t *info= &sbuf;
+
+		__analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */
+
+		if (info->num < 0 || info->num > 1)
+			return -EINVAL;
+
+		__analyzer_dump_state ("taint", info->num); /* { dg-warning "stop" } */
+
+		av7110->ci_slot[info->num].num = info->num;
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); /* { dg-bogus "use of attacker-controlled value in array lookup without bounds checking" "" { xfail *-*-* } } */
+		// FIXME: why the above false +ve?
+	}
+
+	copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c
new file mode 100644
index 00000000000..4ce047902d3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-5.c
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+// TODO: remove need for --param=analyzer-max-svalue-depth=25 here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and
+   dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c
+
+   Further simplified from -4; avoid parg and the cast to char[128].  */
+
+int test_1(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	ca_slot_info_t sbuf;
+
+	if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0)
+		return -1;
+
+	{
+		struct dvb_device *dvbdev = file->private_data;
+		struct av7110 *av7110 = dvbdev->priv;
+
+		/* case CA_GET_SLOT_INFO:  */
+		ca_slot_info_t *info= &sbuf;
+
+		__analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */
+
+		if (info->num > 1)
+			return -EINVAL;
+
+		__analyzer_dump_state ("taint", info->num); /* { dg-warning "has_ub" } */
+
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without checking for negative" } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without checking for negative" } */
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); /* { dg-warning "use of attacker-controlled value in array lookup without bounds checking" } */
+	}
+
+	copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c
new file mode 100644
index 00000000000..c54af790a56
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521-6.c
@@ -0,0 +1,42 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+// TODO: remove need for --param=analyzer-max-svalue-depth=25 here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint --param=analyzer-max-svalue-depth=25" } */
+/* { dg-require-effective-target analyzer } */
+
+/* See notes in this header.  */
+#include "taint-CVE-2011-0521.h"
+
+/* Adapted from dvb_ca_ioctl in drivers/media/dvb/ttpci/av7110_ca.c and
+   dvb_usercopy in drivers/media/dvb/dvb-core/dvbdev.c
+
+   Further simplified from -5; remove all control flow.  */
+
+int test_1(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	ca_slot_info_t sbuf;
+
+	if (copy_from_user(&sbuf, (void __user *)arg, sizeof(sbuf)) != 0)
+		return -1;
+
+	{
+		struct dvb_device *dvbdev = file->private_data;
+		struct av7110 *av7110 = dvbdev->priv;
+
+		/* case CA_GET_SLOT_INFO:  */
+		ca_slot_info_t *info= &sbuf;
+
+		__analyzer_dump_state ("taint", info->num); /* { dg-warning "tainted" } */
+
+		//__analyzer_break ();
+
+		av7110->ci_slot[info->num].num = info->num; /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without bounds checking" } */
+		av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ?  /* { dg-warning "use of attacker-controlled value '\\*info\\.num' in array lookup without bounds checking" } */
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));  /* { dg-warning "use of attacker-controlled value in array lookup without bounds checking" } */
+	}
+
+	copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h
new file mode 100644
index 00000000000..29f66b6e76e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-CVE-2011-0521.h
@@ -0,0 +1,136 @@
+/* Shared header for the various taint-CVE-2011-0521-*.c tests.
+   These are a series of successively simpler reductions of the reproducer.
+   Ideally the analyzer would detect the issue in all of the testcases,
+   but currently requires some simplification of the code to do so.
+
+   "The dvb_ca_ioctl function in drivers/media/dvb/ttpci/av7110_ca.c in the
+   Linux kernel before 2.6.38-rc2 does not check the sign of a certain integer
+   field, which allows local users to cause a denial of service (memory
+   corruption) or possibly have unspecified other impact via a negative value."
+
+   Adapted from Linux 2.6.38, which is under the GPLv2.
+
+   Fixed in e.g. cb26a24ee9706473f31d34cc259f4dcf45cd0644 on linux-2.6.38.y  */
+
+#include <string.h>
+#include "test-uaccess.h"
+#include "../analyzer/analyzer-decls.h"
+
+typedef unsigned int u32;
+
+/* Adapted from include/linux/compiler.h  */
+
+#define __force
+
+/* Adapted from include/asm-generic/errno-base.h  */
+
+#define	ENOMEM		12	/* Out of memory */
+#define	EFAULT		14	/* Bad address */
+#define	ENODEV		19	/* No such device */
+#define	EINVAL		22	/* Invalid argument */
+
+/* Adapted from include/linux/errno.h  */
+
+#define ENOIOCTLCMD	515	/* No ioctl command */
+
+/* Adapted from include/linux/fs.h  */
+
+struct file {
+	/* [...snip...] */
+	void			*private_data;
+	/* [...snip...] */
+};
+
+/* Adapted from drivers/media/dvb/dvb-core/dvbdev.h  */
+
+struct dvb_device {
+	/* [...snip...] */
+	int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg);
+
+	void *priv;
+};
+
+
+/* Adapted from include/linux/dvb/ca.h  */
+
+typedef struct ca_slot_info {
+	int num;               /* slot number */
+
+	int type;              /* CA interface this slot supports */
+#define CA_CI            1     /* CI high level interface */
+#define CA_CI_LINK       2     /* CI link layer level interface */
+	/* [...snip...] */
+} ca_slot_info_t;
+
+
+/* Adapted from drivers/media/dvb/ttpci/av7110.h  */
+
+struct av7110 {
+	/* [...snip...] */
+	ca_slot_info_t		ci_slot[2];
+	/* [...snip...] */
+	u32		    arm_app;
+	/* [...snip...] */
+};
+
+/* Adapted from drivers/media/dvb/ttpci/av7110_hw.h  */
+
+#define FW_CI_LL_SUPPORT(arm_app) ((arm_app) & 0x80000000)
+
+/* Adapted from include/asm-generic/ioctl.h  */
+
+#define _IOC_NRBITS	8
+#define _IOC_TYPEBITS	8
+
+#define _IOC_SIZEBITS	14
+#define _IOC_DIRBITS	2
+
+#define _IOC_SIZEMASK	((1 << _IOC_SIZEBITS)-1)
+#define _IOC_DIRMASK	((1 << _IOC_DIRBITS)-1)
+#define _IOC_NRSHIFT	0
+#define _IOC_TYPESHIFT	(_IOC_NRSHIFT+_IOC_NRBITS)
+#define _IOC_SIZESHIFT	(_IOC_TYPESHIFT+_IOC_TYPEBITS)
+#define _IOC_DIRSHIFT	(_IOC_SIZESHIFT+_IOC_SIZEBITS)
+
+#define _IOC_NONE	0U
+#define _IOC_WRITE	1U
+#define _IOC_READ	2U
+
+#define _IOC_DIR(nr)		(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
+#define _IOC_SIZE(nr)		(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
+
+/* Adapted from include/linux/mutex.h  */
+
+struct mutex {
+	/* [...snip...] */
+};
+
+#define __MUTEX_INITIALIZER(lockname) \
+		{ /* [...snip...] */ }
+
+#define DEFINE_MUTEX(mutexname) \
+	struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
+
+extern void mutex_lock(struct mutex *lock);
+extern void mutex_unlock(struct mutex *lock);
+
+/* Adapted from include/linux/types.h  */
+
+#define __bitwise__
+typedef unsigned __bitwise__ gfp_t;
+
+/* Adapted from include/linux/gfp.h  */
+
+#define ___GFP_WAIT		0x10u
+#define ___GFP_IO		0x40u
+#define ___GFP_FS		0x80u
+#define __GFP_WAIT	((__force gfp_t)___GFP_WAIT)
+#define __GFP_IO	((__force gfp_t)___GFP_IO)
+#define __GFP_FS	((__force gfp_t)___GFP_FS)
+#define GFP_KERNEL  (__GFP_WAIT | __GFP_IO | __GFP_FS)
+
+/* Adapted from include/linux/slab.h  */
+
+void kfree(const void *);
+void *kmalloc(size_t size, gfp_t flags)
+  __attribute__((malloc (kfree)));
diff --git a/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c b/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c
new file mode 100644
index 00000000000..6bb6f1be25c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/taint-antipatterns-1.c
@@ -0,0 +1,139 @@
+/* { dg-do compile } */
+// TODO: remove need for -fanalyzer-checker=taint here:
+/* { dg-options "-fanalyzer -fanalyzer-checker=taint" } */
+/* { dg-require-effective-target analyzer } */
+
+#include "test-uaccess.h"
+
+/* Adapted and simplified decls from linux kernel headers.  */
+
+typedef unsigned char u8;
+typedef unsigned __INT16_TYPE__ u16;
+typedef unsigned __INT32_TYPE__ u32;
+typedef signed __INT32_TYPE__ s32;
+typedef __SIZE_TYPE__ size_t;
+
+#define   EFAULT          14
+
+typedef unsigned int gfp_t;
+#define GFP_KERNEL 0
+
+void kfree(const void *);
+void *kmalloc(size_t size, gfp_t flags)
+  __attribute__((malloc (kfree)));
+
+/* Adapted from antipatterns.ko:taint.c (GPL-v2.0).   */
+
+struct cmd_1
+{
+  u32 idx;
+  u32 val;
+};
+
+static u32 arr[16];
+
+int taint_array_access(void __user *src)
+{
+  struct cmd_1 cmd;
+  if (copy_from_user(&cmd, src, sizeof(cmd)))
+    return -EFAULT;
+  /*
+   * cmd.idx is an unsanitized value from user-space, hence
+   * this is an arbitrary kernel memory access.
+   */
+  arr[cmd.idx] = cmd.val; /* { dg-warning "use of attacker-controlled value 'cmd.idx' in array lookup without upper-bounds checking" } */
+  return 0;
+}
+
+struct cmd_2
+{
+  s32 idx;
+  u32 val;
+};
+
+int taint_signed_array_access(void __user *src)
+{
+  struct cmd_2 cmd;
+  if (copy_from_user(&cmd, src, sizeof(cmd)))
+    return -EFAULT;
+  if (cmd.idx >= 16)
+    return -EFAULT;
+
+  /*
+   * cmd.idx hasn't been checked for being negative, hence
+   * this is an arbitrary kernel memory access.
+   */
+  arr[cmd.idx] = cmd.val; /* { dg-warning "use of attacker-controlled value 'cmd.idx' in array lookup without checking for negative" } */
+  return 0;
+}
+
+struct cmd_s32_binop
+{
+  s32 a;
+  s32 b;
+  s32 result;
+};
+
+int taint_divide_by_zero_direct(void __user *uptr)
+{
+  struct cmd_s32_binop cmd;
+  if (copy_from_user(&cmd, uptr, sizeof(cmd)))
+    return -EFAULT;
+
+  /* cmd.b is attacker-controlled and could be zero */
+  cmd.result = cmd.a / cmd.b; /* { dg-warning "use of attacker-controlled value 'cmd.b' as divisor without checking for zero" } */
+
+  if (copy_to_user (uptr, &cmd, sizeof(cmd)))
+    return -EFAULT;
+  return 0;
+}
+
+int taint_divide_by_zero_compound(void __user *uptr)
+{
+  struct cmd_s32_binop cmd;
+  if (copy_from_user(&cmd, uptr, sizeof(cmd)))
+    return -EFAULT;
+
+  /*
+   * cmd.b is attacker-controlled and could be -1, hence
+   * the divisor could be zero
+   */
+  cmd.result = cmd.a / (cmd.b + 1); /* { dg-warning "use of attacker-controlled value 'cmd.b \\+ 1' as divisor without checking for zero" } */
+
+  if (copy_to_user (uptr, &cmd, sizeof(cmd)))
+    return -EFAULT;
+  return 0;
+}
+
+int taint_mod_by_zero_direct(void __user *uptr)
+{
+  struct cmd_s32_binop cmd;
+  if (copy_from_user(&cmd, uptr, sizeof(cmd)))
+    return -EFAULT;
+
+  /* cmd.b is attacker-controlled and could be zero */
+  cmd.result = cmd.a % cmd.b; /* { dg-warning "use of attacker-controlled value 'cmd.b' as divisor without checking for zero" } */
+
+  if (copy_to_user (uptr, &cmd, sizeof(cmd)))
+    return -EFAULT;
+  return 0;
+}
+
+int taint_mod_by_zero_compound(void __user *uptr)
+{
+  struct cmd_s32_binop cmd;
+  if (copy_from_user(&cmd, uptr, sizeof(cmd)))
+    return -EFAULT;
+
+  /*
+   * cmd.b is attacker-controlled and could be -1, hence
+   * the divisor could be zero
+   */
+  cmd.result = cmd.a % (cmd.b + 1); /* { dg-warning "use of attacker-controlled value 'cmd.b \\+ 1' as divisor without checking for zero" } */
+
+  if (copy_to_user (uptr, &cmd, sizeof(cmd)))
+    return -EFAULT;
+  return 0;
+}
+
+/* TODO: etc.  */
diff --git a/gcc/testsuite/gcc.dg/plugin/test-uaccess.h b/gcc/testsuite/gcc.dg/plugin/test-uaccess.h
new file mode 100644
index 00000000000..42eac985258
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/test-uaccess.h
@@ -0,0 +1,10 @@
+/* Shared header for testcases for copy_from_user/copy_to_user.  */
+
+/* Adapted from include/linux/compiler.h  */
+
+#define __user
+
+/* Adapted from include/asm-generic/uaccess.h  */
+
+extern long copy_from_user(void *to, const void __user *from, long n);
+extern long copy_to_user(void __user *to, const void *from, long n);

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2022-09-09 21:16 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-09 21:16 [gcc r13-2573] analyzer: implement trust boundaries via a plugin for Linux kernel David Malcolm

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).