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

trinity_notify_message_to_smu() fail-open SMC response validation: timeout (0x0) and all undefined codes outside exactly 0xFF/0xFE reported as success to the entire trinity_dpm power-management state machine β€” and unlike the kv twin (DF-5137), the response register is read UNMASKED

Summary

Only 0xFF/0xFE are rejected; timeout and every other code falls to return 0 - and the read is raw 32-bit (Trinity has no SMC_RESP_MASK). All ten wrappers report success against a wedged SMC, and trinity_dpm_enable returns 0 unconditionally - DPM marked enabled on hardware that never acked. Catch-all-reject fix (git-apply clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5205 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
analysis.txt β€” 2.7 KB view raw
fix.diff β€” 354 B view raw
verdict.json β€” 3.0 KB view raw
manifest.json β€” 1.1 KB view raw

DF-5205 β€” trinity_notify_message_to_smu() fail-open SMC response validation

File: sys/dev/drm/radeon/trinity_smc.c:43-53 (radeon Trinity/TN/RL β€” HD 7xxx APU) Family: radeon twin of DF-5137 (sys/dev/drm/radeon/kv_smc.c) / DF-5132 (amdgpu kv_smc.c). Severity: Info β€” hardware-reliability fail-open on a path with no attacker-controlled data.

The defect

trinity_notify_message_to_smu() sends a message id to the SMC microcontroller and polls SMC_RESP_0 for up to rdev->usec_timeout Β΅s (RADEON_MAX_USEC_TIMEOUT = 100000 = 100 ms, radeon.h:131). It then validates the response:

        v = RREG32(SMC_RESP_0);                    /* trinity_smc.c:41 β€” RAW 32-bit */

        if (v != 1) {                              /* :43 */
                if (v == 0xFF) { ... return -EINVAL; }   /* :44-46 */
                else if (v == 0xFE) { ... return -EINVAL; } /* :47-49 */
        }                                          /* :50 β€” NO else: falls through */
        return 0;                                  /* :53 β€” SUCCESS */

Only the two explicit SMC error codes (0xFF "failed", 0xFE "unknown message") produce an error. Every other non-1 value β€” most importantly:

  • v == 0 β€” the SMC never responded at all (poll loop exhausted, i.e. timeout), and
  • v ∈ {0x02 … 0xFD} β€” undefined response codes, and
  • any stray bits in the upper 16 bits (trinity reads the register unmasked; the kv twin at least applies SMC_RESP_MASK = 0xffff, cikd.h:431 β€” Trinity has no such mask anywhere in trinityd.h)

β€” falls out of the if (v != 1) block and the function returns 0 (success).

Consequence

All 10 exported wrappers (trinity_dpm_config :64, trinity_dpm_force_state :74, trinity_dpm_n_levels_disabled :81, trinity_uvd_dpm_config :88, trinity_dpm_no_forced_level :93, trinity_dpm_bapm_enable :56, trinity_dce_enable_voltage_adjustment :98, trinity_gfx_dynamic_mgpg_config :107) report success to the trinity_dpm power-management state machine. The consumers then believe PM reconfiguration that never happened, e.g. trinity_dpm_enable() (trinity_dpm.c:1105-1132) sends DPM-enable messages that silently failed and still return 0, after which the radeon PM core marks DPM enabled and keeps transitioning clock/voltage state against non-responsive SMC hardware. Reliability defect (mis-clocked/mis-volted GPU, dead PM state machine), not memory corruption; no unprivileged input reaches this path (message ids are compile-time constants, the n arguments are driver-derived).

Why untested (see VERDICT.md)

Requires AMD Trinity (HD 7xxx APU) hardware with a live SMC firmware that can be made to time out or return an undefined response code. The audit QEMU guest has no radeon GPU, and this is an Info-class hardening finding, which the audit contract exempts from guest verification. fix.diff is authored but not build/runtime-validated on hardware.

Fix

fix.diff β€” add the missing catch-all: any v != 1 that is neither 0xFF nor 0xFE is an error (-EINVAL), matching the SMC message protocol. Applies clean with git apply.

VERDICT.md
↓ download raw

DF-5205 VERDICT β€” UNTESTED (hardware-absent; Info-class, Phase V exempt)

Status: untested. Reproduced: no. Impact: none (code-shape defect is certain; runtime consequence requires Trinity hardware to demonstrate).

What is certain (static proof)

The fail-open is a static property of the code, verified line-by-line:

  1. sys/dev/drm/radeon/trinity_smc.c:36-40 β€” poll loop breaks on any nonzero response, or silently falls through after rdev->usec_timeout iterations (100 ms β€” RADEON_MAX_USEC_TIMEOUT, sys/dev/drm/radeon/radeon.h:131, set at radeon_device.c:1300).
  2. trinity_smc.c:41 β€” v = RREG32(SMC_RESP_0) unmasked (register 0x230, trinityd.h:170; no SMC_RESP_MASK exists for TN β€” the 0xffff masks in cikd.h:429-431 are CIK/kv-only, and kv's twin applies them at kv_smc.c:35,38,42).
  3. trinity_smc.c:43-53 β€” the if (v != 1) block returns -EINVAL only for exactly 0xFF and 0xFE; the timeout value 0 and every undefined code 0x02-0xFD (and any value with stray upper bits) falls through to return 0 at :53.
  4. Consumers propagate the false success: trinity_dpm_enable() (trinity_dpm.c:1105-1132) returns 0 unconditionally at :1131 even when every SMC message inside it failed open; trinity_dpm_force_state / trinity_dpm_n_levels_disabled results are checked at trinity_dpm.c:836/1221/1226 but will read as success. The radeon DPM core then marks the device DPM-enabled against hardware that never acked.

This is the exact family shape already filed as DF-5137 (radeon kv_smc.c) and DF-5132 (amdgpu kv_smc.c), both Info/certain β€” trinity is the last SMC file in the family and the only one additionally unmasked.

Why not guest-verified

  • The single-tenant QEMU guest has no AMD Trinity GPU; trinity_notify_message_to_smu is only reachable through the radeon DPM code on TN/RL ASICs. There is no software-only trigger (no ioctl/sysctl reaches it; message ids are constants β€” ppsmc.h:173,182 etc.).
  • Info severity / no attacker-controlled input β†’ outside the Phase V mandate (Critical/High, memcorrupt/privesc/hammer2 buckets, or trivial local PoC).
  • Therefore: no PoC authored, no build/run performed, attempts: 0, code_hash hashes the annotated analysis file analysis.txt.

Fix

fix.diff adds the missing catch-all (return -EINVAL for every v != 1 that is not the two defined error codes). Authored against the read-only sys/ tree; git apply --check verified; not hardware-validated (fix_status: not_testable). Never applied to sys/.

Escalation assessment

None. No memory-safety primitive: the bug misreports a hardware-wait outcome only. Message ids and scratch payloads are kernel constants (trinity_smc.c:59-110); n in trinity_dpm_force_state/n_levels_disabled comes from driver-computed power-state level counts (trinity_dpm.c:836,1221,1226), never raw user input. Worst case is a GPU left in boot clocks/voltage with a PM state machine that believes otherwise.

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.diff authored against the read-only sys/ tree and verified with git apply --check; single-hunk, no semantic conflicts. Cannot be hardware-validated: baseline cannot be reproduced without Trinity ASICs, so fix_status is not_testable rather than fixed.

fix.diff in this directory; applied and reverted with git apply --check on sys/dev/drm/radeon/trinity_smc.c (clean); hunk touches lines 48-53 only.
↓ fix.diffper-fix-DF-5205

Confirmed kernel references

Detail

Evidence (decisive lines)

analysis.txt (annotated line-by-line walkthrough); fix.diff (catch-all -EINVAL, git apply --check clean); decisive lines trinity_smc.c:36-40 (timeout fall-through), :41 (unmasked RREG32), :43-53 (only 0xFF/0xFE handled, everything else returns 0); consumer proof trinity_dpm.c:1105-1132 (trinity_dpm_enable returns 0 unconditionally at :1131); register defs trinityd.h:169-170; timeout radeon.h:131

PoC changes

No PoC authored: the defect is only reachable on AMD Trinity (HD 7xxx APU) ASICs via the radeon DPM path; the QEMU audit guest has no radeon GPU and there is no software-only trigger (message ids are compile-time constants). Static proof + fix.diff shipped instead.

Verified recommended fix

Add a catch-all error return in trinity_notify_message_to_smu() for every v != 1 that is not the two defined SMC error codes (see fix.diff).

Verdict

Static fail-open proven at sys/dev/drm/radeon/trinity_smc.c:43-53: trinity_notify_message_to_smu() returns success for an SMC timeout (v==0 after 100ms) and every undefined response code outside exactly 0xFF/0xFE, and unlike the kv twin (DF-5137) it reads SMC_RESP_0 unmasked so stray upper bits also defeat validation; the whole trinity_dpm state machine then believes PM reconfigurations that never happened. Runtime demonstration requires AMD Trinity hardware with a wedged SMC β€” absent from the audit QEMU guest β€” and as an Info-class hardening finding with no attacker-controlled input it is outside the Phase V mandate, so no PoC was authored or run.