amdgpu_kv_notify_message_to_smu() fail-open response validation: SMC timeout (0x00) and undefined response codes 0x02β0xFD all reported as success to the whole kv_dpm power-management state machine
Summary
Only 0xFF/0xFE are rejected - a wedged-SMC timeout (0x00) and every undefined code fall through to return 0, so the entire kv_dpm machine (enable/bapm/~40 table uploads/get-mask) believes success against a dead controller; the ci sibling returns the raw result so its callers DO observe timeouts. The radeon-driver twin is byte-identical with the same hole. SMC-fault-triggered PM desync (no memory safety; SRAM path fails closed). Reject-not-OK fix (both twins).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5132 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| check_static.sh | β | 2.2 KB | view raw | |
| check_static.log | β | 583 B | view raw | |
| fix.diff | β | 603 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-5132 β amdgpu_kv_notify_message_to_smu() fail-open response validation: SMC timeout (0x00) and undefined codes (0x02β0xFD) reported as success
File: sys/dev/drm/amd/amdgpu/kv_smc.c (pass 2, beyond the known DF-2130
missing-smc_idx_lock finding β this defect is independent of the lock).
Root cause
amdgpu_kv_notify_message_to_smu() (kv_smc.c:33-55) posts a message to the
Kaveri/Kabini/Mullins SMC microcontroller and then validates the response:
for (i = 0; i < adev->usec_timeout; i++) { /* :40 */
if ((RREG32(mmSMC_RESP_0) & SMC_RESP_0__SMC_RESP_MASK) != 0)
break;
udelay(1);
}
tmp = RREG32(mmSMC_RESP_0) & SMC_RESP_0__SMC_RESP_MASK; /* :45 */
if (tmp != 1) { /* :47 */
if (tmp == 0xFF) /* :48 Failed */
return -EINVAL;
else if (tmp == 0xFE) /* :50 UnknownCmd */
return -EINVAL;
}
return 0; /* :54 β fall-through */
Per the SMC protocol, 0x01 = PPSMC_Result_OK, 0xFF = Failed,
0xFE = UnknownCmd (sys/dev/drm/amd/amdgpu/ppsmc.h:67-68), and 0x00
means "no response yet". When the wait loop exhausts adev->usec_timeout
(SMC wedged/hung β precisely the condition the check exists for), tmp is
0x00; the nested if only rejects 0xFF/0xFE, so tmp==0 falls through
to return 0 = success. Every undefined code in 0x02..0xFD likewise
returns success.
Contrast with the CI sibling in the same driver:
amdgpu_ci_send_msg_to_smc() (sys/dev/drm/amd/amdgpu/ci_smc.c:170-187)
returns the raw response to its callers, so a timeout (0 β PPSMC_Result_OK)
is observed as not-OK. KV converts it to a silent success.
Impact
On an SMC that has stalled/timed out, every DPM operation
(amdgpu_kv_smc_dpm_enable kv_smc.c:106-112, BAPM :114-120, the ~40
amdgpu_kv_copy_bytes_to_smc() table uploads from kv_dpm.c, force-level and
get-enable-mask messages) is believed to have succeeded. The driver's PM state
machine then proceeds on a dead controller β programming tables onto a wedged
SMC, treating garbage reads as valid state (amdgpu_kv_dpm_get_enable_mask
kv_smc.c:57-67 reads SMC_SYSCON_MSG_ARG_0 only because ret==0). Worst
realistic outcome is PM state desynchronization β wrong clocks/voltage β GPU
hang requiring reboot (local DoS); no kernel memory-safety consequence (the
SRAM write path fails closed β see VERDICT.md). Trigger requires SMC
misbehavior (hardware/firmware fault), not attacker input β Info
(hardening / fail-closed correctness).
The radeon-driver twin sys/dev/drm/radeon/kv_smc.c:30-52 is byte-identical
(same fall-through); noted here so the radeon side can be twin-filed.
Reproduce (static)
./check_static.sh
asserts, against the tree: (1) the exact fall-through structure at
kv_smc.c:44-54 (no error return for tmp==0), (2) PPSMC_Result_OK == 0x01
so 0 cannot be a success code, (3) the ci sibling propagates the raw result.
Runtime reproduction requires Kaveri-class APU hardware with a wedged SMC; the
QEMU audit guest has no AMD GPU at all (pciconf β 0 devices) β see
VERDICT.md.
Fix
fix.diff β reject anything β PPSMC_Result_OK:
- if (tmp != 1) {
- if (tmp == 0xFF)
- return -EINVAL;
- else if (tmp == 0xFE)
- return -EINVAL;
- }
+ if (tmp != PPSMC_Result_OK) {
+ dev_err(adev->dev, "SMC failed to handle message 0x%x (response 0x%x)\n",
+ id & SMC_MESSAGE_0__SMC_MSG_MASK, tmp);
+ return -EINVAL;
+ }
DF-5132 β VERDICT
status: untested (statically certain / runtime not testable on audit guest) reproduced: 0 Β· impact: none demonstrated (PM state desync on wedged SMC; DoS ceiling)
The claim, proven from source
- 0x00 cannot be a success code.
sys/dev/drm/amd/amdgpu/ppsmc.h:67-68:PPSMC_Result_OK = 0x01,PPSMC_Result_Failed = 0xFF; resp0x00is the "no response yet" state the kv_smc.c:41 loop polls for. - The validator only rejects 0xFF/0xFE (kv_smc.c:47-52). A timed-out SMC
leaves
tmp == 0after the loop; the outerif (tmp != 1)body contains no catch-all, so control reachesreturn 0at kv_smc.c:54. Same for every undefined0x02..0xFD. - The CI sibling does not have this hole:
amdgpu_ci_send_msg_to_smc()(ci_smc.c:170-187) returns(PPSMC_Result)tmpraw, so its callers compare againstPPSMC_Result_OKand observe a timeout as failure. - Reachability:
amdgpu_kv_notify_message_to_smuis the sole SMC message channel for the whole kv_dpm.c state machine (amdgpu_kv_smc_dpm_enable,amdgpu_kv_smc_bapm_enable,amdgpu_kv_send_msg_to_smc_with_parameter,amdgpu_kv_dpm_get_enable_maskβ all kv_smc.c:57-120, all invoked from kv_dpm.c DPM enable/disable/workqueue paths).
check_static.sh (this pack) asserts facts 1β3 mechanically against the tree;
output in check_static.log.
Why severity is Info, not Low+
- No attacker-controlled input reaches this path: the message id is a kernel enum and the failure trigger is an SMC firmware/hardware fault.
- No memory-safety consequence: I traced the natural follow-on question β
"does the fail-open let garbage SRAM values reach an unchecked write?" β and
it fails closed.
amdgpu_kv_copy_bytes_to_smc()'s outer sum check(smc_start_address + byte_count) > limit(kv_smc.c:129) can wrap u32 and pass, but any address that wrapped is> limit, and every innerkv_set_smc_sram_address()re-checks(smc_address + 3) > limit(kv_smc.c:83) where wrapping is impossible because the alignment gate at kv_smc.c:81 restricts addresses to multiples of 4 (β€ 0xFFFFFFFC). The first inner check rejects the copy β-EINVALpropagates (kv_dpm.c callers allreturn ret). So even a garbagepi->dpm_table_start(read from wedged-SMC SRAM at kv_dpm.c:589-603) cannot smuggle an out-of-limit SMC SRAM write. (This kill is recorded because the radeon ci twin of that outer check was filed as DF-2105 β on KV the inner re-validation closes it.) - Realistic ceiling: DPM desynchronization on an already-faulted SMC β wrong voltage/clock programming β GPU hang (local DoS). That is a robustness defect worth fixing (defense-in-depth, fail-closed), i.e. Info per the rubric.
Why no runtime PoC
Guest: DragonFly 6.5-DEVELOPMENT x86_64, zero AMD/ATI PCI devices
(pciconf -l | grep -icE 'ati|amd|1002' β 0); amdgpu cannot attach, and the
defect additionally needs Kaveri-class APU silicon with a stalled SMC. Honest
classification = untested (hardware-gated), with the static proof attached.
Exploit chain
(none)
Fix validation
Not applicable (no runtime baseline without Kaveri hardware). fix.diff
authored after verification; git apply --check passes against the unmodified
tree; it was not applied to sys/. The same one-line strictness fix should
be mirrored in sys/dev/drm/radeon/kv_smc.c:44-52 (byte-identical twin).
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_testablefix 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; fix.diff verified to apply cleanly (git apply --check) to the unmodified tree and uses PPSMC_Result_OK which is already in scope via kv_dpm.h:31 -> ppsmc.h
git apply --check findings/poc/DF-5132/fix.diff => clean; hunk @@ -44,11 +44,10 @@ sys/dev/drm/amd/amdgpu/kv_smc.c
Confirmed kernel references
Detail
Evidence (decisive lines)
check_static.log: 'PASS kv_smc.c:47-54 ... return 0 reachable with tmp==0x00' plus the PPSMC_Result_OK==0x01, wait-loop, and ci-contrast PASSes; fix.diff hunk @@ -44,11 +44,10 @@ replacing the nested 0xFF/0xFE ifs with 'tmp != PPSMC_Result_OK' rejection; VERDICT.md records the fail-closed kill of the adjacent kv_smc.c:129 u32-wrap candidate (inner kv_set_smc_sram_address re-validation at :83, non-wrappable due to the :81 4-alignment gate)
PoC changes
no seed PoC existed (filed during pass 2 with untested verdict); authored check_static.sh (5 mechanical assertions against the tree) since no runtime trigger is possible without Kaveri APU hardware
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; mirror in radeon/kv_smc.c - see fix.diff
Verdict
Statically certain logic defect, hardware-gated runtime: amdgpu_kv_notify_message_to_smu() (kv_smc.c:33-55) validates the SMC response by rejecting only 0xFF/0xFE, so a wait-loop timeout (resp 0x00 = 'no response yet' per ppsmc.h:67 where OK==0x01) and every undefined 0x02-0xFD fall through to 'return 0' = success. The whole kv_dpm.c power-management state machine then proceeds against a wedged SMC (dpm enable/bapm/table uploads all believe they succeeded). No attacker-controlled input, no memory-safety consequence (the SMC SRAM write path fails closed - see VERDICT.md kill trace for the kv_smc.c:129 wrap), realistic ceiling is PM desynchronization/GPU hang on already-faulted hardware -> Info. Not runtime-reproducible: audit guest has zero AMD/ATI PCI devices and the trigger needs Kaveri-class APU silicon with a stalled SMC. Untested recorded honestly; mechanical static proof (5 assertions incl. the ci_smc.c:187 contrast where the sibling DOES surface timeouts) in check_static.log.
No comments yet.