[5/6] analyzer: use region::untrusted_p in taint detection

Message ID 20211113203732.2098220-8-dmalcolm@redhat.com
State New
Delegated to: David Malcolm
Headers
Series RFC: adding support to GCC for detecting trust boundaries |

Commit Message

David Malcolm Nov. 13, 2021, 8:37 p.m. UTC
  This patch wires up the "untrusted" region logic to the analyzer's taint
detection, so that any data copied via a __user pointer (e.g. via a
suitably annotated "copy_from_user" decl) is treated as tainted.

It 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 tests (see notes
in the individual cases).

gcc/analyzer/ChangeLog:
	* sm-taint.cc (taint_state_machine::get_default_state): New, using
	region::untrusted_p.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/taint-CVE-2011-0521-1-fixed.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-1.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-2-fixed.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-2.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-3-fixed.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-3.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-4.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-5.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521-6.c: New test.
	* gcc.dg/analyzer/taint-CVE-2011-0521.h: New test.
	* gcc.dg/analyzer/taint-antipatterns-1.c: New test.
	* gcc.dg/analyzer/taint-read-through-untrusted-ptr-1.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/sm-taint.cc                      |  13 ++
 .../analyzer/taint-CVE-2011-0521-1-fixed.c    | 113 +++++++++++++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-1.c   | 113 +++++++++++++++
 .../analyzer/taint-CVE-2011-0521-2-fixed.c    |  93 ++++++++++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-2.c   |  93 ++++++++++++
 .../analyzer/taint-CVE-2011-0521-3-fixed.c    |  56 +++++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-3.c   |  57 ++++++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-4.c   |  40 +++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-5.c   |  42 ++++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521-6.c   |  37 +++++
 .../gcc.dg/analyzer/taint-CVE-2011-0521.h     | 136 +++++++++++++++++
 .../gcc.dg/analyzer/taint-antipatterns-1.c    | 137 ++++++++++++++++++
 .../taint-read-through-untrusted-ptr-1.c      |  37 +++++
 13 files changed, 967 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1-fixed.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2-fixed.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3-fixed.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-antipatterns-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-read-through-untrusted-ptr-1.c
  

Patch

diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 0a51a1fe2ea..53ba6f2b30c 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -85,6 +85,19 @@  public:
 				   const extrinsic_state &ext_state)
     const FINAL OVERRIDE;
 
+  state_machine::state_t
+  get_default_state (const svalue *sval) const FINAL OVERRIDE
+  {
+    /* Default to "tainted" when reading through a pointer to an untrusted
+       region.  */
+    if (const initial_svalue *initial_sval = sval->dyn_cast_initial_svalue ())
+      {
+	if (initial_sval->get_region ()->untrusted_p ())
+	  return m_tainted;
+      }
+    return m_start;
+  }
+
   bool on_stmt (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt) const FINAL OVERRIDE;
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1-fixed.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1-fixed.c
new file mode 100644
index 00000000000..a97896f2266
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1-fixed.c
@@ -0,0 +1,113 @@ 
+/* 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;
+}
+
+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/analyzer/taint-CVE-2011-0521-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1.c
new file mode 100644
index 00000000000..1279f40d948
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-1.c
@@ -0,0 +1,113 @@ 
+/* 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 > 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/analyzer/taint-CVE-2011-0521-2-fixed.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2-fixed.c
new file mode 100644
index 00000000000..2b06bde4063
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2-fixed.c
@@ -0,0 +1,93 @@ 
+/* 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/analyzer/taint-CVE-2011-0521-2.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2.c
new file mode 100644
index 00000000000..c1bf748ae15
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-2.c
@@ -0,0 +1,93 @@ 
+/* 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 > 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/analyzer/taint-CVE-2011-0521-3-fixed.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3-fixed.c
new file mode 100644
index 00000000000..0147759f4df
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3-fixed.c
@@ -0,0 +1,56 @@ 
+/* 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/analyzer/taint-CVE-2011-0521-3.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3.c
new file mode 100644
index 00000000000..c53071afbab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-3.c
@@ -0,0 +1,57 @@ 
+/* 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 > 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/analyzer/taint-CVE-2011-0521-4.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-4.c
new file mode 100644
index 00000000000..eab95929cd6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-4.c
@@ -0,0 +1,40 @@ 
+/* 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 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;
+
+	copy_from_user(parg, (void __user *)arg, sizeof(sbuf));
+
+	{
+		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/analyzer/taint-CVE-2011-0521-5.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-5.c
new file mode 100644
index 00000000000..9cf465204cc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-5.c
@@ -0,0 +1,42 @@ 
+/* 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 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) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+
+	copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-6.c b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-6.c
new file mode 100644
index 00000000000..35a16af2316
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521-6.c
@@ -0,0 +1,37 @@ 
+/* 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 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" } */
+
+		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) ?
+							CA_CI_LINK : CA_CI;
+		memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t));
+	}
+
+	copy_to_user((void __user *)arg, &sbuf, sizeof(sbuf));
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521.h b/gcc/testsuite/gcc.dg/analyzer/taint-CVE-2011-0521.h
new file mode 100644
index 00000000000..0d79f9f9e08
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/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-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/analyzer/taint-antipatterns-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-antipatterns-1.c
new file mode 100644
index 00000000000..5e81410a847
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-antipatterns-1.c
@@ -0,0 +1,137 @@ 
+// TODO: remove need for this:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#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/analyzer/taint-read-through-untrusted-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-read-through-untrusted-ptr-1.c
new file mode 100644
index 00000000000..cd2911683e6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-read-through-untrusted-ptr-1.c
@@ -0,0 +1,37 @@ 
+// TODO: remove need for this:
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "test-uaccess.h"
+
+typedef unsigned __INT32_TYPE__ u32;
+
+struct cmd_1
+{
+  u32 idx;
+  u32 val;
+};
+
+u32 arr[16];
+
+int taint_array_access_1 (struct cmd_1 __user *src)
+{
+  /*
+   * src->idx is an unsanitized value from user-space, hence
+   * this is an arbitrary kernel memory access.
+   */
+  arr[src->idx] = src->val; /* { dg-warning "use of attacker-controlled value '\\*src.idx' in array lookup without upper-bounds checking" } */
+  return 0;
+}
+
+int taint_array_access_2 (struct cmd_1 __user *src)
+{
+  struct cmd_1 cmd;
+  cmd = *src;
+
+  /*
+   * 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 '\\*src.idx' in array lookup without upper-bounds checking" } */
+  return 0;
+}