Ignored btc_dpm_init failure leaves dangling rdev->pm.dpm.ps and un-NULLed dyn_state pointers: double free in btc_dpm_fini (two paths) plus eg_pi/ps_priv leaks β btc twin of DF-3645/3755/3955
Summary
On per-state ps_priv kzalloc failure rv7xx_parse_power_table kfrees dpm.ps without NULLing it; the ignored error keeps the device alive; teardown runs btc_dpm_fini which kfrees the dangling ps again. On the dispclk-entries alloc-failure path the 13 extended-power-table pointers are never NULLed and fini frees them all a second time. Heap double free / UAF at driver teardown on a groomable attach allocation failure.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3973 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| verdict.json | β | 2.6 KB | view raw | |
| manifest.json | β | 878 B | view raw | |
| fix.diff | β | 1.8 KB | view raw |
DF-3973 β btc_dpm_init failure ignored: dangling rdev->pm.dpm.ps double-free in btc_dpm_fini (+ r600_free_extended_power_table double free, ps_priv/eg_pi leaks)
- File:
sys/dev/drm/radeon/btc_dpm.c(pass 2), cross-file sinks rv770_dpm.c / r600_dpm.c / radeon_pm.c - Class: CWE-416 use-after-free / double free (Medium)
- Family: DF-3645 (ci) / DF-3755 (ni) / DF-3955 (kv) β the btc twin, checklist item (b).
Build / Run / Expected
Hardware-gated (Barts/Turks/Caicos). Trigger requires a memory-allocation failure at a specific kzalloc during VBIOS power-table parse, achievable under kernel heap pressure (spray kmalloc-64/128 until allocation fails at the right site). No PoC without the card.
Root cause (source-level)
rv7xx_parse_power_table()βsys/dev/drm/radeon/rv770_dpm.c:2289-2329: - allocatesrdev->pm.dpm.ps(2289); - on per-stateps_privkzalloc failure (2306-2310) it doeskfree(rdev->pm.dpm.ps); return -ENOMEM;without NULLing the pointer and leaks all previously allocatedps_privobjects.num_psis only set at 2327, so it stays 0 whiledpm.psdangles.btc_dpm_init()βsys/dev/drm/radeon/btc_dpm.c:2574-2576β propagates the error.radeon_pm_init_dpm()βsys/dev/drm/radeon/radeon_pm.c:1448β ignoresradeon_dpm_init()'s return and continues toradeon_dpm_setup_asic/radeon_dpm_enable. boot_ps may already point at the freed array (if a BOOT state was parsed before the failing state) β UAF read during enable.- On driver teardown,
radeon_pm_fini_dpm()βradeon_dpm_fini()(radeon_pm.c:1705, unconditional) βbtc_dpm_fini()βbtc_dpm.c:2721-2724: -for (i = 0; i < num_ps; i++) kfree(ps[i].ps_priv);(0 iterations, num_ps==0) -kfree(rdev->pm.dpm.ps);β double free of the array freed at rv770_dpm.c:2308. If the slab object was reused in between, this frees a live foreign object. - Second error path:
btc_dpm_initat 2583-2586, when thevddc_dependency_on_dispclk.entrieskzalloc fails, callsr600_free_extended_power_table(rdev)and returns;r600_free_extended_power_table(r600_dpm.c:1299-1315) kfrees 13dyn_statepointers without NULLing any of them. Teardown then calls it again frombtc_dpm_fini(2727) β double free of every allocated extended-power-table object.eg_pialso leaks on every early error return (2559 sets priv; no free on 2570-2586).
Expected manifestation
Heap corruption (double free) at driver unload/detach, or UAF reads during the ignored-error enable run. Same class and reachability as DF-3755 (rated Medium, C:H/I:H/A:H).
VERDICT β DF-3973
status: inconclusive (hardware-gated; source-level certainty: likely β requires an alloc failure at the exact kzalloc site, which is attacker-influenceable via heap pressure but probabilistic)
Trace (proof of the code shape)
Path A β dangling ps array:
- rv770_dpm.c:2289 rdev->pm.dpm.ps = kzalloc(...)
- rv770_dpm.c:2306-2310 per-state ps = kzalloc(sizeof(struct rv7xx_ps)); on failure:
kfree(rdev->pm.dpm.ps); return -ENOMEM; β pointer left dangling, earlier ps_priv leaked,
num_ps (2327) never assigned β remains 0.
- btc_dpm.c:2574-2576 ret = rv7xx_parse_power_table(rdev); if (ret) return ret;
- radeon_pm.c:1448 radeon_dpm_init(rdev); β return value discarded; 1452-1453 setup+enable run
regardless. If a BOOT-classified state was parsed before the failure, boot_ps points INTO the freed
array (UAF reads through btc_init_smc_table at btc_dpm.c:2446); if not, boot_ps is NULL β DF-3972's
panic instead.
- radeon_pm.c:1705 radeon_dpm_fini unconditional at teardown β btc_dpm.c:2724 kfree(rdev->pm.dpm.ps)
β second free of the same object (num_ps==0 so the ps_priv loop is skipped).
Path B β extended-power-table double free:
- btc_dpm.c:2581-2586: vddc_dependency_on_dispclk.entries kzalloc failure β
r600_free_extended_power_table(rdev); return -ENOMEM;
- r600_dpm.c:1299-1315: 13 kfree()s, no pointer NULLing.
- Ignored error (radeon_pm.c:1448) β teardown β btc_dpm.c:2727 calls it again β double free of every
dyn_state pointer that was non-NULL at the first call (vddc/vddci/mvdd dependency entries, cac
tables, ppm, cac_tdp, vce/uvd tables).
Attacker influence: allocation failure timing via heap grooming (the kzalloc sizes are small, kmalloc-32/64/256; a local unprivileged user can spray to force failure exactly once during drm attach β the parse runs at boot/attach, so the practical window is early-boot pressure or driver reload via kldunload/kldload on a system where the attacker can trigger module reload; on DFly the drm driver is typically compiled in, so the realistic trigger is boot-time pressure or a card hotplug event on a system so configured). Physical/proximity-gated: Medium with AC:H, consistent with DF-3755's rating.
Why not reproduced
Guest has no radeon GPU (env.txt) β neither the parse path nor the alloc-failure window can be entered. Recorded inconclusive/likely per the DF-3645/3755/3955 family convention.
Fix validation
not_testable (no hardware). fix.diff: NULL the pointers after every free on error paths and honor btc_dpm_init's return in radeon_pm_init_dpm (shared with DF-3972's half of the diff).
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
not_testablefix.diff authored (NULL-after-free at both sites + error propagation); not build-testable without a Barts-class GPU.
findings/poc/DF-3973/fix.diff
Confirmed kernel references
- sys/dev/drm/radeon/btc_dpm.c:2574
- sys/dev/drm/radeon/btc_dpm.c:2584
- sys/dev/drm/radeon/btc_dpm.c:2721
- sys/dev/drm/radeon/btc_dpm.c:2724
- sys/dev/drm/radeon/btc_dpm.c:2727
- sys/dev/drm/radeon/rv770_dpm.c:2308
- sys/dev/drm/radeon/r600_dpm.c:1303
- sys/dev/drm/radeon/radeon_pm.c:1448
- sys/dev/drm/radeon/radeon_pm.c:1705
Detail
Evidence (decisive lines)
VERDICT.md (two double-free traces + leak inventory); fix.diff (NULL-after-free + error propagation)
PoC changes
No PoC: hardware-gated + requires timed kzalloc failure during attach (heap-pressure window).
Verified recommended fix
NULL every freed pointer in r600_free_extended_power_table and in rv7xx_parse_power_table's error path; honor radeon_dpm_init's return in radeon_pm_init_dpm.
Verdict
btc_dpm_init's failure is ignored by radeon_pm_init_dpm (radeon_pm.c:1448); two error paths then leave dangling pointers that btc_dpm_fini frees a second time: (1) rv7xx_parse_power_table's per-state kzalloc failure kfrees rdev->pm.dpm.ps without NULLing (rv770_dpm.c:2308) while radeon_dpm_enable may still read through boot_ps into the freed array, and btc_dpm_fini (btc_dpm.c:2724) kfrees it again; (2) the vddc_dependency_on_dispclk alloc-failure path (btc_dpm.c:2584) calls r600_free_extended_power_table, whose 13 un-NULLed pointers (r600_dpm.c:1303-1314) are freed a second time at teardown (btc_dpm.c:2727). eg_pi and already-parsed ps_priv objects also leak on every early error return. Hardware-gated (no radeon GPU in the guest), alloc-failure-window gated (AC:H): same class/rating family as DF-3755/DF-3645.
No comments yet.