DF-3953 / trigger_df3953.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 | /* * DF-3953 โ radeon kv_dpm.c boot_ps NULL dereference (SIMULATION HARNESS) * ========================================================================== * Target: sys/dev/drm/radeon/kv_dpm.c (radeon KV/Kaveri DPM, pass 2) * * Chain (source-certain, hardware-gated): * kv_parse_pplib_non_clock_info() kv_dpm.c:2598-2601 only assigns * rdev->pm.dpm.boot_ps for a state with ATOM_PPLIB_CLASSIFICATION_BOOT. * A VBIOS PowerPlayInfo table that parses (kv_parse_power_table -> 0) but * contains no BOOT-classified state leaves boot_ps == NULL. * radeon_pm_init_dpm() radeon_pm.c:1448 *discards* kv_dpm_init()'s return * value and proceeds to radeon_dpm_enable() == kv_dpm_enable() anyway. * kv_dpm_enable() kv_dpm.c:1277 / kv_dpm_disable() kv_dpm.c:1326 call * kv_update_current_ps(rdev, rdev->pm.dpm.boot_ps) with boot_ps == NULL * -> kv_get_ps(NULL) kv_dpm.c:239-244 dereferences rps->ps_priv. * Additional sink of the same family: kv_dpm_get_sclk() kv_dpm.c:2881 * dereferences pi->requested_rps.ps_priv which stays NULL whenever * radeon_dpm_pick_power_state() returned NULL (radeon_pm.c:1051) and * radeon_pm_compute_clocks_dpm bailed at radeon_pm.c:1075-1078; the next * dce8_bandwidth_update (cik.c:9287/9327) then faults through * requested_state->levels[0]. * * This harness replicates the exact struct layout + dereference arithmetic in * userland: a forked child performs the verbatim `NULL + offsetof` read and * must die with SIGSEGV at exactly the address the kernel would page-fault on. * It is an arithmetic demonstration, NOT a kernel reproduction. * * Build: cc -O2 -Wall -o trigger_df3953 trigger_df3953.c * Run: ./trigger_df3953 (analysis only, exits 0) * ./trigger_df3953 crash (child performs the faulting derefs) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #include <errno.h> /* --- verbatim-layout stand-ins (radeon.h / kv_dpm.h) ------------------- */ struct radeon_ps { /* ps_priv is the last-ish member */ unsigned long pad0[8]; /* misc radeon_ps fields */ void *ps_priv; /* kv_get_ps() reads this */ }; struct kv_ps { struct { unsigned long sclk; unsigned char pad[8]; } levels[5]; unsigned long num_levels; }; static struct radeon_ps boot_ps_null; /* stays NULL-ps_priv */ static struct kv_ps current_ps; /* kv_get_ps() โ kv_dpm.c:239-244 */ static struct kv_ps *kv_get_ps(struct radeon_ps *rps) { return rps->ps_priv; /* faults when rps == NULL */ } /* kv_update_current_ps() โ kv_dpm.c:1135-1144 (volatile sink so the * compiler cannot eliminate the faulting load, unlike a dead store) */ static struct kv_ps *volatile observed_ps; static void kv_update_current_ps(struct radeon_ps *rps) { struct kv_ps *new_ps = kv_get_ps(rps); /* <-- the deref of NULL */ observed_ps = new_ps; /* observable, not eliminable */ } /* kv_dpm_get_sclk() low branch โ kv_dpm.c:2878-2887 (ps_priv NULL window) */ static unsigned long kv_dpm_get_sclk_pspriv_null(struct radeon_ps *requested_rps) { struct kv_ps *requested_state = kv_get_ps(requested_rps); return requested_state->levels[0].sclk; } int main(int argc, char **argv) { int crash = (argc > 1 && !strcmp(argv[1], "crash")); size_t off_ps_priv = offsetof(struct radeon_ps, ps_priv); size_t off_lv0 = offsetof(struct kv_ps, levels[0]); printf("DF-3953 radeon kv_dpm boot_ps NULL family (simulation)\n"); printf("offsetof(struct radeon_ps, ps_priv) = %zu\n", off_ps_priv); printf("kv_get_ps(NULL) reads *(void **)(NULL + %zu)\n", off_ps_priv); printf("kv_dpm_enable (:1277) -> kv_update_current_ps(rdev, boot_ps=NULL)\n"); printf("kv_dpm_disable (:1326) -> kv_update_current_ps(rdev, boot_ps=NULL)\n"); printf("kv_dpm_get_sclk (:2881) reads *(kv_ps **)(NULL + %zu).levels[0] @ +%zu\n", off_ps_priv, off_lv0); printf("kernel fault address for kv_get_ps(NULL): 0x%zx (NULL page, always unmapped)\n", off_ps_priv); if (!crash) { printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute the derefs.\n"); return 0; } /* Child 1: kv_dpm_enable() end-of-enable NULL deref */ pid_t p1 = fork(); if (p1 == 0) { printf("child1: executing kv_update_current_ps(boot_ps == NULL) ...\n"); fflush(stdout); boot_ps_null.ps_priv = NULL; kv_update_current_ps(NULL); /* the real bug: rps == NULL */ printf("child1: NOT REACHED (observed_ps=%p)\n", (void *)observed_ps); _exit(0); } /* Child 2: kv_dpm_get_sclk() ps_priv-NULL window (pick_power_state==NULL) */ pid_t p2 = fork(); if (p2 == 0) { printf("child2: executing kv_dpm_get_sclk with requested_rps == NULL ...\n"); fflush(stdout); unsigned long s = kv_dpm_get_sclk_pspriv_null(NULL); printf("child2: NOT REACHED (sclk=%lu)\n", s); _exit(0); } int st, rc = 0; waitpid(p1, &st, 0); printf("child1 (kv_get_ps(NULL) @ :239-244 via :1277): %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited"); if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st)); printf(" -> kernel equivalent: page fault on NULL page => panic\n"); if (!WIFSIGNALED(st) || WTERMSIG(st) != SIGSEGV) rc = 1; waitpid(p2, &st, 0); printf("child2 (kv_dpm_get_sclk :2881): %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited"); if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st)); printf(" -> kernel equivalent: page fault on NULL page => panic\n"); if (!WIFSIGNALED(st) || WTERMSIG(st) != SIGSEGV) rc |= 2; printf("RESULT: %s (rc=%d)\n", rc ? "DEVIATION" : "BOTH SINKS FAULT AS PREDICTED", rc); return rc; } |