DF-5075 / trigger_nullhelper.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-5075 trigger_nullhelper.c -- userspace semantics harness (NOT a kernel PoC). * * Mirrors, exactly, the C-level shape of the kernel defect chain: * * (1) amdgpu kzalloc's struct bios_parser -> bp->cmd_helper == NULL * [sys/dev/drm/amd/display/dc/bios/bios_parser.c:100] * * (2) bios_parser construct() calls * dal_bios_parser_init_cmd_tbl_helper(&bp->cmd_helper, dce_version) * and IGNORES the returned bool [bios_parser.c:2935-2936] * * (3) for dce_version outside {DCE 8.0,8.1,8.3,10.0,11.0,11.2,11.22} * -- i.e. DCE_VERSION_12_0 / DCN_VERSION_1_0 / DCN_VERSION_1_01 / * DCE_VERSION_UNKNOWN -- the hub switch falls to default, leaves *h * untouched (NULL) and returns false * [sys/dev/drm/amd/display/dc/bios/command_table_helper.c:34-63] * * (4) construct() continues and returns true; dc_construct succeeds * (resource pools exist for DCE12/DCN: dc_resource.c:138-146); * the first BIOS command at mode set does * bp->cmd_helper->assign_control_parameter(...) [command_table.c:204] * -> load of a function pointer from NULL+offset -> page fault. * * This program reproduces the *C semantics* of that chain (steps 1-4) in an * unprivileged user process: expect the child to die with SIGSEGV. * Success criterion: exit code 42. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> /* shape of struct command_table_helper (first members, offsets matter) */ struct command_table_helper { int (*controller_id_to_atom)(int id, unsigned char *atom_id); /* +0x00 */ int (*encoder_action_to_atom)(int action); /* +0x08 */ long (*encoder_mode_bp_to_atom)(int s, int audio); /* +0x10 */ void (*engine_bp_to_atom)(int engine_id, unsigned *atom_id); /* +0x18 */ void (*assign_control_parameter)(const struct command_table_helper *h, void *control, void *ctrl_param); /* +0x20 */ int (*clock_source_id_to_atom)(int id, unsigned *pll_id); /* +0x28 */ }; /* the hub: mirrors dal_bios_parser_init_cmd_tbl_helper() -- on an unhandled * version it returns 0 and leaves *h exactly as it was (NULL). */ static int init_cmd_tbl_helper(const struct command_table_helper **h, int dce_version) { switch (dce_version) { case 80: case 81: case 83: case 100: case 110: case 112: case 1122: /* static const table omitted: handled versions would set *h */ *h = (const struct command_table_helper *)(uintptr_t)0x1000; return 1; default: /* Unsupported DCE -- BREAK_TO_DEBUGGER() is a no-op in release */ return 0; /* *h NOT touched */ } } int main(void) { /* (1) kzalloc semantics: zero-filled storage */ struct parser_shim { const struct command_table_helper *cmd_helper; /* first member */ } *bp = calloc(1, sizeof(*bp)); struct bp_encoder_control { int dummy; } cntl = { 0 }; struct dig_params { unsigned char raw[16]; } params = { 0 }; int dce_version = 1200; /* DCE_VERSION_12_0 shape: unhandled */ pid_t pid; int st; if (bp == NULL) { perror("calloc"); return 1; } printf("[*] kzalloc'd bios_parser: bp->cmd_helper = %p (NULL)\n", (void *)bp->cmd_helper); pid = fork(); if (pid < 0) { perror("fork"); return 1; } if (pid == 0) { /* (2)+(3) the caller-side bug: return value IGNORED, *h left NULL */ (void)init_cmd_tbl_helper(&bp->cmd_helper, dce_version); printf("[*] init_cmd_tbl_helper(dce_version=%d) returned; " "return value ignored, cmd_helper = %p\n", dce_version, (void *)bp->cmd_helper); fflush(stdout); /* (4) first command-table dispatch: unguarded indirect call -- * reads the member function pointer from NULL+0x20 */ printf("[*] calling bp->cmd_helper->assign_control_parameter() " "(member at +0x20)...\n"); fflush(stdout); bp->cmd_helper->assign_control_parameter(bp->cmd_helper, &cntl, ¶ms); /* NOT REACHED if the semantics hold */ printf("[-] call returned -- semantics NOT reproduced\n"); exit(1); } if (waitpid(pid, &st, 0) < 0) { perror("waitpid"); return 1; } if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) { printf("[+] child died SIGSEGV at the indirect call through " "the NULL vtable -- semantics chain (1)-(4) confirmed\n"); return 42; } printf("[-] unexpected child status %#x\n", st); return 1; } |