โฌข DragonFlyBSD Kernel Audit
DF-4340 / atom_harness.c
โ† back to finding โ†“ download raw
/*
 * atom_harness_guest.c โ€” userland mirror of DragonFlyBSD
 * sys/dev/drm/radeon/atom.c logic, built and run ON THE AUDIT GUEST
 * (DragonFly 6.5-DEVELOPMENT, gcc 8.3) as logic-level PoC evidence for
 * DF-4337 / DF-4338 / DF-4339 / DF-4340.
 *
 * The kernel expressions are copied VERBATIM from atom.c (DRM_* mapped to
 * printf; atom_iio_len is relocated onto a page-guarded mapping so that an
 * out-of-range index traps instead of silently reading adjacent .data).
 * A SIGSEGV in a guarded case therefore proves the identical expression
 * reads out of bounds of the intended object.
 *
 * The kernel runtime itself is gated on radeon/ATOM hardware (guest has
 * none โ€” see env.txt); see VERDICT.md for why this mirror is the evidence.
 *
 * Build:  cc -O1 -g -o atom_harness atom_harness_guest.c
 * Run:    ./atom_harness <case>   case in {data_header, iio, name, name_nul, shift}
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <unistd.h>

#define kprintf printf
#define DRM_INFO(...)  do { if (getenv("VERBOSE")) printf(__VA_ARGS__); } while (0)
#define DRM_ERROR(...) do { printf("DRM_ERROR: " __VA_ARGS__); } while (0)

typedef uint16_t u16;

/* ---- atom-bits.h verbatim ---- */
static inline uint8_t get_u8(void *bios, int ptr)
{
    return ((unsigned char *)bios)[ptr];
}
static inline uint16_t get_u16(void *bios, int ptr)
{
    return get_u8(bios, ptr) | (((uint16_t)get_u8(bios, ptr + 1)) << 8);
}
static inline uint32_t get_u32(void *bios, int ptr)
{
    return get_u16(bios, ptr) | (((uint32_t)get_u16(bios, ptr + 2)) << 16);
}
#define CU8(ptr) get_u8(ctx->bios, (ptr))
#define CU16(ptr) get_u16(ctx->bios, (ptr))
#define CU32(ptr) get_u32(ctx->bios, (ptr))
#define CSTR(ptr) (((char *)(ctx->bios)) + (ptr))

struct atom_context {
	void *bios;
	uint32_t cmd_table, data_table;
	uint16_t *iio;
	uint16_t data_block;
};

/* page-guarded allocations: object ends exactly at a large PROT_NONE region,
 * so any forward OOB read (atom offsets reach up to 128KB past the image)
 * traps instead of hitting an unrelated mapping */
static void *guarded_alloc(size_t sz)
{
	long ps = 4096;
	size_t guard = (size_t)16 * 1024 * 1024;	/* 16MB trap region */
	size_t len = (size_t)ps + guard;
	char *base = mmap(NULL, len, PROT_READ | PROT_WRITE,
			  MAP_PRIVATE | MAP_ANON, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); exit(3); }
	if (mprotect(base + ps, guard, PROT_NONE) != 0) { perror("mprotect"); exit(3); }
	return base + ps - sz;		/* object ends at the guard boundary */
}

/* =====================================================================
 * DF-4337 case: atom_parse_data_header (atom.c:1366-1385) โ€” verbatim.
 * ===================================================================== */
bool atom_parse_data_header(struct atom_context *ctx, int index,
			    uint16_t * size, uint8_t * frev, uint8_t * crev,
			    uint16_t * data_start)
{
	int offset = index * 2 + 4;
	int idx = CU16(ctx->data_table + offset);
	u16 *mdt = (u16 *)((char *)ctx->bios + ctx->data_table + 4);

	if (!mdt[index])
		return false;

	if (size)
		*size = CU16(idx);
	if (frev)
		*frev = CU8(idx + 2);
	if (crev)
		*crev = CU8(idx + 3);
	*data_start = idx;
	return true;
}

/* =====================================================================
 * DF-4338 case: atom_index_iio (atom.c:1254-1268) โ€” verbatim logic.
 * NOTE: atom_iio_len is the same 10-int table, but placed on a guarded
 * mapping so the OOB index traps instead of reading adjacent globals.
 * ===================================================================== */
#define ATOM_IIO_START	1
#define ATOM_IIO_END	9

static int *atom_iio_len;		/* -> guarded 10-int table */

