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

trinity_acquire_mutex() is void and cannot fail: on SMC mutex-grant timeout it silently returns without the mutex, and all six live callers then program SMC registers assuming exclusivity against the SMC firmware

Summary

The grant poll falls out with the request left asserted and the failure unreportable by construction - the six live callers issue SMC register programs immediately, interleaving with the SMC firmware's own accesses. Unique to Trinity in the radeon SMC family; kernel-side smc_idx_lock present (checked). int-return + caller-bail fix (3 files, apply-clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5206 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
analysis.txt β€” 2.4 KB view raw
fix.diff β€” 2.8 KB view raw
verdict.json β€” 3.2 KB view raw
manifest.json β€” 1.1 KB view raw

DF-5206 β€” trinity_acquire_mutex() cannot fail: silently proceeds without the SMC mutex on timeout

File: sys/dev/drm/radeon/trinity_smc.c:112-122 (+ all call sites in trinity_dpm.c) Severity: Info β€” hardware-reliability fail-open, unique to Trinity in the radeon SMC family (kv/si/ci/sumo have no equivalent void mutex-grant wait).

The defect

trinity_acquire_mutex() requests the SMC firmware mutex by writing 1 to SMC_INT_REQ (register 0x220, trinityd.h:167) and polls for the grant:

void trinity_acquire_mutex(struct radeon_device *rdev)   /* :112 β€” VOID */
{
    int i;

    WREG32(SMC_INT_REQ, 1);                            /* :116 */
    for (i = 0; i < rdev->usec_timeout; i++) {         /* :117 β€” up to 100 ms */
        if ((RREG32(SMC_INT_REQ) & 0xffff) == 1)       /* :118 */
            break;                                     /* grant */
        udelay(1);
    }
}                                                        /* :122 β€” timeout: silent return */

If the SMC firmware never grants the mutex (busy, wedged, or in a failure state), the function returns anyway after the 100 ms poll, without the mutex and without any way to tell the caller: the return type is void, so the failure is unreportable by construction. Every caller then programs SMC registers as if exclusivity vs the SMC firmware were held:

  • trinity_dpm_enable_bapm β€” trinity_dpm.c:1099
  • trinity_dpm_enable β€” trinity_dpm.c:1109
  • trinity_dpm_late_enable β€” trinity_dpm.c:1138
  • trinity_dpm_disable β€” trinity_dpm.c:1158
  • trinity_dpm_set_power_state β€” trinity_dpm.c:1258
  • trinity_dpm_setup_asic β€” trinity_dpm.c:1288
  • (a 7th site at :1300 is inside #if 0 β€” dead code)

This mutex exists precisely to serialize the host driver against the SMC firmware's own accesses during DPM reprogramming; proceeding without it lets host register sequences interleave with SMC-internal ones β†’ corrupted SMC register programs (clock/voltage/power-gating misprogramming). Reliability defect; no memory-safety primitive and no attacker-controlled input.

Why untested (see VERDICT.md)

Requires AMD Trinity hardware with an SMC that refuses the mutex grant. The audit QEMU guest has no radeon GPU; Info-class finding exempt from guest verification per the audit contract. fix.diff (API change: int return + -ETIME, all live call sites updated to bail out) is authored and git apply --check-verified, not hardware-validated.

Fix

fix.diff β€” trinity_acquire_mutex() returns int (0 on grant, -ETIME after backing off the request), prototype in trinity_dpm.h updated, and all six live call sites in trinity_dpm.c check the result and bail out.

VERDICT.md
↓ download raw

DF-5206 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)

  1. sys/dev/drm/radeon/trinity_smc.c:112 β€” void trinity_acquire_mutex(...). A void return means a failed SMC-mutex acquisition is structurally unreportable: no caller can ever know the grant timed out.
  2. trinity_smc.c:117-121 β€” the grant poll runs at most rdev->usec_timeout iterations (100 ms, RADEON_MAX_USEC_TIMEOUT, radeon.h:131); on exhaustion the function silently returns at :122 with the mutex ungranted and the request line left asserted.
  3. All six live call sites (trinity_dpm.c:1099,1109,1138,1158,1258,1288) proceed to program SMC registers immediately after the call, assuming exclusivity against the SMC firmware. The one remaining reference (:1300) is inside #if 0 dead code.
  4. The kernel-side indirect-access race class (DF-2135) was checked and is NOT present on Trinity: tn_smc_rreg/tn_smc_wreg (sys/dev/drm/radeon/ni.c:41-58) hold rdev->smc_idx_lock for the TN_SMC_IND_INDEX/DATA pairs. What trinity_acquire_mutex arbitrates is host-vs-SMC-firmware serialization, which that lock does not cover β€” so the gap is real, not a duplicate of DF-2135/DF-2130.

Why not guest-verified

  • Reachable only on AMD Trinity (HD 7xxx APU) ASICs; requires an SMC firmware that refuses/never grants the mutex (busy or wedged). The QEMU audit guest has no radeon GPU; no ioctl/sysctl/other software path reaches these functions β€” they run at DPM init/enable/disable/set-power-state from the driver's PM core.
  • 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, attempts: 0, code_hash hashes analysis.txt.

Fix

fix.diff (3 files): - trinity_smc.c: int trinity_acquire_mutex() β€” returns 0 on grant, -ETIME on timeout after de-asserting the request and logging; - trinity_dpm.h:131: prototype updated; - trinity_dpm.c: all six live call sites check the result and bail (void functions return, int functions return -ETIME).

git apply --check verified clean against the pristine tree; never applied to sys/; not hardware-validated (fix_status: not_testable).

Escalation assessment

None. No memory-safety primitive; the defect degrades hardware PM sequencing only. No unprivileged input touches this path.

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 (trinity_smc.c + trinity_dpm.h + trinity_dpm.c, six live call sites updated) and verified with git apply --check. 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; git apply --check clean against the pristine tree (verified during authoring); no kernel build attempted since runtime validation is impossible without TN hardware.
↓ fix.diffper-fix-DF-5206

Confirmed kernel references

Detail

Evidence (decisive lines)

analysis.txt (annotated walkthrough); fix.diff (3-file API change: int return + -ETIME, prototype, six live call sites; git apply --check clean); decisive lines trinity_smc.c:112 (void return), :117-121 (poll loop), :122 (silent fall-through); consumers trinity_dpm.c:1099,1109,1138,1158,1258,1288; register def trinityd.h:167; timeout radeon.h:131; negative check ni.c:41-58

PoC changes

No PoC authored: only reachable on AMD Trinity ASICs through the radeon DPM core (init/enable/disable/set-power-state); the QEMU audit guest has no radeon GPU and there is no software-only trigger. Static proof + fix.diff shipped instead.

Verified recommended fix

Make trinity_acquire_mutex() return int (0 on grant, -ETIME after de-asserting the request) and have every live caller bail out on failure (see fix.diff).

Verdict

Static fail-open proven at sys/dev/drm/radeon/trinity_smc.c:112-122: trinity_acquire_mutex() is void, polls the SMC firmware mutex grant (SMC_INT_REQ) for up to 100 ms, and on timeout silently returns without the mutex and with the failure unreportable by construction; all six live call sites in trinity_dpm.c (1099, 1109, 1138, 1158, 1258, 1288) then program SMC registers assuming exclusivity against the SMC firmware. Runtime demonstration requires Trinity hardware with a non-granting 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. The DF-2135/DF-2130 kernel-side idx-lock class was checked and is NOT present on Trinity (tn_smc_rreg/tn_smc_wreg hold smc_idx_lock, ni.c:41-58): this gap is host-vs-SMC-firmware serialization only.