NULL rdev->uvd_fw dereference in uvd_v4_2_resume via the unguarded KV DPM ungate path (kv_dpm.c:1628) β kernel panic on suspend/pm-fini of a DPM-enabled CIK APU without UVD firmware
Summary
uvd_v4_2.c:51 reads rdev->uvd_fw->datasize with no guard; the cik_uvd_start caller is guarded (has_uvd/vcpu_bo) but kv_dpm_powergate_uvd(rdev,false) calls it directly with none. On KV/CIK APUs with SMC fw but no UVD fw kmods (natural on DFly - all radeon fw is separate opt-in kmods), every suspend (radeon_pm_suspend runs BEFORE the has_uvd guard) or radeonkms unload panics. REPRODUCED on guest: production uvd_v4_2_resume from freshly built radeon.ko -> Fatal trap 12 fault va=0x10 at uvd_v4_2_resume+0x30, twice (panic.txt, panic.2.txt); one-line guard applied in guest /usr/src + rebuilt radeon.ko -> identical trigger survives returning -EINVAL (run.fixed.log); guest recovered clean. Physical-user lid-close or root unload; DoS ceiling only.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5230 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 6.5 KB | β raw | |
| df5230.c | β | 3.4 KB | view raw | |
| Makefile | β | 444 B | β download | |
| build.sh | β | 125 B | view raw | |
| run.sh | β | 139 B | view raw | |
| panic.txt | β | 1.9 KB | view raw | |
| panic.2.txt | β | 1.0 KB | view raw | |
| run.baseline.log | β | 0 B | β download | |
| run.baseline2.log | β | 7 B | view raw | |
| run.fixed.log | β | 405 B | view raw | |
| fix_run.log | β | 405 B | view raw | |
| fix_build.log | β | 955 B | view raw | |
| load_modules.log | β | 354 B | view raw | |
| env.txt | β | 165 B | view raw | |
| fix.diff | β | 695 B | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-5230 β NULL rdev->uvd_fw dereference in uvd_v4_2_resume() via the KV DPM ungate path β kernel panic
File: sys/dev/drm/radeon/uvd_v4_2.c:51 (trigger chain via sys/dev/drm/radeon/kv_dpm.c:1628)
Severity: Low (hardware+firmware-config gated local DoS) Β· Bucket: base:dev
Status: reproduced (panic) on the QEMU guest with a synthetic-state harness calling the production function; the production trigger chain is proven statically link-by-link below.
What to run
./build.sh # on the guest: build drm.ko, radeon.ko (baseline), df5230.ko ./run.sh # kldload drm.ko radeon.ko df5230.ko -> instant panic
Expected (serial console / vm.sh log):
DF5230: pre uvd_fw=0 fw_header_present=0 gpu_addr=0x0 max_handles=10 DF5230: calling production uvd_v4_2_resume() (radeon.ko) -- expect panic at uvd_v4_2.c:51 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x10 Stopped at uvd_v4_2_resume+0x30: movq 0x10(%rdx),%rdx
0x10 is offsetof(struct firmware, datasize) (sys/sys/firmware.h:51-56:
name 8B + data 8B + datasize) β the read at uvd_v4_2.c:51
(rdev->uvd_fw->datasize) with uvd_fw == NULL. The fault happens before
the first WREG32 (uvd_v4_2.c:52), so no MMIO is touched.
Fix validation (same trigger, one-line guard)
vm.sh reset with-src(clean source)patch -p1 < fix.diffin the guest's/usr/src, rebuildradeon.ko- identical
kldload df5230.koβ
DF5230: pre uvd_fw=0 fw_header_present=0 gpu_addr=0x0 max_handles=10 DF5230: calling production uvd_v4_2_resume() (radeon.ko) -- expect panic at uvd_v4_2.c:51 DF5230: survived, uvd_v4_2_resume returned -22
Guest stays up; kldload succeeds. Baseline panicked twice (once on the
original spawn, once from a fresh vm.sh reset with source reverted),
identical signature both times (run.baseline.log, panic.txt,
run.baseline2.log, panic.2.txt).
Files
df5230.c,Makefileβ the harness kld (zeroedstruct radeon_device= the exact post-failed-radeon_uvd_init()state; calls the productionuvd_v4_2_resumecompiled intoradeon.ko)build.sh,run.shβ exact commandspanic.txt,panic.2.txtβ serial-console panic transcripts (2 runs)run.baseline.log,run.baseline2.log,run.fixed.log,load_modules.logfix.diffβ one-line guard (git apply --checkclean)env.txtβ guest environment
DF-5230 VERDICT β NULL uvd_fw deref in uvd_v4_2_resume() (kv DPM ungate path)
Status: reproduced (kernel panic), synthetic-state harness calling the production function; production trigger chain proven statically link-by-link. Impact: panic (local DoS on CIK/KV APU hardware with a mixed fw config). Confidence: certain (the deref itself is mechanically demonstrated twice; the field trigger's hw/fw precondition is stated precisely below).
The defect
uvd_v4_2_resume() reads rdev->uvd_fw->datasize at
sys/dev/drm/radeon/uvd_v4_2.c:51 with no uvd_fw/vcpu_bo/has_uvd guard.
Two callers exist. One is guarded; the other is not:
cik_uvd_start()(cik.c:8167-8175) returns early when!rdev->has_uvd, and theradeon_uvd_resume()it calls first checksvcpu_bo == NULL(radeon_uvd.c:286). Safe.kv_dpm_powergate_uvd(rdev, false)β the UVD un-gate branch β callsuvd_v4_2_resume(rdev)directly atkv_dpm.c:1628with no guard at all. (The ungate even callsuvd_v1_0_start()right after,kv_dpm.c:1629.)
Production trigger chain (every link verified path:line)
system suspend / pm_fini / dpm-disable
-> cik_suspend() cik.c:8545 (radeon_pm_suspend FIRST β
the `if (rdev->has_uvd)` guard at
cik.c:8550 sits BELOW this call)
-> radeon_pm_suspend() radeon_pm.c:1269-1271
-> radeon_pm_suspend_dpm() radeon_pm.c:1257
-> radeon_dpm_disable(rdev) radeon.h:2797 macro
-> kv_dpm_disable() kv_dpm.c:1305,1316 (asic bind
radeon_asic.c:2285)
-> kv_dpm_powergate_uvd(rdev,false) kv_dpm.c:1608
[caps_uvd_pg == true unconditionally, kv_dpm.c:2773;
pi->uvd_power_gated was flipped to true at boot by
kv_dpm_late_enable() -> powergate_uvd(true), kv_dpm.c:1300 β
so the `== gate` early-return at kv_dpm.c:1612 does NOT fire]
-> uvd_v4_2_resume(rdev) kv_dpm.c:1628 NO GUARD
-> rdev->uvd_fw->datasize uvd_v4_2.c:51 uvd_fw == NULL
radeon_uvd_init() sets fw_header_present=false (radeon_uvd.c:137) and
max_handles=10 (:138) before attempting the firmware load, and on CIK the
load of radeonkmsfw_bonaire_uvd (+:176 fallback radeonkmsfw_BONAIRE_uvd)
fails when the fw kmods are not loaded β uvd_fw stays NULL β
cik.c:8150-8157 clears has_uvd β UVD never starts, but DPM is
independent: on KAVERI/KABINI/MULLINS (KV family, kv_dpm_*) DPM comes up
from the SMC firmware (kv_smc.c), kv_dpm_late_enable() power-gates UVD at
boot, and the first suspend / pm-fini then un-gates it through the unguarded
path above.
Preconditions (why Low)
- KV-family CIK APU (KAVERI/KABINI/MULLINS β radeonkms, DPM capable).
- DPM enabled β SMC fw kmod loaded (
radeonkmsfw_*_smc). - UVD fw kmods (
radeonkmsfw_bonaire_uvd/radeonkmsfw_BONAIRE_uvd) NOT loaded βradeon_uvd_init()failed,uvd_fw == NULL.
On DragonFly all radeon firmware ships as separate opt-in kmods
(sys/dev/drm/radeonfw/*/), so "SMC loaded, UVD not" is a natural
misconfiguration, not a contrived one. Trigger: any system suspend (lid close
/ S3 β an unprivileged physical action), kldunload radeonkms
(radeon_pm_fini_dpm, radeon_pm.c:1694), or a power_method sysfs switch.
Result: deterministic NULL-deref panic. Ceiling = DoS; the fault is a pure
data read (datasize, offset 0x10) β no write primitive, no info leak.
Verification on this guest (no AMD GPU β env.txt: NO_AMD_PCI)
The full suspend chain needs KV hardware, so the harness (df5230.c)
reconstructs the exact post-failed-init device state (zeroed
struct radeon_device + max_handles=10, i.e. fw_header_present=false,
gpu_addr=0, uvd_fw=NULL β the state kv_dpm.c:1628 would pass) and calls
the production uvd_v4_2_resume() from the freshly built radeon.ko
(the module that ships the function; no code copied or stubbed):
- baseline Γ2 (second from a fresh
vm.sh resetwith source reverted):Fatal trap 12 ... fault virtual address = 0x10 ... Stopped at uvd_v4_2_resume+0x30: movq 0x10(%rdx),%rdxβ guest down (panic.txt,panic.2.txt). - patched (fix.diff applied in guest
/usr/src, radeon.ko rebuilt, identical trigger):DF5230: survived, uvd_v4_2_resume returned -22, guest up (run.fixed.log).
The faulting instruction movq 0x10(%rdx),%rdx is the datasize load at
uvd_v4_2.c:51 (0x10 = offsetof(struct firmware, datasize),
sys/sys/firmware.h:51-56), and it fires before the first WREG32
(uvd_v4_2.c:52), so the synthetic run exercises precisely the audited deref
and nothing else.
Negative-check list (everything else hunted in uvd_v4_2.c)
- Window-chain overhang past
vcpu_bo(+512 B with the shipping fw) β real, filed separately as DF-5229 (family of DF-4832/DF-4524/DF-4742). uvd_fwNULL on the guarded caller (cik.c:8167): killed β has_uvd covers it;radeon_uvd_resume()also checksvcpu_bo(radeon_uvd.c:286).- Boot-time gate path (
kv_dpm_late_enableβuvd_v1_0_stop): killed β register writes only (uvd_v1_0.c:391-411), no fw deref. - Integer width:
addru64,WREG32truncations are by-design (bits 28-31 at :67, bits 32-39 at :71);sizeu32 values all small constants β killed. max_handlessourced from fw version (radeon_uvd.c:165-166, β€30) βhandles[RADEON_MAX_UVD_HANDLES]is 30 (radeon.h:1674,1685) β no overflow β killed (and DF-5229 shows SESSION*N is bounded by bo sizing anyway).- This file consumes no user-controlled data at all (registers + fw-derived scalars only) β the only user-reachable surface is via CS on real hw, which is DF-1655/DF-4500/DF-4501 territory in radeon_uvd.c, not here.
Recommended fix
fix.diff (one-line guard + comment, git apply --check clean): return
-EINVAL when rdev->uvd_fw == NULL in uvd_v4_2_resume, mirroring the
has_uvd/vcpu_bo guards of the other two paths. Validated on-guest above.
(An alternative placement β guard in kv_dpm_powergate_uvd() β fixes only
the known caller; the function-local guard also covers future callers.)
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
fixedBaseline (stock radeon.ko): identical trigger panics (fault VA 0x10 at uvd_v4_2_resume+0x30), twice, second from a fresh vm.sh reset with reverted source. Patched (fix.diff in guest /usr/src, radeon.ko rebuilt): identical trigger returns -22, prints 'survived', guest stays up, kldload succeeds. The previously-observed panic is GONE under the fix.
fix_run.log (patched-kernel run: survived/returned -22, guest up); fix_build.log (patch + incremental rebuild transcript); panic.txt/panic.2.txt (baseline); fix.diff
Confirmed kernel references
- sys/dev/drm/radeon/uvd_v4_2.c:51
- sys/dev/drm/radeon/kv_dpm.c:1628
- sys/dev/drm/radeon/kv_dpm.c:1608
- sys/dev/drm/radeon/kv_dpm.c:1305
- sys/dev/drm/radeon/kv_dpm.c:1300
- sys/dev/drm/radeon/kv_dpm.c:2773
- sys/dev/drm/radeon/radeon_pm.c:1257
- sys/dev/drm/radeon/radeon_pm.c:1694
- sys/dev/drm/radeon/radeon.h:2797
- sys/dev/drm/radeon/cik.c:8545
- sys/dev/drm/radeon/cik.c:8167
- sys/dev/drm/radeon/radeon_uvd.c:137
- sys/dev/drm/radeon/radeon_uvd.c:286
- sys/dev/drm/radeon/radeon_asic.c:2285
- sys/sys/firmware.h:51
Detail
Evidence (decisive lines)
panic.txt + panic.2.txt (serial console: DF5230 markers, Fatal trap 12, fault VA 0x10, Stopped at uvd_v4_2_resume+0x30 movq 0x10(%rdx),%rdx -- twice, second from fresh reset); run.fixed.log / fix_run.log (patched radeon.ko: 'survived, uvd_v4_2_resume returned -22', guest up); VERDICT.md (full link-by-link production trigger chain with path:line); fix.diff (git apply --check clean); env.txt (no AMD PCI device on guest)
PoC changes
no seed; harness written fresh (df5230.c: zeroed struct radeon_device + max_handles=10 == post-failed-radeon_uvd_init state, calls production radeon.ko symbol; MODULE_DEPEND removed because radeon.ko declares no MODULE_VERSION so a versioned dep always fails)
Verified recommended fix
one-line guard in uvd_v4_2_resume: if (rdev->uvd_fw == NULL) return -EINVAL; (mirrors has_uvd/vcpu_bo guards of the other paths)
Verdict
uvd_v4_2_resume() dereferences rdev->uvd_fw->datasize at sys/dev/drm/radeon/uvd_v4_2.c:51 with no guard. The cik_uvd_start() caller is guarded (has_uvd, cik.c:8167; vcpu_bo, radeon_uvd.c:286), but the KV DPM ungate caller is not: any suspend or pm-fini on a DPM-enabled KV/CIK APU (KAVERI/KABINI/MULLINS) whose UVD firmware kmods are absent (uvd_fw NULL after failed radeon_uvd_init, radeon_uvd.c:137/176; has_uvd cleared cik.c:8156 while DPM stays up from SMC fw) reaches kv_dpm.c:1628 via cik.c:8545 -> radeon_pm.c:1257 -> radeon.h:2797 -> kv_dpm.c:1305,1316,1608 (caps_uvd_pg always true kv_dpm.c:2773; uvd_power_gated set true at boot kv_dpm.c:1300) and panics. The guest has no AMD GPU, so verification used a harness kld that reconstructs the exact post-failed-init radeon_device state (uvd_fw=NULL, fw_header_present=false, gpu_addr=0, max_handles=10) and calls the PRODUCTION uvd_v4_2_resume compiled into a freshly built radeon.ko: deterministic panic 'fault virtual address = 0x10' (offsetof(struct firmware, datasize)) at 'uvd_v4_2_resume+0x30: movq 0x10(%rdx),%rdx', reproduced twice, the second from a fresh vm.sh reset with reverted source; fault precedes the first WREG32 so no MMIO is touched. With fix.diff (one-line uvd_fw==NULL -> -EINVAL guard) applied in the guest /usr/src and radeon.ko rebuilt, the identical trigger prints 'survived, uvd_v4_2_resume returned -22' and the guest stays up. Field trigger is hardware-gated (KV APU + SMC-without-UVD fw config); impact ceiling is a local DoS panic (pure data read, no write primitive).
No comments yet.