โฌข DragonFlyBSD Kernel Audit
DF-3745 / harness_objtable.c
โ† back to finding โ†“ download raw
/*
 * DF-3745 harness: connector/encoder object-table walks dereference unvalidated VBIOS
 * offsets and indices โ€” OOB reads at connector init
 *
 * Replicas of radeon_get_atom_connector_info_from_object_table(),
 * sys/dev/drm/radeon/radeon_atombios.c:509-836:
 *
 *  (1) :551-557  path walk: path_size += le16_to_cpu(path->usSize);  โ€” accumulated
 *      attacker u16; after 2 paths of 0x8000 the record pointer is 64 KiB past the
 *      table (still dereferenced at :556-557).
 *  (2) :654-656 / :685-698  record/srcdst pointers = bios + data_offset +
 *      le16_to_cpu(...usRecordOffset / usSrcDstTableOffset)   โ€” u16 offsets never
 *      validated against the image length; dereferenced immediately (:660, :703).
 *  (3) :703-707  enum_id runs to *num_dst_objs (BIOS u8, up to 255) and is then used
 *      at :732/:740 as ucMuxState[enum_id] โ€” but ucMuxState is UCHAR[2]
 *      (atombios.h:4380-4387) -> up to 253 bytes OOB byte read.
 *  (4) :660-672 / :772-803  record chains stepped by ucRecordSize with no image-end
 *      check (walk continues while size>0 and type in range; OOB once past image).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

struct bios_image { u8 data[4096]; u8 guard[1024]; };
static unsigned long oob_reads;
static u8 rd(const struct bios_image *b, size_t off)
{
	if (off >= sizeof(b->data) + sizeof(b->guard)) { oob_reads++; return 0; }
	if (off >= sizeof(b->data)) { oob_reads++; return b->guard[off - sizeof(b->data)]; }
	return b->data[off];
}

int main(void)
{
	static struct bios_image img;
	memset(&img, 0x66, sizeof(img));

	/* ---- (1) path_size accumulation (usSize read from memory, as the kernel does) ---- */
	size_t path_size = 0, addr = 0;
	u8  ucNumOfDispPath = 3;				/* BIOS u8 */
	for (int i = 0; i < (int)ucNumOfDispPath; i++) {
		addr = path_size;					/* :553-556 */
		u16 usSize = (u16)rd(&img, addr + 2) | ((u16)rd(&img, addr + 3) << 8);
		u16 devtag = (u16)rd(&img, addr + 4) | ((u16)rd(&img, addr + 5) << 8);
		(void)devtag;
		path_size += usSize;					/* :557 */
		printf("path %d: record@img+0x%04zx%s -> path_size=0x%zx (image is %zu bytes)\n",
		       i, addr, addr + 2 >= sizeof(img.data) ? "  <-- record OOB (past image)" : "",
		       path_size, sizeof(img.data));
	}

	/* ---- (2) unvalidated record offset ---- */
	u16 usRecordOffset = 0xFFF0;			/* BIOS u16 */
	size_t roff = usRecordOffset;			/* :654-656: bios + data_offset + off */
	u8 rectype = rd(&img, roff);
	u8 recsize = rd(&img, roff + 1);
	printf("encoder record @+0x%04x (image is %zu bytes): type=%u size=%u โ€” OOB deref\n",
	       usRecordOffset, sizeof(img.data), rectype, recsize);

	/* ---- (3) ucMuxState[enum_id] with enum_id from BIOS u8 count ---- */
	u8 num_dst_objs = 255;				/* *num_dst_objs, :696-699 */
	u8 ucMuxState_len = 2;				/* atombios.h:4394 (DDC record: [2]) */
	size_t enum_id;
	for (enum_id = 0; enum_id < num_dst_objs; enum_id++) {
		u16 want = 0x1111, got = (u16)rd(&img, 2 * (size_t)enum_id) |
			  ((u16)rd(&img, 2 * (size_t)enum_id + 1) << 8);
		if (want == got) break;			/* :703-707 no match -> 255 */
	}
	printf("*num_dst_objs=%u, no match -> enum_id=%zu ; ucMuxState[%zu] read is %zd bytes "
	       "past the 2-byte array (:732/:740)\n",
	       num_dst_objs, enum_id, enum_id, (ssize_t)(enum_id - ucMuxState_len));

	/* ---- (4) record chain walk past image end ---- */
	/* craft: last image bytes hold a valid record header (type 2, size 16) and the guard
	 * region continues the chain (as adjacent heap would if it happens to hold 02 10) */
	size_t coff = sizeof(img.data) - 2;
	img.data[coff] = 2; img.data[coff + 1] = 16;		/* ATOM_COMMON_RECORD_HEADER */
	img.guard[0] = 2; img.guard[1] = 16;
	img.guard[16] = 2; img.guard[17] = 16;
	u8 type = rd(&img, coff);
	unsigned hops = 0;
	while (type > 0 && type <= 0x0A && hops < 1000) {	/* :660-662 */
		u8 sz = rd(&img, coff + 1);			/* ucRecordSize, BIOS u8 */
		if (sz == 0) sz = 1;
		coff += sz;					/* :670-671 */
		type = rd(&img, coff);
		hops++;
		if (coff >= sizeof(img.data))
			printf("hop %u: record@+%zu is %zu bytes past image end โ€” still walked\n",
			       hops, coff, coff - sizeof(img.data) + 1);
	}
	printf("record chain ran %u hops, final offset +%zu (%zu bytes past image), "
	       "stopped only on non-record garbage\n", hops, coff, coff - sizeof(img.data) + 1);

	printf("total modeled OOB byte reads: %lu\n", oob_reads);
	printf("REPRODUCED (harness): object-table OOB reads from unvalidated offsets/indices\n");
	return 0;
}