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 <dmalc...@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

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;
+}
-- 
2.26.3

Reply via email to