Dangling rdev->pm.dpm.ps after kfree-without-NULL on the ENOMEM path of sumo_parse_power_table + swallowed init error -> double free in sumo_dpm_fini at unload/reboot; ps_priv[0..i-1] leaked β sumo twin of DF-4117/4144/4125/4023/3973/3755/3955
Summary
The parse frees dpm.ps on a kzalloc failure without NULLing the pointer; sumo_dpm_init propagates but radeon_pm.c:1448 discards the error (DF-4162), so the driver loads with a dangling dpm.ps, num_ps==0, and ps_priv leaked. On unload/reboot sumo_dpm_fini kfrees the freed pointer again - heap double free of a 96/128-byte block (freelist corruption; classic exploitable primitive when reallocated in between). The machine survives to unload on warm reset (enable early-returns instead of hitting the DF-4187 NULL deref). Harness-proven with canary allocator (DOUBLE FREE detected, exit 98; fixed clean). Cleanup+NULL fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4188 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | verbatim replica; mode 4188 = ENOMEM at sumo_ps#1 -> dangling dpm.ps -> fini double free (canary-detected) | 34.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall; harness_fixed with -DFIX_ALL | 65 B | view raw |
| run.sh | run-script | all modes, baseline + fixed | 120 B | view raw |
| build.log | build-log | final successful builds (guest cc 8.3) | 1018 B | view raw |
| run.log | run-log | decisive run: baseline 99/98/99/99, fixed 0/0/0/0 | 3.5 KB | view raw |
| fix.diff | suggested-fix | free ps_priv[0..i-1], kfree array, NULL the pointer on the ENOMEM path | 598 B | view raw |
| radeon_fix_apply.log | apply-log | patch(1) hunks on guest /usr/src (hunk 3 = this fix) | 808 B | view raw |
| radeon_module_build.log | fix-build-log | radeon.ko build with all four fixes, -Werror, MAKE_RC=0 | 139.5 KB | view raw |
| env.txt | environment | guest uname, cc, no radeon loaded | 568 B | view raw |
| VERDICT.md | verdict | full narrative incl. warm-reset survival path | 4.6 KB | β raw |
DF-4188 β VERDICT
Status: REPRODUCED (logic-level harness; kernel-level repro hardware-gated) Impact: kernel heap double free (freelist corruption β panic / potential UAF) Confidence: certain
Root cause
sumo_parse_power_table() (sys/dev/drm/radeon/sumo_dpm.c:1455-1528) frees
the power-state array on an allocation failure without clearing the pointer:
ps = kzalloc(sizeof(struct sumo_ps), GFP_KERNEL); /* :1500 */
if (ps == NULL) {
kfree(rdev->pm.dpm.ps); /* :1502 β dangling! */
return -ENOMEM; /* :1503 */
}
rdev->pm.dpm.ps now points at freed memory. sumo_dpm_init() propagates the
error (:1783-1785), but radeon_pm.c:1448 discards it (the DF-4162 hub
bug), so the driver loads with:
rdev->pm.dpm.psβ freed block (dangling),rdev->pm.dpm.num_psstill 0 (only set on success,:1526),ps_priv[0..i-1]leaked.
On unload/reboot, radeon_pm_fini() (radeon_pm.c:1721-1727, still
PM_METHOD_DPM β the method is never reset on the swallowed failure) β
radeon_pm_fini_dpm() (:1705) β sumo_dpm_fini():
for (i = 0; i < rdev->pm.dpm.num_ps; i++) /* :1876 β 0 iters */
kfree(rdev->pm.dpm.ps[i].ps_priv);
kfree(rdev->pm.dpm.ps); /* :1879 β DOUBLE FREE */
A second kfree of an already-freed kmalloc block corrupts the kernel heap
freelist. The intervening radeon_dpm_enable() normally panics first through
the DF-4187 NULL-boot_ps path, except when the hardware already reports
DPM_SCLK_ENABLE (warm reset): then sumo_dpm_enable() early-returns -EINVAL
at :1209-1210, no crash, and the machine runs until unload/reboot β exactly
the DF-4117 (cypress) scenario "double free at unload/reboot".
Secondary leak on the same error ladder: the -EINVAL return at :1498-1499
leaves dpm.ps and all ps_priv states allocated (no double free there, just
leaks); the ENOMEM path additionally leaks ps_priv[0..i-1].
Trigger / threat model
kzalloc failure for a ~128-byte struct sumo_ps at exactly state N β₯ 1 while
states 0..N-1 already parsed β i.e., transient kernel memory exhaustion during
radeon driver load on a Sumo/Ontario/Palm APU (memory pressure, coredumps,
fork bombs racing module load). No VBIOS crafting required (any table with β₯ 2
states), though a crafted multi-state table widens the window. Combined with
the DF-4162 swallow: driver keeps running; reboot/kldunload β heap corruption
β panic or worse (double free of a 96/128-byte block is a classic exploitable
primitive if the block got reallocated in between β reallocation is likely
given the driver continues to allocate).
Harness proof (mode 4188)
Verbatim replica of sumo_parse_power_table + sumo_dpm_fini; canary
allocator replaces the kernel heap; the Nth kzalloc is forced to fail exactly
like GFP pressure would:
[sumo_ps#1] kzalloc(#6, 128) -> INJECTED FAILURE (ENOMEM) sumo_parse_power_table() = -12 rdev->pm.dpm.ps is NOT NULL after the failed parse <- dangling pointer kept sumo_dpm_fini() ... *** CANARY ALLOCATOR: DOUBLE FREE of 0x8004d1d00 (size 96) DETECTED *** exit=98
The kernel performs the identical two kfrees on the identical pointer
(sumo_dpm.c:1502 then :1879); only the allocator differs.
Fix validation
harness_fixed 4188(-DFIX_DF4188: freeps_priv[i-1..0], kfree the array, NULL it): "rdev->pm.dpm.ps is NULL after the failed parse", fini completes,canary_violations = 0, exit 0.fix.diffapplied with the other three sumo fixes on the guest/usr/src(fix_combined.diff, seeradeon_fix_apply.loghunk 3): fullradeon.komodule build under-Werror,MAKE_RC=0(radeon_module_build.log).
Kernel-on-hardware validation not possible on the audit guest (no ATI APU).
Kernel references
- sys/dev/drm/radeon/sumo_dpm.c:1500-1504 (kfree-without-NULL)
- sys/dev/drm/radeon/sumo_dpm.c:1876-1879 (fini double free)
- sys/dev/drm/radeon/sumo_dpm.c:1783-1785 (init propagates)
- sys/dev/drm/radeon/radeon_pm.c:1448 (DF-4162 swallow)
- sys/dev/drm/radeon/radeon_pm.c:1705, :1721-1727 (fini_dpm reachable)
- sys/dev/drm/radeon/sumo_dpm.c:1209-1210 (enable early-return that lets the machine survive to unload β warm-reset scenario)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedharness_fixed 4188: pointer NULLed after cleanup, sumo_dpm_fini clean, exit 0; combined fix builds radeon.ko under -Werror (MAKE_RC=0)
run.log fixed section ('rdev->pm.dpm.ps is NULL after the failed parse', canary_violations = 0); radeon_fix_apply.log hunk 3; radeon_module_build.log MAKE_RC=0
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log '===== mode 4188 =====': 'DOUBLE FREE of 0x8004d1d00 (size 96) DETECTED', exit=98; fixed section exit=0; radeon_module_build.log MAKE_RC=0
PoC changes
wrote harness from scratch (no seed): verbatim parse/fini replicas + canary allocator with double-free detection + Nth-kzalloc failure injection
Verified recommended fix
on the ENOMEM path free ps_priv[0..i-1], then kfree(dpm.ps) and set rdev->pm.dpm.ps = NULL (see fix.diff); honor dpm.init failure in radeon_pm.c per DF-4162
Verdict
Reproduced at logic level with the verbatim replica (harness mode 4188): kzalloc(struct sumo_ps) failure at state 1 makes sumo_parse_power_table() kfree rdev->pm.dpm.ps at :1502 WITHOUT nulling it; the init error is swallowed by radeon_pm.c:1448 (DF-4162); at unload/reboot sumo_dpm_fini() kfrees the same pointer again at :1879 β the canary allocator detects the deterministic DOUBLE FREE. In the kernel this is heap-freelist corruption on a 96/128-byte block (classic exploitable primitive when the block is reallocated in between; the driver keeps running until unload in the warm-reset scenario where sumo_dpm_enable early-returns at :1209). Kernel-level repro hardware-gated (no ATI APU on guest). Fix proven: harness_fixed completes cleanly (pointer NULLed) and radeon.ko builds with the fix under -Werror.
No comments yet.