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)
PoC verification
Evidence pack
findings/poc/DF-5137 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 prooffix.diffβ the fix, unified difffix_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
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)
- 0x00 cannot be a success code.
sys/dev/drm/radeon/ppsmc.h:67-68:PPSMC_Result_OK = 0x01,PPSMC_Result_Failed = 0xFF. Response0x0000is the "no response yet" state that the wait loop atkv_smc.c:37-41polls for (!= 0breaks the loop). - The validator only rejects 0xFF/0xFE (
kv_smc.c:44-49). A timed-out SMC leavestmp == 0after the loop; theif (tmp != 1)body contains no catch-all, so control reachesreturn 0atkv_smc.c:51. Same for every undefined code. - Radeon-specific widening:
SMC_RESP_MASKis0xffff(sys/dev/drm/radeon/cikd.h:431), sotmpis 16 bits wide and the literal comparisons match only0x00FF/0x00FE. A response of0xFF00(byte-swappedFAILED),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: - The radeon siblings do NOT have this hole:
si_send_msg_to_smc()(si_smc.c:172-190) andrv770_send_msg_to_smc()(rv770_smc.c:424-448) return the raw(PPSMC_Result)tmp, so their callers compare againstPPSMC_Result_OKand observe a timeout as failure. KV is the only radeon SMC wrapper that swallows it. - Reachability:
kv_notify_message_to_smuis 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). Onret == 0the 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_MASKat 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 innerkv_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 β-EINVALpropagates; all kv_dpm.c callersreturn ret. So even a garbagepi->dpm_table_start(read from wedged-SMC SRAM viakv_process_firmware_header, kv_dpm.c:451-471, bounded reads at fixed header offsetsSMU7_FIRMWARE_HEADER_LOCATION + offsetof(...), smu7.h0x20000, limitpi->sram_endset 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 atradeon_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_testableruntime 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
Confirmed kernel references
- sys/dev/drm/radeon/kv_smc.c:30-52
- sys/dev/drm/radeon/kv_smc.c:44-51
- sys/dev/drm/radeon/kv_smc.c:54-64
- sys/dev/drm/radeon/ppsmc.h:67-68
- sys/dev/drm/radeon/cikd.h:429-431
- sys/dev/drm/radeon/si_smc.c:172-190
- sys/dev/drm/radeon/rv770_smc.c:424-448
- sys/dev/drm/radeon/kv_dpm.c:451-471
- sys/dev/drm/radeon/kv_smc.c:125
- sys/dev/drm/radeon/kv_smc.c:78-81
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.
No comments yet.