DF-3610 / harness.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 143 144 145 | /* * DF-3610 — boot_ps NULL-pointer dereference in si_dpm_enable (radeon si_dpm.c) * * Twin of amdgpu DF-3586. rdev->pm.dpm.boot_ps is only ever set in * si_parse_pplib_non_clock_info() (si_dpm.c:6726-6727) when a VBIOS power * state carries ATOM_PPLIB_CLASSIFICATION_BOOT (0x0008). A crafted VBIOS * whose state array has no such state (or ucNumEntries == 0) leaves * boot_ps == NULL, but si_parse_power_table() still returns 0. * radeon_pm_init_dpm() (radeon_pm.c:1448-1453) ignores the dpm.init return * value, assigns current_ps/requested_ps = boot_ps and calls * radeon_dpm_enable() -> si_dpm_enable(rdev): * * si_dpm.c:6354 struct radeon_ps *boot_ps = rdev->pm.dpm.boot_ps; // NULL * si_dpm.c:6401 ret = si_init_smc_table(rdev); * si_dpm.c:4700 radeon_boot_state = rdev->pm.dpm.boot_ps; // NULL * si_dpm.c:4746 si_populate_smc_initial_state(rdev, NULL, table) * si_dpm.c:4372 struct ni_ps *initial_state = ni_get_ps(NULL) * ni_dpm.c:733-735 return rps->ps_priv; // read at NULL+0x28 -> panic * * This harness replicates the parse + first deref. Part [A] executes the * real dereference in a forked child (expect SIGSEGV at si_addr = 0x28 — * in-kernel equivalent: unrecoverable NULL-deref panic during boot/DPM * enable). Part [B] shows the one-line guard from fix.diff rejecting the * configuration instead. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <stdint.h> #include <errno.h> #include <stddef.h> /* --- verbatim-relevant replica of struct radeon_ps (radeon.h:1342-1356) --- */ struct radeon_ps { uint32_t caps; uint32_t class; uint32_t class2; uint32_t vclk; uint32_t dclk; uint32_t evclk; uint32_t ecclk; uint8_t vce_active; /* bool */ uint8_t _pad[3]; int32_t vce_level; /* enum */ void *ps_priv; /* offset 0x28 on LP64 */ }; struct ni_pl { uint32_t mclk, sclk, vddc; }; struct ni_ps { uint32_t performance_level_count; struct ni_pl performance_levels[16]; }; #define ATOM_PPLIB_CLASSIFICATION_BOOT 0x0008 /* pptable.h:230 */ static struct radeon_ps *g_boot_ps; /* == rdev->pm.dpm.boot_ps */ /* ni_dpm.c:733-735 */ static struct ni_ps *ni_get_ps(struct radeon_ps *rps) { return rps->ps_priv; /* NULL+0x28 read when rps==NULL */ } /* si_dpm.c:4368-4372 (first statements of si_populate_smc_initial_state) */ static uint32_t si_populate_smc_initial_state(struct radeon_ps *radeon_initial_state) { struct ni_ps *initial_state = ni_get_ps(radeon_initial_state); return initial_state->performance_levels[0].mclk; /* first data deref */ } /* si_parse_pplib_non_clock_info (si_dpm.c:6706-6730), BOOT-classification only */ static void parse_non_clock_info(struct radeon_ps *rps, uint16_t usClassification) { rps->class = usClassification; if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT) g_boot_ps = rps; } /* si_init_smc_table (si_dpm.c:4696/4700/4746) */ static int si_init_smc_table(struct radeon_ps *boot_state) { return (int)si_populate_smc_initial_state(boot_state); } /* si_dpm_enable (si_dpm.c:6349-6354, 6401) */ static int si_dpm_enable(struct radeon_ps *boot_ps) { return si_init_smc_table(boot_ps); } static volatile uint32_t sink; static void run_deref(struct radeon_ps *boot_ps) { sink = (uint32_t)si_dpm_enable(boot_ps); /* the kernel panics in here */ printf(" UNREACHABLE: deref survived\n"); } int main(void) { /* crafted VBIOS: 3 power states, none classified BOOT */ struct radeon_ps ps[3]; struct ni_ps priv[3]; uint8_t class_i[] = { 0x0001 /* UI battery */, 0x0002 /* UI balanced */, 0x0004 /* UI performance */ }; printf("== DF-3610 harness: crafted VBIOS with no ATOM_PPLIB_CLASSIFICATION_BOOT state ==\n\n"); printf("rdev->pm.dpm.boot_ps initial = %p\n", (void *)g_boot_ps); for (int i = 0; i < 3; i++) { memset(&ps[i], 0, sizeof(ps[i])); memset(&priv[i], 0, sizeof(priv[i])); priv[i].performance_level_count = 1; priv[i].performance_levels[0].mclk = 60000 + i; ps[i].ps_priv = &priv[i]; parse_non_clock_info(&ps[i], class_i[i]); } printf("after si_parse_power_table (3 states): boot_ps = %p\n", (void *)g_boot_ps); printf("si_parse_power_table returns 0 (parse 'succeeded')\n"); printf("radeon_pm.c:1448 ignores dpm.init rc; :1449 current_ps=requested_ps=boot_ps\n"); printf("offsetof(struct radeon_ps, ps_priv) = 0x%zx\n", offsetof(struct radeon_ps, ps_priv)); printf("\n[A] Buggy path: si_dpm_enable -> si_init_smc_table -> " "si_populate_smc_initial_state -> ni_get_ps(NULL)->ps_priv\n"); fflush(stdout); pid_t p = fork(); if (p == 0) { run_deref(g_boot_ps); _exit(0); } int st = 0; waitpid(p, &st, 0); if (WIFSIGNALED(st)) printf(" child died from SIGSEGV (signal %d): NULL+0x28 read\n" " >>> In kernel: page-fault at 0x28 -> panic during DPM enable <<<\n", WTERMSIG(st)); else printf(" child exited %d (NOT expected)\n", WEXITSTATUS(st)); printf("\n[B] Fixed path (fix.diff: si_dpm_enable guard `if (!boot_ps) return -EINVAL;`)\n"); if (g_boot_ps == NULL) { printf(" rc=-EINVAL (DPM enable refused, no dereference)\n"); } else { printf(" boot_ps non-NULL, normal path\n"); } return 0; } |