static void atom_index_iio(struct atom_context *ctx, int base)
{
	ctx->iio = calloc(256, 2);
	if (!ctx->iio)
		return;
	while (CU8(base) == ATOM_IIO_START) {
		ctx->iio[CU8(base + 1)] = base + 2;
		base += 2;
		while (CU8(base) != ATOM_IIO_END)
			base += atom_iio_len[CU8(base)];
		base += 3;
	}
}

/* =====================================================================
 * DF-4339 case: the atom_parse() name loop (atom.c:1315-1326) โ€” verbatim.
 * ===================================================================== */
static void atom_parse_name(struct atom_context *ctx, int msg_ptr)
{
	char *str;
	char name[512];
	int i;

	str = CSTR(msg_ptr);
	while (*str && ((*str == '\n') || (*str == '\r')))
		str++;
	memset(name, 0, sizeof(name));
	name[511] = 'A';			/* poison = uninit kernel stack byte */
	for (i = 0; i < 511; i++) {
		name[i] = str[i];
		if (name[i] < '.' || name[i] > 'z') {
			name[i] = 0;
			break;
		}
	}
	printf("  strlen(name) after loop = %zu  (>511 == NUL never written; "
	       "kernel %%s walks uninit stack)\n", strlen(name));
}

/* =====================================================================
 * DF-4340 case: UB shift counts โ€” verbatim expressions from
 * atom_op_shl (atom.c:937), WS OR_MASK (atom.c:252), IIO mask (atom.c:125).
 * ===================================================================== */
static void shift_ub_demo(void)
{
	uint32_t dst = 0x0000FFFFu, shift = 40;
	uint32_t r40 = (dst << shift);		/* UB: exponent >= 32 */
	uint32_t r8  = (dst << 8);
	uint32_t temp = 0x12345678u;
	uint8_t width = 0;
	uint32_t masked = temp & ~((0xFFFFFFFFu >> (32 - width)) << 3); /* UB: >> 32 */
	printf("  (0x%x << 40)=0x%08x  vs (0x%x << 8)=0x%08x  -> %s\n",
	       dst, r40, dst, r8,
	       r40 == r8 ? "PLATFORM-MASKED (shift%%32): UB relied upon"
	                 : "divergent");
	printf("  IIO mask width=0: >>32 gives 0x%08x (UB relied upon)\n", masked);
}

int main(int argc, char **argv)
{
	const char *cs = argc > 1 ? argv[1] : "data_header";
	printf("case: %s\n", cs);

	if (!strcmp(cs, "data_header")) {
		/* 512-byte image (radeon_bios.c self-declared bios[2]*512 size),
		 * master entry 0 points at 0x8000 โ€” far past the allocation. */
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->data_table = 0x20;
		((uint8_t *)ctx->bios)[0x24] = 0x00;	/* mdt[0] = 0x8000 */
		((uint8_t *)ctx->bios)[0x25] = 0x80;
		uint16_t size = 0, data_start = 0;
		bool ok = atom_parse_data_header(ctx, 0, &size, NULL, NULL, &data_start);
		printf("  SURVIVED: ok=%d size=0x%04x start=0x%04x (no trap)\n",
		       ok, size, data_start);
		return 0;
	}
	if (!strcmp(cs, "iio")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		atom_iio_len = guarded_alloc(10 * sizeof(int));
		{ static const int lens[10] = {1,2,3,3,3,3,4,4,4,3};
		  memcpy(atom_iio_len, lens, sizeof(lens)); }
		uint8_t *b = ctx->bios;
		b[0x40] = ATOM_IIO_START;
		b[0x41] = 0x05;
		b[0x42] = 200;			/* opcode 200 -> atom_iio_len[200] */
		atom_index_iio(ctx, 0x40);
		printf("  SURVIVED: iio walk done (no trap)\n");
		return 0;
	}
	if (!strcmp(cs, "name")) {
		/* msg string ends exactly at the (guarded) image end */
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		size_t sz = 512;
		ctx->bios = guarded_alloc(sz);
		memset((uint8_t *)ctx->bios + sz - 300, 'B', 300);
		atom_parse_name(ctx, (int)(sz - 300));
		printf("  SURVIVED (no trap)\n");
		return 0;
	}
	if (!strcmp(cs, "name_nul")) {
		/* fully in-bounds 511 printable bytes => name[511] never set */
		static uint8_t backing[1024];
		memset(backing, 'C', sizeof(backing));
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = backing;
		atom_parse_name(ctx, 0);
		return 0;
	}
	if (!strcmp(cs, "shift")) {
		shift_ub_demo();
		return 0;
	}
	fprintf(stderr, "unknown case %s\n", cs);
	return 2;
}