DF-3745 / harness_objtable.c
/* * 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; } |