DF-3942 / bios_ss_null_deref_replica.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 117 118 119 120 | /* * DF-3942 logic replica — bios_parser.c unchecked GET_IMAGE() NULL derefs * * Target: sys/dev/drm/amd/display/dc/bios/bios_parser.c * get_ss_info_from_internal_ss_info_tbl_V2_1 GET_IMAGE at :1041, deref at :1046 * get_ss_info_from_ss_info_table GET_IMAGE at :1114, deref at :1140 * get_ss_entry_number_from_ss_info_tbl GET_IMAGE at :1645, deref at :1671 * get_ss_entry_number_from_internal_ss_info_tbl_v2_1 GET_IMAGE at :1723, deref at :1726 * get_ss_entry_number_from_internal_ss_info_tbl_V3_1 GET_IMAGE at :1759, deref at :1761 * * These functions call GET_IMAGE(type, DATA_TABLES(ASIC_InternalSS_Info | SS_Info)) * and dereference the result WITHOUT a NULL check, unlike every other parser in * the file (e.g. get_gpio_pin_info :1802, get_embedded_panel_info_v1_2 :1233). * * GET_IMAGE(type, offset) == (type *)bios_get_image(bp, offset, sizeof(type)) * (bios_parser_helper.h / bios_parser_helper.c:36-44): * if (bp->bios && offset + size < bp->bios_size) * return bp->bios + offset; * return NULL; * * Reachability with a crafted VBIOS: the SS table pointer * (master_data_tbl->ListOfDataTables.AsicInternalSS_Info) is a VBIOS u16. * Point it at the last bytes of the image: * - the revision probe GET_IMAGE(ATOM_COMMON_TABLE_HEADER, off) (4 bytes, * bios_parser.c:952-954 / :1111) still succeeds and reports rev 2.1, * which gates entry into get_ss_info_from_internal_ss_info_tbl_V2_1; * - the struct fetch GET_IMAGE(ATOM_ASIC_INTERNAL_SS_INFO_V2, off) * (4 + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2)=4+14=18 bytes) FAILS -> NULL; * - line 1046 reads header->sHeader.usStructureSize => NULL deref => * kernel panic during amdgpu/dc attach. * * This program replicates that exact decision path in userland (same * bounds semantics, same order of probes) against a synthetic VBIOS image. * It is a LOGIC REPLICA, not the in-kernel path: no AMD GPU with a crafted * ROM is available in the audit guest (documented in VERDICT.md / run.log). * * Expected output: the struct fetch returns NULL (as in the kernel) and the * replica takes the deref path -> controlled SIGSEGV, proving the reachable * NULL. In the kernel this address is the NULL page -> panic. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> /* --- exact copies of the kernel-side bounds/parse logic --------------- */ static uint8_t *g_bios; static uint32_t g_bios_size; static void *bios_get_image(uint32_t offset, uint32_t size) /* bios_parser_helper.c:36 */ { if (g_bios && offset + size < g_bios_size) return g_bios + offset; return NULL; } #define GET_IMAGE(type, offset) ((type *)bios_get_image(offset, sizeof(type))) typedef struct { uint16_t usStructureSize; uint8_t ucTableFormatRevision, ucTableContentRevision; } __attribute__((packed)) ATOM_COMMON_TABLE_HEADER; typedef struct { uint32_t ulTargetClockRange; uint16_t usSpreadSpectrumPercentage, usSpreadRateIn10Hz; uint8_t ucClockIndication, ucSpreadSpectrumMode, ucReserved[2]; } __attribute__((packed)) ATOM_ASIC_SS_ASSIGNMENT_V2; typedef struct { ATOM_COMMON_TABLE_HEADER sHeader; ATOM_ASIC_SS_ASSIGNMENT_V2 asSpreadSpectrum[1]; } __attribute__((packed)) ATOM_ASIC_INTERNAL_SS_INFO_V2; /* replica of get_ss_info_from_internal_ss_info_tbl_V2_1 (bios_parser.c:1028) */ static __attribute__((noinline)) uint32_t get_ss_info_V2_1(uint32_t table_offset) { ATOM_ASIC_INTERNAL_SS_INFO_V2 *header; uint32_t tbl_size; header = GET_IMAGE(ATOM_ASIC_INTERNAL_SS_INFO_V2, table_offset); /* :1041 */ /* [no NULL check here either -- this is the bug] */ tbl_size = (header->sHeader.usStructureSize /* :1046 DEREF */ - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2); return tbl_size; /* keep the load live so the NULL deref executes */ } int main(void) { uint32_t off_hdr, off_tbl; ATOM_COMMON_TABLE_HEADER *hdr; g_bios_size = 128 * 1024; /* typical VBIOS image */ g_bios = calloc(1, g_bios_size); /* * Crafted VBIOS: AsicInternalSS_Info points 10 bytes before the end of * the image. 4-byte header probe fits; 18-byte struct fetch does not. */ off_tbl = g_bios_size - 10; hdr = GET_IMAGE(ATOM_COMMON_TABLE_HEADER, off_tbl); /* replica of :952 */ if (hdr == NULL) { printf("[-] header probe failed -- offset not reachable\n"); return 1; } /* claim revision 2.1 so the kernel takes the V2_1 branch */ hdr->ucTableFormatRevision = 2; hdr->ucTableContentRevision = 1; printf("[+] 4-byte revision probe at off=%u succeeded (rev %u.%u) --" " kernel gates into get_ss_info_from_internal_ss_info_tbl_V2_1\n", off_tbl, hdr->ucTableFormatRevision, hdr->ucTableContentRevision); off_hdr = offsetof(ATOM_ASIC_INTERNAL_SS_INFO_V2, asSpreadSpectrum); printf("[+] sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2)=%zu > %u bytes left" " -> kernel GET_IMAGE at :1041 returns NULL\n", sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2), g_bios_size - off_tbl); if (bios_get_image(off_tbl, sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2)) == NULL) printf("[+] kernel takes the deref at bios_parser.c:1046" " (header->sHeader.usStructureSize) -> NULL page -> panic\n"); /* now take the same path in the replica: this SIGSEGVs on the NULL */ printf("[*] entering replica of bios_parser.c:1041-1046 ...\n"); fflush(stdout); printf("[*] replica computed tbl_size=%u\n", get_ss_info_V2_1(off_tbl)); printf("[-] not reached\n"); return 0; } |