β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4188

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4188 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative incl. warm-reset survival path
↓ download 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_ps still 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

  1. harness_fixed 4188 (-DFIX_DF4188: free ps_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.
  2. fix.diff applied with the other three sumo fixes on the guest /usr/src (fix_combined.diff, see radeon_fix_apply.log hunk 3): full radeon.ko module 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

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

fixed
baseline reproduced→ patch + rebuild →patched clean

harness_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
↓ fix.diffper-fix-DF-4188

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.