DF-4545 / od_null_deref.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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | /* * DF-4545 — vega20 pptable init succeeds with NULL OD arrays * ========================================================== * Lifted-copy harness (DF-1179/DF-4395 convention; the in-kernel path needs * a Vega20 ASIC, absent on the audit guest). * * Faithful replica of: * - check_powerplay_tables (vega20_processpptables.c:636-657) * - init_powerplay_table_information (:802-892): the * `ucODTableRevision == 1` gate (:823) around ALL od_* copies, the five * copy calls whose return values are IGNORED (:835-846, :870-877), the * smc_pptable kmalloc (:880) and memcpy (:884) that unconditionally * succeed, and the `return result` of append_vbios_pptable (:889-891) * which is 0 -> init reports SUCCESS * - the consumer's first deref in vega20_od8_set_feature_capabilities * (vega20_hwmgr.c:906): pptable_information->od_feature_capabilities[0] * * Variant A: crafted VBIOS with OverDrive8Table.ucODTableRevision = 2 * (a perfectly legal value the driver does not recognise). * Variant B: revision 1 but a kzalloc failure inside * copy_overdrive_feature_capabilities_array whose -ENOMEM is * dropped on the floor at :835. * * Both leave hwmgr->pptable->od_feature_capabilities == NULL while * vega20_pp_tables_initialize returns 0; backend_init then runs * vega20_od8_initialize_default_settings (vega20_hwmgr.c:1598) which * dereferences the NULL pointer -> kernel panic. The harness catches the * resulting SIGSEGV and reports the fault address. * * Build: cc -O2 -Wall -o od_null_deref od_null_deref.c * Run: ./od_null_deref */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <signal.h> #include <ucontext.h> #include <unistd.h> #include <errno.h> #include <sys/wait.h> typedef uint8_t UCHAR; typedef uint16_t USHORT; typedef uint32_t ULONG; #define ATOM_VEGA20_TABLE_REVISION_VEGA20 11 #define ATOM_VEGA20_ODFEATURE_COUNT 10 #define PPTABLE_V20_SMU_VERSION 3 #pragma pack(push, 1) struct atom_common_table_header { USHORT structuresize; UCHAR format_revision; UCHAR content_revision; }; typedef struct _ATOM_VEGA20_OVERDRIVE8_RECORD { UCHAR ucODTableRevision; ULONG ODFeatureCount; UCHAR ODFeatureCapabilities[32]; ULONG ODSettingCount; ULONG ODSettingsMax[32]; ULONG ODSettingsMin[32]; } ATOM_VEGA20_OVERDRIVE8_RECORD; typedef struct _ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD { UCHAR ucTableRevision; ULONG PowerSavingClockCount; ULONG PowerSavingClockMax[16]; ULONG PowerSavingClockMin[16]; } ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD; typedef struct _PPTable_t_stub { ULONG Version; UCHAR pad[2044]; } PPTable_t_stub; typedef struct _ATOM_Vega20_POWERPLAYTABLE { struct atom_common_table_header sHeader; UCHAR ucTableRevision; USHORT usTableSize; ULONG ulGoldenPPID; ULONG ulGoldenRevision; USHORT usFormatID; ULONG ulPlatformCaps; UCHAR ucThermalControllerType; USHORT usSmallPowerLimit1; USHORT usSmallPowerLimit2; USHORT usBoostPowerLimit; USHORT usODTurboPowerLimit; USHORT usODPowerSavePowerLimit; USHORT usSoftwareShutdownTemp; ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD PowerSavingClockTable; ATOM_VEGA20_OVERDRIVE8_RECORD OverDrive8Table; USHORT usReserve[5]; PPTable_t_stub smcPPTable; } ATOM_Vega20_POWERPLAYTABLE; #pragma pack(pop) struct phm_ppt_v3_information { uint8_t *od_feature_capabilities; uint32_t *od_settings_max; uint32_t *od_settings_min; }; static int inject_alloc_failure; /* variant B */ static int patched; /* PATCHED control (fix.diff) */ static volatile unsigned long fault_addr; static void sigsegv(int sig, siginfo_t *si, void *uc) { char msg[96]; int len; (void)sig; (void)uc; fault_addr = (unsigned long)si->si_addr; /* async-signal-safe report of the fault address */ len = snprintf(msg, sizeof(msg), " >>> SIGSEGV: fault address 0x%lx (od_feature_capabilities + 0)" "\n", fault_addr); write(1, msg, len); _exit(1); /* 1 = NULL-deref faulted */ } /* copy_overdrive_feature_capabilities_array (:684-712) */ static int copy_overdrive_feature_capabilities_array( struct phm_ppt_v3_information *pi, const UCHAR *pptable_array, uint32_t od_feature_count) { uint8_t *table; if (inject_alloc_failure) return -ENOMEM; /* kzalloc failed */ table = calloc(1, od_feature_count); if (!table) return -ENOMEM; for (uint32_t i = 0; i < od_feature_count; i++) table[i] = pptable_array[i]; pi->od_feature_capabilities = table; return 0; } static uint32_t le32(uint32_t v) { return v; } /* LE host stand-in */ /* check_powerplay_tables (:636-657) */ static int check_powerplay_tables(const ATOM_Vega20_POWERPLAYTABLE *t) { if (t->sHeader.format_revision < ATOM_VEGA20_TABLE_REVISION_VEGA20) { printf(" check: unsupported format_revision\n"); return -1; } if (t->sHeader.structuresize <= 0) { printf(" check: invalid structuresize\n"); return -1; } if (t->smcPPTable.Version != PPTABLE_V20_SMU_VERSION) { printf(" check: PPTable version mismatch\n"); return -1; } printf(" check_powerplay_tables: PASS (format_revision=%u, " "structuresize=%u, smcPPTable.Version=%u)\n", t->sHeader.format_revision, t->sHeader.structuresize, t->smcPPTable.Version); return 0; } /* init_powerplay_table_information (:802-892) */ static int init_powerplay_table_information( struct phm_ppt_v3_information *pi, const ATOM_Vega20_POWERPLAYTABLE *powerplay_table) { uint32_t od_feature_count, od_setting_count; int result; /* :823 */ if (powerplay_table->OverDrive8Table.ucODTableRevision == 1) { od_feature_count = (le32(powerplay_table->OverDrive8Table.ODFeatureCount) > ATOM_VEGA20_ODFEATURE_COUNT) ? ATOM_VEGA20_ODFEATURE_COUNT : le32(powerplay_table->OverDrive8Table.ODFeatureCount); od_setting_count = 14; /* :835 -- return value IGNORED (CWE-252) */ copy_overdrive_feature_capabilities_array(pi, powerplay_table->OverDrive8Table.ODFeatureCapabilities, od_feature_count); /* :839-846 phm_copy_overdrive_settings_limits_array x2, * returns equally ignored */ (void)od_setting_count; } if (patched) { /* fix.diff: PP_ASSERT(od_feature_capabilities != NULL && ...) * -> init fails instead of reporting success */ if (pi->od_feature_capabilities == NULL || pi->od_settings_max == NULL) { printf(" init_powerplay_table_information: PATCHED " "init refuses to succeed (returns -EINVAL)\n"); return -22; /* -EINVAL */ } } /* :880-886 -- smc_pptable alloc+copy always succeed here */ /* :889 */ result = 0; /* append_vbios_pptable OK */ printf(" init_powerplay_table_information: return %d " "(od_feature_capabilities=%p od_settings_max=%p od_settings_min=%p)" "\n", result, (void *)pi->od_feature_capabilities, (void *)pi->od_settings_max, (void *)pi->od_settings_min); return result; /* SUCCESS with NULLs */ } /* vega20_od8_set_feature_capabilities first deref (vega20_hwmgr.c:906) */ static void consumer(struct phm_ppt_v3_information *pi) { /* data->smu_features[GNLD_DPM_GFXCLK].enabled is true on real HW; * the very first array access is od_feature_capabilities[0] */ printf(" consumer: vega20_od8_set_feature_capabilities: reading " "od_feature_capabilities[0] at %p ...\n", (void *)pi->od_feature_capabilities); if (pi->od_feature_capabilities[0]) /* <- faults when NULL */ printf(" cap[0] set\n"); } static struct sigaction sa; static int run_case(const char *name, UCHAR od_rev, int alloc_fail, int expect_fault) { ATOM_Vega20_POWERPLAYTABLE *t; struct phm_ppt_v3_information pi = { NULL, NULL, NULL }; int pid, status, ret; printf("=== %s (ucODTableRevision=%u, inject kzalloc failure=%d) ===\n", name, od_rev, alloc_fail); t = calloc(1, sizeof(*t)); t->sHeader.structuresize = sizeof(*t); t->sHeader.format_revision = ATOM_VEGA20_TABLE_REVISION_VEGA20; t->OverDrive8Table.ucODTableRevision = od_rev; t->OverDrive8Table.ODFeatureCount = ATOM_VEGA20_ODFEATURE_COUNT; t->OverDrive8Table.ODSettingCount = 14; memset(t->OverDrive8Table.ODFeatureCapabilities, 1, 32); t->smcPPTable.Version = PPTABLE_V20_SMU_VERSION; inject_alloc_failure = alloc_fail; patched = (strstr(name, "PATCHED") != NULL); if ((pid = fork()) < 0) { perror("fork"); exit(2); } if (pid == 0) { /* child runs the flow */ sigaction(SIGSEGV, &sa, NULL); if (check_powerplay_tables(t) != 0) _exit(10); if (init_powerplay_table_information(&pi, t) != 0) _exit(11); printf(" pptable_init returned SUCCESS, running " "backend_init consumer ...\n"); consumer(&pi); _exit(0); /* clean completion */ } waitpid(pid, &status, 0); ret = WEXITSTATUS(status); if (ret == 1) { printf(" >>> consumer NULL-DEREF confirmed (see SIGSEGV line " "above)\n"); } else if (ret == 0) { printf(" >>> consumer completed without fault\n"); } else { printf(" >>> flow bailed early (exit %d)\n", ret); } printf(" %s\n\n", ret == 1 ? "PANIC-CLASS FAULT REPRODUCED" : (expect_fault ? "NOT REPRODUCED" : "OK (no fault, as expected)")); free(t); return ret == 1; } int main(void) { memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sigsegv; sa.sa_flags = SA_SIGINFO; sigfillset(&sa.sa_mask); printf("DF-4545 lifted-copy harness: vega20 pptable init succeeds with " "NULL OD arrays -> vega20_hwmgr.c:906 NULL deref\n\n"); /* control: revision 1, allocations succeed -> clean */ run_case("CONTROL rev=1 healthy alloc", 1, 0, 0); /* variant A: crafted table, revision != 1 -> arrays never copied */ int a = run_case("VARIANT A crafted ucODTableRevision=2", 2, 0, 1); /* variant B: rev=1 but the :835 kzalloc fails, -ENOMEM ignored */ int b = run_case("VARIANT B rev=1 ignored -ENOMEM", 1, 1, 1); /* patched: same crafted input, init now refuses -> consumer never runs */ int p = run_case("PATCHED crafted rev=2", 2, 0, 0); patched = 0; (void)p; printf("%s: init returns success with NULL od arrays%s\n", (a || b) ? "REPRODUCED" : "NOT REPRODUCED", (a && b) ? " (both the revision gate and the dropped -ENOMEM)" : ""); return (a || b) ? 0 : 1; } |