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

kv_notify_message_to_smu() fail-open SMC response validation (radeon twin of DF-5132): timeout (0x0000) and all undefined response codes except 0x00FF/0x00FE reported as success to the entire kv_dpm power-management state machine

Summary

Rejects only the literals 0xFF/0xFE while SMC_RESP_MASK is 0xffff (65536 codes; even a byte-swapped FAILED 0xFF00 reports success) - wider than the amdgpu twin. All 37 kv_dpm call sites + the enable-mask reader proceed against a wedged SMC. 11/11 static assertions incl. the si/rv770 raw-return contrast (KV is the only radeon wrapper that swallows timeouts). Reject-not-OK fix compile-validated (full radeon.ko -Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5137 Β· 9 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
check_static.sh β€” 2.8 KB view raw
check_static.log β€” 280 B view raw
fix.diff β€” 416 B view raw
fix_build.log β€” 143.5 KB view raw
env.txt β€” 467 B view raw
manifest.json β€” 958 B view raw
verdict.json β€” 5.3 KB view raw

DF-5137 β€” radeon kv_smc.c: fail-open SMC response validation

File: sys/dev/drm/radeon/kv_smc.c:30-52 (radeon twin of DF-5132, amdgpu)

What: kv_notify_message_to_smu() validates the SMC response by rejecting ONLY the literal values 0xFF and 0xFE. A wait-loop timeout (response 0x0000 = "no response yet") and every undefined response code fall through to return 0 = success. On radeon the hole is WIDER than the amdgpu twin: SMC_RESP_MASK is 0xffff (cikd.h:431), so the response register is compared as a 16-bit value and the literals only match 0x00FF/0x00FE β€” e.g. a byte-swapped FAILED (0xFF00) or any 0x0102..0xFFFD also reports success.

Impact: the whole kv_dpm.c power-management state machine (37 call sites) proceeds against a wedged SMC believing every message succeeded (DPM enable, BAPM enable, table uploads); kv_dpm_get_enable_mask() (kv_smc.c:54-64) then reads the message-ARG register as a "valid" enable mask after a timed-out query. No attacker-controlled input reaches this path and the SMC SRAM write path fails closed (see VERDICT.md kill trace), so the ceiling is PM desynchronization / GPU hang on already-faulted hardware => Info, matching DF-5132.

Reproduce (static proof; runtime needs Kaveri APU silicon)

sh check_static.sh /home/maxx/dfbsd/dfbsd     # 11 assertions, all must PASS
cat check_static.log

Runtime reproduction is hardware-gated: the audit QEMU guest has zero AMD/ATI PCI devices (pciconf -l | grep -icE 'ati|amd|1002' β†’ 0, see env.txt) and the trigger additionally needs a Kaveri-class APU with a stalled SMC.

Fix

fix.diff (git-apply clean against the unmodified tree) replaces the 0xFF/0xFE whitelist with fail-closed rejection of everything != PPSMC_Result_OK, mirroring the DF-5132 fix for the amdgpu twin. fix_build.log is the full radeon.ko build with the fix applied (compiles clean with -Werror; kv_smc.o linked).

Files

  • check_static.sh / check_static.log β€” mechanical static proof
  • fix.diff β€” the fix, unified diff
  • fix_build.log β€” radeon.ko compile-validation of the fix (guest /usr/src)
  • env.txt β€” guest environment (no AMD hardware)
  • manifest.json, verdict.json, VERDICT.md
VERDICT.md
↓ download raw

DF-5137 β€” VERDICT

status: untested (statically certain / runtime hardware-gated) reproduced: 0 Β· impact: none demonstrated (PM state desync on a wedged SMC; local DoS ceiling)

The claim, proven from source (check_static.log β€” 11/11 PASS)

  1. 0x00 cannot be a success code. sys/dev/drm/radeon/ppsmc.h:67-68: PPSMC_Result_OK = 0x01, PPSMC_Result_Failed = 0xFF. Response 0x0000 is the "no response yet" state that the wait loop at kv_smc.c:37-41 polls for (!= 0 breaks the loop).
  2. The validator only rejects 0xFF/0xFE (kv_smc.c:44-49). A timed-out SMC leaves tmp == 0 after the loop; the if (tmp != 1) body contains no catch-all, so control reaches return 0 at kv_smc.c:51. Same for every undefined code.
  3. Radeon-specific widening: SMC_RESP_MASK is 0xffff (sys/dev/drm/radeon/cikd.h:431), so tmp is 16 bits wide and the literal comparisons match only 0x00FF/0x00FE. A response of 0xFF00 (byte-swapped FAILED), 0x0102, … all return success. The amdgpu twin (DF-5132) compares the same mask but the radeon file documents the hole against its own driver siblings:
  4. The radeon siblings do NOT have this hole: si_send_msg_to_smc() (si_smc.c:172-190) and rv770_send_msg_to_smc() (rv770_smc.c:424-448) return the raw (PPSMC_Result)tmp, so their callers compare against PPSMC_Result_OK and observe a timeout as failure. KV is the only radeon SMC wrapper that swallows it.
  5. Reachability: kv_notify_message_to_smu is the sole SMC message channel for the kv_dpm state machine (kv_smc_dpm_enable, kv_smc_bapm_enable, kv_send_msg_to_smc_with_parameter, kv_dpm_get_enable_mask β€” 37 references in kv_dpm.c). On ret == 0 the enable-mask query reads the ARG register (kv_smc.c:60-61), propagating wedged-SMC garbage into PM decisions.

Why severity is Info (rubric), identical reasoning to DF-5132

  • No attacker-controlled input reaches the path: the message id is a kernel enum (id & SMC_MSG_MASK at kv_smc.c:35); the trigger is SMC firmware/hardware fault.
  • No memory-safety consequence β€” I re-traced the natural follow-on for the RADEON file: kv_copy_bytes_to_smc()'s outer check (smc_start_address + byte_count) > limit (kv_smc.c:125) can wrap u32 and pass, but every inner kv_set_smc_sram_address() re-validates alignment (smc_address & 3, kv_smc.c:78) and (smc_address + 3) > limit (kv_smc.c:80-81) where wrap is impossible for 4-aligned addresses (≀ 0xFFFFFFFC + 3). The first inner check rejects the copy β†’ -EINVAL propagates; all kv_dpm.c callers return ret. So even a garbage pi->dpm_table_start (read from wedged-SMC SRAM via kv_process_firmware_header, kv_dpm.c:451-471, bounded reads at fixed header offsets SMU7_FIRMWARE_HEADER_LOCATION + offsetof(...), smu7.h 0x20000, limit pi->sram_end set at kv_dpm.c:2738) cannot smuggle an out-of-limit SMC SRAM write. Fails closed.
  • Wait-loop bound: rdev->usec_timeout = RADEON_MAX_USEC_TIMEOUT = 100000 (100 ms) set at radeon_device.c:1300 β€” bounded, no unbounded spin.
  • Realistic ceiling: DPM desynchronization on an already-faulted SMC β†’ wrong voltage/clock programming β†’ GPU hang (local DoS). Defense-in-depth / fail-closed robustness fix => Info.

Why no runtime PoC

Guest: DragonFly 6.5-DEVELOPMENT x86_64 with zero AMD/ATI PCI devices (env.txt: pciconf -l | grep -icE 'ati|amd|1002' β†’ 0); radeon cannot attach, and the defect needs Kaveri-class APU silicon with a stalled SMC. Honest classification: untested (hardware-gated) with the mechanical static proof attached (11/11 PASS in check_static.log).

Relationship to knowns / twins

  • Independent of DF-2135 (missing smc_idx_lock β€” the register-access serialization issue); this finding is the response VALIDATOR.
  • Byte-identical logic twin of DF-5132 (amdgpu kv_smc.c:33-55), explicitly anticipated in DF-5132's notes ("radeon/kv_smc.c:30-52 … carries the same fall-through"). This is the radeon-side filing.

Fix validation

Runtime not possible (no hardware); compile validation performed: fix.diff applied to the guest /usr/src and radeon.ko rebuilt β€” full -Werror build succeeded, kv_smc.o linked (fix_build.log). git apply --check clean against the unmodified repo tree.

Exploit chain

(none)

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
baseline no→ patch + rebuild →patched clean

runtime fix validation not applicable: no runtime baseline can exist on the QEMU audit guest (no AMD GPU) and the defect requires a stalled Kaveri SMC to manifest. Compile validation performed instead: fix.diff applied to the guest /usr/src and radeon.ko rebuilt with -Werror β€” clean build, kv_smc.o linked (fix_build.log, 170 lines). fix.diff verified git-apply-clean against the unmodified repo tree and uses PPSMC_Result_OK already in scope via kv_dpm.h:32 -> ppsmc.h, dev_err via linux/device.h:67

fix_build.log (full radeon.ko build output with the fix applied, exit 0); 'git apply --check findings/poc/DF-5137/fix.diff' => clean; hunk @@ -41,11 +41,10 @@ sys/dev/drm/radeon/kv_smc.c
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

check_static.log: 11/11 PASS incl. 'kv_fallthrough_ret0' (return 0 reachable with tmp==0), 'resp_mask_is_16bit' (cikd.h:431 = 0xffff), 'si_returns_raw'/'rv770_returns_raw' contrast, 'getmask_reads_arg'; fix.diff hunk @@ -41,11 +41,10 @@ replacing the 0xFF/0xFE whitelist with 'tmp != PPSMC_Result_OK' rejection; VERDICT.md records the fail-closed kill of the adjacent kv_smc.c:125 u32-wrap candidate (inner re-validation at :78-81, same analysis DF-5132 applied to the amdgpu twin); fix_build.log = full radeon.ko -Werror build with the fix applied on the guest /usr/src

PoC changes

no seed PoC existed (twin filed during pass 2); authored check_static.sh with 11 mechanical assertions against the tree since no runtime trigger is possible without Kaveri APU hardware; fix.diff mirrors the accepted DF-5132 fix shape adapted to radeon identifiers (dev_err(rdev->dev,...), PPSMC_Result_OK in scope via kv_dpm.h:32 -> ppsmc.h)

Verified recommended fix

Reject every response != PPSMC_Result_OK (return -EINVAL with a dev_err identifying the message and response) instead of whitelisting only 0xFF/0xFE β€” see fix.diff

Verdict

Statically certain logic defect, hardware-gated runtime: kv_notify_message_to_smu() (sys/dev/drm/radeon/kv_smc.c:30-52) validates the SMC response by rejecting only the literals 0xFF/0xFE (kv_smc.c:44-49), so a wait-loop timeout (resp 0x0000 = 'no response yet' per radeon ppsmc.h:67 where OK==0x01) and every undefined code fall through to 'return 0' = success; because SMC_RESP_MASK is 0xffff (cikd.h:431) the literals only match 0x00FF/0x00FE, widening the hole vs the amdgpu twin (e.g. 0xFF00 byte-swapped FAILED also succeeds). The whole kv_dpm.c state machine (37 refs) then proceeds against a wedged SMC; kv_dpm_get_enable_mask (kv_smc.c:54-64) additionally reads the ARG register as a valid mask after a timed-out query. No attacker-controlled input, no memory-safety consequence (SRAM write path fails closed via kv_set_smc_sram_address:78-81 re-validation of 4-aligned addresses; outer u32 wrap at kv_smc.c:125 cannot smuggle an OOB write), ceiling is PM desync/GPU hang on already-faulted silicon -> Info. Runtime not reproducible on the audit guest (zero AMD/ATI PCI devices); honest status untested with 11/11 mechanical static assertions in check_static.log, including the same-driver contrast where si_smc.c:190 and rv770_smc.c:446 return the raw (PPSMC_Result)tmp so their callers DO observe timeouts as failures.