DF-3956 / trigger_df3956.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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /* * DF-3956 โ radeon kv_dpm.c vid_mapping_table num_entries==0 -> * entries[0xFFFFFFFF] wild read (~+16 GiB) * ================================================================ * Target: sys/dev/drm/radeon/kv_dpm.c (pass 2) * * Producer (other file, known as amdgpu DF-3890 / radeon sumo DF-1439 family): * sumo_construct_vid_mapping_table() sumo_dpm.c:1616-1645 terminates with * vid_mapping_table->num_entries = i; where i == 0 when no entry has a * nonzero vid_7bit (all sAvail_SCLK[].ulSupportedSCLK == 0, or the * IntegratedSystemInfo table is missing entirely โ kv_parse_sys_info_table * kv_dpm.c:2495-2546 skips everything and still returns 0). * NOTE: the usVoltageIndex OOB *write* inside that constructor is DF-1437 * (sumo_dpm.c) โ NOT re-reported here; this finding is the kv_dpm.c * consumer-side underflow. * * Consumers (this file): * kv_convert_vid2_to_vid7() kv_dpm.c:562-566 (else-branch, taken when * rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.count == 0): * return vid_mapping_table->entries[num_entries - 1].vid_7bit; * num_entries is u32 (sumo_dpm.h:64-67) -> 0-1 = 0xFFFFFFFF, * sizeof(entry)=4 -> displacement 0x3FFFFFFFC (~16 GiB) past the * kv_power_info heap object. * kv_convert_vid7_to_vid2() kv_dpm.c:585-590 โ identical expression. * * Boot path: kv_dpm_enable -> kv_init_graphics_levels (:2344) else-branch -> * kv_set_vid (:2382->:618) / high_voltage check (:2378) -> * kv_convert_2bit_index_to_voltage (:604) -> kv_convert_vid2_to_vid7 (:566); * also kv_parse_sys_info_table boot call kv_construct_max_power_limits_table * (:2543->:1973) and kv_get_high_voltage_limit (:2122). * Requires vddc_dependency_on_sclk.count == 0 (KB/ML IGP boards where the * sclk dependency table is absent) + empty/missing IGP table => all * VBIOS-gated. * * Harness replicates the exact expression against a guard-page-backed object; * arithmetic demonstration, NOT a kernel reproduction. * * Build: cc -O2 -Wall -o trigger_df3956 trigger_df3956.c * Run: ./trigger_df3956 / ./trigger_df3956 crash */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <unistd.h> #include <sys/wait.h> #include <sys/mman.h> #include <signal.h> #define SUMO_MAX_NUMBER_VOLTAGES 4 struct sumo_vid_mapping_entry { unsigned short vid_2bit, vid_7bit; }; struct sumo_vid_mapping_table { /* sumo_dpm.h:64-67 */ unsigned int num_entries; /* u32 */ struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES]; }; _Static_assert(sizeof(struct sumo_vid_mapping_entry) == 4, "entry layout drifted"); /* Producer replay (simplified to the num_entries computation) */ static void sumo_construct_vid_mapping_table_empty( struct sumo_vid_mapping_table *t, const unsigned long avail_sclk[5]) { int i, j; memset(t, 0, sizeof(*t)); for (i = 0; i < 5; i++) if (avail_sclk[i] != 0) /* all zero => nothing stored */ t->entries[0].vid_7bit = 1; /* (indices are DF-1437's bug) */ for (i = 0; i < SUMO_MAX_NUMBER_VOLTAGES; i++) if (t->entries[i].vid_7bit == 0) { for (j = i + 1; j < SUMO_MAX_NUMBER_VOLTAGES; j++) if (t->entries[j].vid_7bit != 0) break; if (j == SUMO_MAX_NUMBER_VOLTAGES) break; /* i == 0 when table empty */ } t->num_entries = i; /* 0 on the crafted/empty table */ } /* Consumer โ verbatim shape of kv_dpm.c:562-566 */ static unsigned short kv_convert_vid2_to_vid7(struct sumo_vid_mapping_table *t, unsigned int vid_2bit, unsigned int vddc_sclk_count) { if (vddc_sclk_count) { /* if-branch: not our path */ return 0; } else { for (unsigned int i = 0; i < t->num_entries; i++) /* 0 iters */ if (t->entries[i].vid_2bit == vid_2bit) return t->entries[i].vid_7bit; return t->entries[t->num_entries - 1].vid_7bit; /* +0xFFFFFFFF*4 */ } } int main(int argc, char **argv) { int crash = (argc > 1 && !strcmp(argv[1], "crash")); unsigned long avail_sclk[5] = { 0, 0, 0, 0, 0 }; /* crafted IGP */ struct sumo_vid_mapping_table probe; sumo_construct_vid_mapping_table_empty(&probe, avail_sclk); printf("DF-3956 radeon kv_dpm vid_mapping num_entries==0 (simulation)\n"); printf("producer: sumo_construct_vid_mapping_table -> num_entries = %u\n", probe.num_entries); unsigned int bad32 = (unsigned int)0 - 1; /* u32 num_entries(0) - 1 */ unsigned long disp = (size_t)bad32 * sizeof(struct sumo_vid_mapping_entry); printf("consumer: kv_convert_vid2_to_vid7 :566 -> entries[0x%x] -> +0x%lx (%.2f GiB)\n", bad32, disp, disp / 1073741824.0); printf("kernel effect: wild read ~16 GiB past kv_power_info => page fault => panic\n"); if (!crash) { printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute the deref.\n"); return 0; } const size_t map_sz = 4096; char *region = mmap(NULL, map_sz + 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (region == MAP_FAILED) { perror("mmap"); return 1; } if (mprotect(region + map_sz, 4096, PROT_NONE) != 0) { perror("mprotect"); return 1; } struct sumo_vid_mapping_table *t = (struct sumo_vid_mapping_table *)(region + map_sz - sizeof(*t)); sumo_construct_vid_mapping_table_empty(t, avail_sclk); pid_t p = fork(); if (p == 0) { printf("child: executing 'entries[num_entries - 1].vid_7bit' (kv_dpm.c:566) ...\n"); fflush(stdout); unsigned short v = kv_convert_vid2_to_vid7(t, 0, 0 /* count==0 */); printf("child: NOT REACHED (vid_7bit=0x%x)\n", v); _exit(0); } int st; waitpid(p, &st, 0); printf("child: %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited"); if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st)); printf(" -> predicted wild read at +0x%lx confirmed faulting\n", disp); printf("RESULT: %s\n", (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ? "WILD READ FAULTS AS PREDICTED" : "DEVIATION"); return (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ? 0 : 1; } |