DragonFlyBSD Kernel Audit
DF-1919 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/raid/mrsas/mrsas_ioctl.c b/sys/dev/raid/mrsas/mrsas_ioctl.c
--- a/sys/dev/raid/mrsas/mrsas_ioctl.c
+++ b/sys/dev/raid/mrsas/mrsas_ioctl.c
@@ -44,6 +44,18 @@
 
 #include <dev/raid/mrsas/mrsas.h>
 #include <dev/raid/mrsas/mrsas_ioctl.h>
+/*
+ * DF-1919: cap the per-SGE DMA data transfer.  The previous code stored
+ * user_ioc->sgl[i].iov_len (size_t on amd64) into an `int ioctl_data_size`
+ * (mrsas_ioctl.c:161), truncating the size used for the bus_dma_tag /
+ * bus_dmamem_alloc, while copyin() (mrsas_ioctl.c:258) was given the
+ * ORIGINAL size_t length -- so an iov_len like 0x100000008 allocated an
+ * 8-byte DMA buffer and then copyin'd 4 GiB+8 of attacker bytes into it.
+ * The cap below is enforced against the full 64-bit iov_len, and
+ * ioctl_data_size is widened to bus_size_t so the value used for the
+ * allocation and the value used for copyin can never disagree.
+ */
+#define MRSAS_IOCTL_MAX_DATA_SIZE	(1024 * 1024)	/* 1 MiB per SGE */
 
 /* 
  * Function prototypes 
@@ -158,7 +170,8 @@
     bus_dmamap_t ioctl_sense_dmamap = 0;
     void *ioctl_sense_mem = NULL;  
     bus_addr_t ioctl_sense_phys_addr = 0; 
-    int i, adapter, ioctl_data_size, ioctl_sense_size, ret=0;
+    int i, adapter, ioctl_sense_size, ret=0;
+    bus_size_t ioctl_data_size;		/* DF-1919: was `int` -- size_t truncation */
     struct mrsas_sge32 *kern_sge32;
     unsigned long *sense_ptr;
 
@@ -225,6 +238,20 @@
     for (i=0; i < user_ioc->sge_count; i++) {
         if (!user_ioc->sgl[i].iov_len)
             continue;
+        /*
+         * DF-1919: validate the full 64-bit iov_len BEFORE storing it into
+         * ioctl_data_size.  Reject anything beyond the per-SGE cap; this
+         * also blocks the size_t->int truncation that previously split
+         * the DMA allocation size from the copyin length.
+         */
+        if (user_ioc->sgl[i].iov_len == 0 ||
+            user_ioc->sgl[i].iov_len > MRSAS_IOCTL_MAX_DATA_SIZE) {
+            device_printf(sc->mrsas_dev,
+                "In %s() SGE[%d] iov_len %ju out of range\n",
+                __func__, i, (uintmax_t)user_ioc->sgl[i].iov_len);
+            ret = EINVAL;
+            goto out;
+        }
         ioctl_data_size = user_ioc->sgl[i].iov_len;
         if (bus_dma_tag_create( sc->mrsas_parent_tag,   // parent
                                 1, 0,                   // algnmnt, boundary
@@ -252,11 +279,11 @@
 
         /* Save the physical address and length */
         kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i];
-        kern_sge32[i].length = user_ioc->sgl[i].iov_len;
+        kern_sge32[i].length = (u_int32_t)ioctl_data_size;	/* DF-1919: validated, no truncation */
 
         /* Copy in data from user space */
-        ret = copyin(user_ioc->sgl[i].iov_base, ioctl_data_mem[i], 
-                        user_ioc->sgl[i].iov_len);
+        ret = copyin(user_ioc->sgl[i].iov_base, ioctl_data_mem[i],
+                        ioctl_data_size);		/* DF-1919: same size we allocated */
         if (ret) {
             device_printf(sc->mrsas_dev, "IOCTL copyin failed!\n");
             goto out;