⬢ DragonFlyBSD Kernel Audit
DF-3178 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/hpfs/hpfs_vfsops.c
+++ b/sys/vfs/hpfs/hpfs_vfsops.c
@@ -31,6 +31,7 @@
 #include <sys/systm.h>
 #include <sys/nlookup.h>
 #include <sys/conf.h>
+#include <sys/device.h>
 #include <sys/proc.h>
 #include <sys/kernel.h>
 #include <sys/vnode.h>
@@ -290,6 +291,42 @@
 		goto failed;
 	}
 
+	/*
+	 * DF-3178: validate the volume census BEFORE any of it is used as
+	 * a size or index.  Previously only the magic numbers were checked:
+	 * an on-disk su_btotal then flowed unvalidated into hpfs_bminit(),
+	 * where dbnum = (su_btotal + 0x3FFF) / 0x4000 drives both the
+	 * bitmap/bmind kmalloc sizes and a single bmind-list bread of
+	 * ((dbnum + 0x7F) & ~0x7F) << 2 bytes.  A crafted su_btotal
+	 * >= 0x10000001 made that bread exceed MAXBSIZE and panicked the
+	 * kernel at mount time; smaller lies wired attacker-chosen kernel
+	 * heap (dbnum * 2048 bytes) and were echoed verbatim into statfs
+	 * f_blocks.  (su_btotal >= 0xFFFFC001 additionally wraps the u32
+	 * addition -- DF-0862's undersize regime.)
+	 *
+	 * Bounds:
+	 *  - non-zero, and no larger than the backing device
+	 *    (dev_dpsize() reports DEV_BSIZE-scale blocks);
+	 *  - at most 0x4000 data bands, which keeps hpfs_bminit's single
+	 *    bitmap-index bread within MAXBSIZE (volumes > 128 GiB in
+	 *    512-byte sectors are beyond what this driver's bminit can
+	 *    read in one buffer; reject instead of panicking);
+	 *  - su_rootfno must lie inside the volume.
+	 */
+	{
+		int64_t devsize = dev_dpsize(dev);
+		u_int64_t dbnum = ((u_int64_t)sup->su_btotal + 0x3FFF) / 0x4000;
+
+		if (sup->su_btotal == 0 ||
+		    (devsize > 0 && (u_int64_t)sup->su_btotal > (u_int64_t)devsize) ||
+		    dbnum > 0x4000 || sup->su_rootfno >= sup->su_btotal) {
+			kprintf("hpfs_mountfs: implausible su_btotal %u or "
+			    "su_rootfno %u\n", sup->su_btotal, sup->su_rootfno);
+			error = EINVAL;
+			goto failed;
+		}
+	}
+
 	mp->mnt_data = (qaddr_t)hpmp;
 	hpmp->hpm_devvp = devvp;
 	hpmp->hpm_dev = dev;