DF-3954 / trigger_df3954.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 | /* * DF-3954 โ radeon kv_dpm.c num_levels==0 -> levels[0xFFFFFFFF] wild read * ======================================================================== * Target: sys/dev/drm/radeon/kv_dpm.c (radeon KV/Kaveri DPM, pass 2) * * Root cause (source-certain, hardware-gated): * kv_parse_power_table() kv_dpm.c:2681-2694: for each power state, k only * advances when a clockInfoIndex is < clock_info_array->ucNumEntries AND * k < SUMO_MAX_HARDWARE_POWERLEVELS. A crafted VBIOS state with * ucNumDPMLevels == 0 (or with every clockInfoIndex out of range) never * calls kv_parse_pplib_clock_info(), so kzalloc'd ps->num_levels stays 0 * (struct kv_ps, kv_dpm.h:79-87: u32 num_levels, levels[] is FIRST member). * * Sinks (u32 arithmetic: 0 - 1 = 0xFFFFFFFF, sizeof(struct kv_pl) = 12): * kv_set_valid_clock_range() kv_dpm.c:1721 and :1745-1747 * new_ps->levels[new_ps->num_levels - 1].sclk * -> levels + 0xFFFFFFFF*12 = object + 0x2FFFFFFF4 (~12.9 GiB) * kv_dpm_get_sclk() kv_dpm.c:2886 (non-low branch) * requested_state->levels[requested_state->num_levels - 1].sclk * * Reachability: such a zero-level state is picked by * radeon_dpm_pick_power_state() (radeon_pm.c:950-1051) whenever its class * matches (e.g. PERFORMANCE) -> radeon_dpm_change_power_state_locked -> * kv_dpm_pre_set_power_state -> kv_dpm_set_power_state -> * kv_set_valid_clock_range (:1721). kv_dpm_get_sclk is called from * dce8_bandwidth_update (cik.c:9287/9327) after that state became * 'requested'. * * This harness replicates the struct layout and the exact index expression in * userland: the object is mmap'd directly below a PROT_NONE guard region far * smaller than the 12.9 GiB computed displacement, so the dereference faults. * Arithmetic demonstration, NOT a kernel reproduction. * * Build: cc -O2 -Wall -o trigger_df3954 trigger_df3954.c * Run: ./trigger_df3954 / ./trigger_df3954 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_HARDWARE_POWERLEVELS 5 struct kv_pl { /* kv_dpm.h:70-78 โ 12 bytes */ unsigned int sclk; unsigned char vddc_index; unsigned char ds_divider_index; unsigned char ss_divider_index; unsigned char allow_gnb_slow; unsigned char force_nbp_state; unsigned char display_wm; unsigned char vce_wm; }; struct kv_ps { /* kv_dpm.h:79-87 โ levels[] FIRST member */ struct kv_pl levels[SUMO_MAX_HARDWARE_POWERLEVELS]; unsigned int num_levels; /* u32 */ unsigned char need_dfs_bypass; unsigned char dpm0_pg_nb_ps_lo, dpm0_pg_nb_ps_hi; unsigned char dpmx_nb_ps_lo, dpmx_nb_ps_hi; }; _Static_assert(sizeof(struct kv_pl) == 12, "kv_pl layout drifted"); int main(int argc, char **argv) { int crash = (argc > 1 && !strcmp(argv[1], "crash")); unsigned int bad32 = (unsigned int)0 - 1; /* u32 num_levels(0) - 1 */ unsigned long disp = (size_t)bad32 * sizeof(struct kv_pl); printf("DF-3954 radeon kv_dpm num_levels==0 wild read (simulation)\n"); printf("sizeof(struct kv_pl) = %zu\n", sizeof(struct kv_pl)); printf("num_levels(u32) = 0 => index = 0x%x => displacement = 0x%lx (%.2f GiB)\n", bad32, disp, disp / 1073741824.0); printf("kv_set_valid_clock_range :1721/:1745-1747 -> read at ps_obj + 0x%lx\n", disp); printf("kv_dpm_get_sclk :2886 -> read at ps_obj + 0x%lx\n", disp); printf("kernel effect: unmapped/non-canonical address => page fault => panic\n"); if (!crash) { printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute the deref.\n"); return 0; } /* Place a fake kv_ps just below a guard region; +12.9 GiB is far beyond * the mapping -> SIGSEGV exactly like the kernel page fault. */ 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 kv_ps *ps = (struct kv_ps *)(region + map_sz - sizeof(struct kv_ps)); memset(ps, 0, sizeof(*ps)); ps->num_levels = 0; /* crafted state: ucNumDPMLevels == 0 */ pid_t p = fork(); if (p == 0) { printf("child: executing 'ps->levels[ps->num_levels - 1].sclk' (kv_dpm.c:1721) ...\n"); fflush(stdout); /* verbatim expression from kv_set_valid_clock_range() */ unsigned int sclk = ps->levels[ps->num_levels - 1].sclk; printf("child: NOT REACHED (sclk=0x%x)\n", sclk); _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; } |