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)
PoC verification
Evidence pack
findings/poc/DF-5205 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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), andv β {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.
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:
sys/dev/drm/radeon/trinity_smc.c:36-40β poll loop breaks on any nonzero response, or silently falls through afterrdev->usec_timeoutiterations (100 ms βRADEON_MAX_USEC_TIMEOUT,sys/dev/drm/radeon/radeon.h:131, set atradeon_device.c:1300).trinity_smc.c:41βv = RREG32(SMC_RESP_0)unmasked (register0x230,trinityd.h:170; noSMC_RESP_MASKexists for TN β the 0xffff masks incikd.h:429-431are CIK/kv-only, and kv's twin applies them atkv_smc.c:35,38,42).trinity_smc.c:43-53β theif (v != 1)block returns-EINVALonly for exactly 0xFF and 0xFE; the timeout value 0 and every undefined code 0x02-0xFD (and any value with stray upper bits) falls through toreturn 0at :53.- 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_disabledresults are checked attrinity_dpm.c:836/1221/1226but 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_smuis 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,182etc.). - 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_hashhashes the annotated analysis fileanalysis.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_testablefix.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.
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.
No comments yet.