Dangling rdev->pm.dpm.dyn_state entries pointers after error-path kfree in r600_parse_extended_power_table β r600_free_extended_power_table at teardown kfrees them again (heap double free; root site shared by all six DPM family callers)
Summary
When a dependency-table parse fails, the error paths kfree the already-parsed tables' entries but never NULL the pointers. Every family caller propagates the error; the unconditional dpm fini at unload/reboot kfrees the same pointers a second time. The shared root site for the six DPM family callers (btc/ni/si/ci/kv/trinity) - family-calibrated with DF-3973 (btc caller-side only)/4023/4117/4125. Attacker model: hostile VBIOS + one induced allocation failure at attach. NULL-each + idempotent-fini fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4371 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| trigger.md | β | 1.9 KB | β raw | |
| fix.diff | β | 3.1 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-4371 β r600_parse_extended_power_table error-path kfree leaves dangling
dyn_state entries pointers β double free in r600_free_extended_power_table
- File:
sys/dev/drm/radeon/r600_dpm.c(:939, :950-951, :962-964 dangling kfree sites; :1299-1315 the second free), triggered from every DPM family caller (btc/ni/si/ci/kv/trinity) and completing at driver teardown. - Severity: Medium β same family/calibration as DF-3973/DF-4023/DF-4117/DF-4125.
- Class: CWE-416/CWE-459 (double free / dangling pointer on error path).
The bug
r600_parse_clk_voltage_dep_table() returns error only when its kzalloc
fails (r600_dpm.c:829-831). When the second/third/fourth table's parse
hits that failure, the error paths of r600_parse_extended_power_table()
free the already-parsed tables' entries but leave the pointers dangling:
/* r600_dpm.c:936-941 (vddci-on-mclk failure frees vddc-on-sclk) */
if (ret) {
kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries);
return ret; /* pointer NOT set to NULL */
}
(same shape at :949-953 freeing two pointers, and :961-966 freeing three).
Every caller (e.g. btc_dpm.c:2577-2579, ni_dpm.c:4075-4077,
si_dpm.c:6953-6955, kv_dpm.c:2731-2733, trinity_dpm.c:2001-2003,
ci_dpm.c:5776-5779) propagates the error; dpm init fails. At unload or
reboot radeon_pm_fini() calls radeon_dpm_fini() unconditionally
(radeon_pm.c:1705 β no dpm_enabled guard), dispatching to e.g.
btc_dpm_fini() (btc_dpm.c:2727) β r600_free_extended_power_table()
β kfree() of the same, already-freed pointers β heap double free.
Trigger (needs AMD hardware; documented, not runnable on audit VM)
- radeon GPU (BTC/NI/SI/CI/KV/Trinity family) whose VBIOS carries a pplib4+
PowerPlayInfo table with
usVddcDependencyOnSCLKOffset != 0andusVddciDependencyOnMCLKOffset != 0. - Force the second parse's kzalloc to fail (kernel slab fault injection β
failslabon thedrmuma/slab, or genuine memory pressure at attach). - Device attach fails;
dpm_enabled = false. - Unload/reboot β double free of
vddc_dependency_on_sclk.entries(or the 2/3-pointer variants).
Artifacts
trigger.mdβ the exact call chain and a fault-injection runbookfix.diffβ NULL every pointer after kfree (both in the error paths and inr600_free_extended_power_table, making the teardown free idempotent); verifiedpatch -p1 --dry-runAPPLIES_CLEANVERDICT.mdβ full analysis
DF-4371 VERDICT β dangling dyn_state entries β double free (r600_dpm.c error paths)
Bottom line
Certain by static proof; not reproduced at runtime β the audit VM has no
radeon GPU, and the trigger additionally requires an induced allocation
failure during VBIOS parsing. Same defect family as DF-3973 (caller-side
btc instance), DF-4023/DF-4117/DF-4125 (ps pointer); this is the root
site inside r600_dpm.c shared by all six family callers.
Why the double free is real (path:line)
r600_dpm.c:927-931βvddc_dependency_on_sclkparsed OK (entries= live kzalloc'd pointer).r600_dpm.c:936-941β thevddci_dependency_on_mclkparse fails (its internalkzallocatr600_dpm.c:829returned NULL β-ENOMEM):c if (ret) { kfree(rdev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries); return ret; }vddc_dependency_on_sclk.entriesis now dangling; itscountmay also be stale from a previous partially-parsed attempt. Two-pointer variant atr600_dpm.c:949-953(vddc-on-mclk failure), three- pointer variant atr600_dpm.c:961-966(mvdd failure).- Caller propagates: e.g.
btc_dpm.c:2577-2579ret = r600_parse_extended_power_table(rdev); if (ret) return ret;(identical:ni_dpm.c:4075,si_dpm.c:6953,kv_dpm.c:2731,trinity_dpm.c:2001,ci_dpm.c:5776-5779). radeon_pm_initdpm path fails βrdev->pm.dpm_enabled = false(radeon_pm.c:1465,1470). Device init failure is not fatal for the driver session; teardown always follows at unload/reboot.radeon_pm.c:1705βradeon_dpm_fini(rdev);runs unconditionally (nodpm_enabledcheck; line 1705 sits outside the `if (rdev-pm.dpm_enabled)` block that ends at 1704).
- β
btc_dpm_fini()(btc_dpm.c:2727) /ni_dpm_fini(ni_dpm.c:4276) /si_dpm_fini(si_dpm.c:7088) /kv_dpm_fini(kv_dpm.c:2870) /trinity_dpm_fini(trinity_dpm.c:2091) /ci_dpm.c:5713βr600_free_extended_power_table()(r600_dpm.c:1299-1315) βkfree(dyn_state->vddc_dependency_on_sclk.entries);β second free of the same pointer.
DragonFly kfree() of an already-freed slab object is a double free /
free-list corruption (uma zone poisoning may catch it as a panic; on a
groomed heap it is a classic UAF-style corruption primitive). Precondition:
attacker controls the VBIOS (hostile card / modified ROM / VFCT override via
radeon_get_bios ACPI path radeon_bios.c:717) and can force one
kzalloc failure at attach (failslab injection or real memory pressure).
Verification attempts on the audit VM
- Guest: DragonFly 6.5-DEVELOPMENT x86_64, no GPU (
pciconf: only QEMU virtio VGA 0x1111:1234), no radeon module loadable, no/dev/dri. - The radeon DPM attach path cannot run at all β the parse/failure/fini chain is unreachable at runtime on this VM.
- Static verification only, therefore status not_reproduced, impact
none(on this guest). Confidence in the code defect itself: certain β every hop above is a direct citation.
Severity calibration
Identical trigger model and impact class as DF-3973 / DF-4023 / DF-4117 /
DF-4125 (all Medium, CVSS:3.1/AV:P/AC:H/PR:L, CWE-416, memcorrupt bucket).
Kept consistent: Medium, CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H.
Fix
fix.diff: (a) NULL each pointer immediately after the error-path kfree
(sites :939, :950-951, :962-964); (b) make r600_free_extended_power_table()
idempotent by NULLing every pointer it frees β this also protects against the
caller-side free-then-fini pattern (DF-3973's second path) reaching the same
double free. Verified patch -p1 --dry-run β APPLIES_CLEAN on a scratch
copy. Runtime fix validation impossible on this VM (no radeon hardware):
fix_status: not_testable.
kernel_refs
- sys/dev/drm/radeon/r600_dpm.c:939,950,951,962,963,964 (dangling kfrees)
- sys/dev/drm/radeon/r600_dpm.c:829-831 (the only failure source)
- sys/dev/drm/radeon/r600_dpm.c:1299-1315 (second free)
- sys/dev/drm/radeon/radeon_pm.c:1705 (unconditional dpm_fini)
- sys/dev/drm/radeon/btc_dpm.c:2577-2579,2727 (caller chain instance)
- sys/dev/drm/radeon/ni_dpm.c:4075,4276; si_dpm.c:6953,7088; kv_dpm.c:2731,2870; trinity_dpm.c:2001,2091; ci_dpm.c:5776,5713
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.
DF-4371 trigger (documentation β requires radeon hardware)
Runtime reproduction requires (1) an radeon DPM-family GPU whose VBIOS has a
pplib4+ PowerPlayInfo table with both usVddcDependencyOnSCLKOffset and
usVddciDependencyOnMCLKOffset set, and (2) one forced allocation failure
inside the second/third/fourth r600_parse_clk_voltage_dep_table() call.
Call chain
radeon_device_init β radeon_pm_init β radeon_dpm_init β <asic>_dpm_init
β r600_get_platform_caps (r600_dpm.c:846)
β <asic>_parse_power_table
β r600_parse_extended_power_table (r600_dpm.c:874)
parse vddc_on_sclk OK (:927-931) entries allocated
parse vddci_on_mclk FAILS (:932-941) kzalloc NULL at :829
kfree(vddc_on_sclk.entries) (:939) pointer dangling
return -ENOMEM
<asic>_dpm_init returns error (btc_dpm.c:2578 et al.)
radeon_pm_init: dpm_enabled = false (radeon_pm.c:1470)
... driver unbind / reboot ...
radeon_pm_fini β radeon_dpm_fini (radeon_pm.c:1705, unconditional)
β <asic>_dpm_fini β r600_free_extended_power_table (r600_dpm.c:1299)
kfree(vddc_on_sclk.entries) AGAIN <-- double free
Fault-injection runbook (on suitable hardware)
- Boot a kernel with slab fault injection available (DragonFly: debug
sysctls / DEBUGFLAGS, or patch
kzalloccall at r600_dpm.c:829 to fail on the 2nd invocation), or attach the card under heavy memory pressure. - Load radeon with dpm=1 (default). Observe
radeon: dpm initialization failedin dmesg with the device still present. kldunload radeon(or reboot). Observe the double-free: DragonFly's malloc flags withM_WAITOK+ slab invariant checks panic on free-list corruption; without redzones the corruption is silent β check for subsequent unrelated panics/allocator reports.- Expected on patched kernel (fix.diff): clean unload, no second free (pointers NULLed).
Fix verification
not_testablefix.diff authored post-verification; applies cleanly (patch -p1 --dry-run) to a scratch copy of r600_dpm.c; runtime before/after validation impossible on this VM (no radeon hardware)
findings/poc/DF-4371/fix.diff; dry-run output 'checking file sys/dev/drm/radeon/r600_dpm.c / DF4371_APPLIES_CLEAN'
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (path:line proof of both frees and the unconditional fini); trigger.md (fault-injection runbook); fix.diff verified APPLIES_CLEAN (patch -p1 --dry-run, scratch copy)
PoC changes
No seed present; trigger documented as a runbook (hardware + fault injection required β no standalone userspace PoC can reach kernel VBIOS parsing)
Verified recommended fix
NULL every freed dyn_state pointer in the error paths and make r600_free_extended_power_table() idempotent by NULLing after each kfree (see fix.diff)
Verdict
Certain by static proof, not reproduced at runtime: the audit VM has no radeon GPU (only QEMU virtio VGA) so the radeon DPM attach path cannot execute, and the trigger additionally needs an induced kzalloc failure during VBIOS parse. The error paths of r600_parse_extended_power_table (r600_dpm.c:939/950-951/962-964) kfree already-parsed dyn_state entries and leave the pointers dangling; every family caller propagates the error; radeon_pm_fini calls radeon_dpm_fini unconditionally (radeon_pm.c:1705) and
No comments yet.