DF-3744 / harness_lvds.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | /* * DF-3744 harness: unbounded LVDS mode-patch record walk + fake-EDID copy past the * VBIOS image buffer * * Replica of radeon_atombios_get_lvds_info(), sys/dev/drm/radeon/radeon_atombios.c:1691-1752: * record = bios + usModePatchTableOffset (BIOS u16, never validated, :1697-1705) * while (*record != ATOM_RECORD_END_TYPE) { // :1706 no end-of-buffer check * case LCD_FAKE_EDID_PATCH_RECORD_TYPE: // :1717 * if (ucFakeEDIDLength) { * edid_size = max(128, ucFakeEDIDLength); * edid = kmalloc(edid_size); * memcpy(edid, &record->ucFakeEDIDString[0], ucFakeEDIDLength); // :1725 * if (drm_edid_is_valid(edid)) attach as bios_hardcoded_edid * } * record += ucFakeEDIDLength ? ucFakeEDIDLength + 2 : sizeof(...); * ... * } * * The walk has no bound against the BIOS image length: a table whose last record is a * fake-EDID record of length L placed within L-1 bytes of the image end makes the kernel * (a) memcpy up to 255 bytes starting past the heap image end, and (b) continue walking * past the image end. On pass of drm_edid_is_valid() (1/256 checksum odds on uncontrolled * bytes, attacker-controlled while in-image), the copied bytes become the connector EDID * blob, readable from userspace. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; #define ATOM_RECORD_END_TYPE 0 #define LCD_MODE_PATCH_RECORD_MODE_TYPE 0x02 #define LCD_RTS_RECORD_TYPE 0x03 #define LCD_CAP_RECORD_TYPE 0x04 #define LCD_FAKE_EDID_PATCH_RECORD_TYPE 0x05 #define LCD_PANEL_RESOLUTION_RECORD_TYPE 0x06 struct bios_image { /* model of rdev->bios kmalloc(bytes_read) */ u8 data[8192]; u8 guard[512]; /* models adjacent heap after the image */ }; static unsigned long oob_reads, oob_copies; static u8 read_b(const struct bios_image *b, size_t off) /* bounds-checking CU8() */ { size_t total = sizeof(b->data) + sizeof(b->guard); if (off >= sizeof(b->data)) oob_reads++; /* past image, still in model */ if (off >= total) { oob_reads++; return 0xAA; } /* far OOB */ return off < sizeof(b->data) ? b->data[off] : b->guard[off - sizeof(b->data)]; } int main(void) { static struct bios_image img; memset(&img, 0x5C, sizeof(img)); /* adjacent-heap model bytes */ /* place a fake-EDID record at the very end of the image: * [type=0x05][len=0xC8][200 bytes of "EDID" starting past image end] */ size_t image_len = sizeof(img.data); size_t rec_off = image_len - 2; img.data[rec_off] = LCD_FAKE_EDID_PATCH_RECORD_TYPE; img.data[rec_off + 1] = 200; /* ucFakeEDIDLength */ u16 usModePatchTableOffset = (u16)rec_off; /* BIOS u16, unvalidated */ int oob_copy_bytes = 0; /* === verbatim walk, radeon_atombios.c:1706-1752 (bounded only in harness) === */ size_t roff = usModePatchTableOffset; unsigned iter = 0; while (read_b(&img, roff) != ATOM_RECORD_END_TYPE) { u8 type = read_b(&img, roff); if (type == LCD_FAKE_EDID_PATCH_RECORD_TYPE) { u8 len = read_b(&img, roff + 1); if (len) { /* kmalloc(max(128,len)); memcpy from record+2 for len bytes */ u8 *edid = malloc(len > 128 ? len : 128); for (int i = 0; i < len; i++) { size_t src = roff + 2 + (size_t)i; if (src >= image_len) oob_copy_bytes++; /* bytes past image */ edid[i] = read_b(&img, src); } printf("fake-EDID: len=%u ; %d of %u bytes read from BEYOND the " "VBIOS image end (heap OOB read -> kmalloc'd EDID)\n", len, oob_copy_bytes, len); free(edid); } roff += len ? (size_t)len + 2 : 4; } else if (type == LCD_MODE_PATCH_RECORD_MODE_TYPE) { roff += 6; } else if (type == LCD_RTS_RECORD_TYPE) { roff += 3; } else if (type == LCD_CAP_RECORD_TYPE) { roff += 5; } else if (type == LCD_PANEL_RESOLUTION_RECORD_TYPE) { roff += 4; } else { printf("bad record %u at +%zu (walk already %zu bytes past image end)\n", type, roff, roff >= image_len ? roff - image_len + 1 : 0); break; /* kernel: DRM_ERROR + break */ } if (++iter > 64) { printf("walk still running after %u records, now %zu bytes past image end\n", iter, roff - image_len); break; } } printf("total OOB byte reads in model: %lu\n", oob_reads); printf("kernel: bytes past rdev->bios kmalloc'd image copied into bios_hardcoded_edid: %d\n", oob_copy_bytes); if (oob_copy_bytes == 0) { printf("FAIL\n"); return 1; } printf("REPRODUCED (harness): unbounded record walk reads/copies past VBIOS buffer\n"); return 0; } |