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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3973 Β· 5 files
FileTypeDescriptionSize
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)

  1. rv7xx_parse_power_table() β€” sys/dev/drm/radeon/rv770_dpm.c:2289-2329: - allocates rdev->pm.dpm.ps (2289); - on per-state ps_priv kzalloc failure (2306-2310) it does kfree(rdev->pm.dpm.ps); return -ENOMEM; without NULLing the pointer and leaks all previously allocated ps_priv objects. num_ps is only set at 2327, so it stays 0 while dpm.ps dangles.
  2. btc_dpm_init() β€” sys/dev/drm/radeon/btc_dpm.c:2574-2576 β€” propagates the error.
  3. radeon_pm_init_dpm() β€” sys/dev/drm/radeon/radeon_pm.c:1448 β€” ignores radeon_dpm_init()'s return and continues to radeon_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.
  4. 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.
  5. Second error path: btc_dpm_init at 2583-2586, when the vddc_dependency_on_dispclk.entries kzalloc fails, calls r600_free_extended_power_table(rdev) and returns; r600_free_extended_power_table (r600_dpm.c:1299-1315) kfrees 13 dyn_state pointers without NULLing any of them. Teardown then calls it again from btc_dpm_fini (2727) β†’ double free of every allocated extended-power-table object. eg_pi also 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.md
↓ download raw

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_testable

fix.diff authored (NULL-after-free at both sites + error propagation); not build-testable without a Barts-class GPU.

findings/poc/DF-3973/fix.diff
↓ fix.diffper-fix-DF-3973

Confirmed kernel references

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.