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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5132 Β· 7 files
FileTypeDescriptionSize
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;
+   }
VERDICT.md
↓ download raw

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

  1. 0x00 cannot be a success code. sys/dev/drm/amd/amdgpu/ppsmc.h:67-68: PPSMC_Result_OK = 0x01, PPSMC_Result_Failed = 0xFF; resp 0x00 is the "no response yet" state the kv_smc.c:41 loop polls for.
  2. The validator only rejects 0xFF/0xFE (kv_smc.c:47-52). A timed-out SMC leaves tmp == 0 after the loop; the outer if (tmp != 1) body contains no catch-all, so control reaches return 0 at kv_smc.c:54. Same for every undefined 0x02..0xFD.
  3. The CI sibling does not have this hole: amdgpu_ci_send_msg_to_smc() (ci_smc.c:170-187) returns (PPSMC_Result)tmp raw, so its callers compare against PPSMC_Result_OK and observe a timeout as failure.
  4. Reachability: amdgpu_kv_notify_message_to_smu is 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 inner kv_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 β†’ -EINVAL propagates (kv_dpm.c callers all return ret). So even a garbage pi->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_testable
baseline no→ patch + rebuild →patched clean

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; 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
↓ fix.diffper-fix-DF-5132

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.