DF-3955 / trigger_df3955.c
/* * DF-3955 — radeon kv_dpm.c double-free of rdev->pm.dpm.ps on the * kzalloc-failure path of kv_parse_power_table() * ===================================================================== * Target: sys/dev/drm/radeon/kv_dpm.c (pass 2) * * kv_parse_power_table() kv_dpm.c:2673-2677: * ps = kzalloc(sizeof(struct kv_ps), GFP_KERNEL); * if (ps == NULL) { * kfree(rdev->pm.dpm.ps); <-- freed, pointer NOT NULLed * return -ENOMEM; <-- num_ps (set only at :2700) * } stays 0; ps_priv of states * 0..i-1 LEAK (num_ps == 0) * * kv_dpm_init() propagates the error (:2787-2789), but radeon_pm_init_dpm() * radeon_pm.c:1448 DISCARDS kv_dpm_init()'s return value, so the device lives * on with a dangling rdev->pm.dpm.ps until driver detach/suspend: * radeon_pm_fini -> radeon_pm_fini_dpm (radeon_pm.c:1705) -> radeon_dpm_fini * -> kv_dpm_fini() kv_dpm.c:2861-2871: * for (i = 0; i < rdev->pm.dpm.num_ps; i++) // num_ps == 0: leaks * kfree(rdev->pm.dpm.ps[i].ps_priv); * kfree(rdev->pm.dpm.ps); // SECOND free => heap * // corruption * * Kernel impact: double free of a ~sizeof(struct radeon_ps)*ucNumEntries * block (kmalloc bucket), potentially adjacent to other radeon objects. * Trigger window: kzalloc failure during boot-time VBIOS power-table parse * on KV/BT/APU hardware (memory-pressure gated; rare by nature). * * This harness replays the identical alloc/free SEQUENCE in userland; the C * library's double-free detection standing in for the kernel heap's * (uma/kmem) corruption. Arithmetic/sequence demonstration, NOT a kernel * reproduction. * * Build: cc -O2 -Wall -o trigger_df3955 trigger_df3955.c * Run: ./trigger_df3955 / ./trigger_df3955 crash */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> struct radeon_ps { unsigned long pad[6]; void *ps_priv; }; struct kv_ps { unsigned long pad[8]; }; int main(int argc, char **argv) { int crash = (argc > 1 && !strcmp(argv[1], "crash")); printf("DF-3955 radeon kv_dpm double-free on parse-OOM path (simulation)\n"); printf("kv_parse_power_table :2673-2677 kfree(dpm.ps), no NULL, num_ps stays 0\n"); printf("kv_dpm_fini :2868 kfree(dpm.ps) AGAIN + ps_priv leak\n"); printf("gate: kzalloc(sizeof(struct kv_ps)) fails mid-loop (boot, KV APU)\n"); /* Replay: 3-state table; the kzalloc for state #2's ps_priv is forced to * fail exactly like the GFP_KERNEL allocation under memory pressure. */ const int num_entries = 3; const int fail_at = 2; struct radeon_ps *dpm_ps = calloc(num_entries, sizeof(*dpm_ps)); if (!dpm_ps) { perror("calloc"); return 1; } int i = 0; for (i = 0; i < num_entries; i++) { struct kv_ps *ps; if (i == fail_at) { ps = NULL; /* injected kzalloc failure */ } else { ps = malloc(sizeof(struct kv_ps)); } if (!ps) { /* verbatim error path */ printf("state %d: kzalloc(kv_ps) failed -> kfree(dpm.ps); return -ENOMEM\n", i); free(dpm_ps); /* dangling on purpose */ break; } dpm_ps[i].ps_priv = ps; } if (i == num_entries) { printf("setup deviation: allocation unexpectedly succeeded\n"); return 1; } printf("dangling dpm_ps = %p ; leaked ps_priv count = %d ; num_ps = 0\n", (void *)dpm_ps, i); if (!crash) { printf("NON-CRASH MODE: stopping before the second free " "(leaked %d kv_ps objects on purpose)\n", i); return 0; } /* kv_dpm_fini(): num_ps==0 so the ps_priv loop is a no-op (leak), then */ free(dpm_ps); /* <-- the double free */ /* DragonFly userland malloc does NOT abort here (no glibc-style check) * and neither does the kernel heap: kmalloc/kmem would silently corrupt * its free list / adjacent objects. Demonstrates the sequence executes * to completion with a dangling pointer handed to the allocator twice. */ printf("SECOND FREE COMPLETED SILENTLY (userland no-abort; kernel: silent heap corruption)\n"); return 0; } |