aboutsummaryrefslogtreecommitdiffstats

FINDING: the PSP ring frame's vmid byte is host-controlled, propagated into the PSP's own memory accesses, and unvalidated

Evidence class: [HW] — Navi 31 / RX 7900 XTX, live sOS/SYS_DRV 0.49.0.54. Status: host-controlled VMID propagation CONFIRMED. Cross-context access NOT achieved.

How this was reached

The fence_addr finding opened the ring frame as a surface no earlier pass had examined. Sweeping all 16 dwords of psp_gfx_rb_frame at dword resolution (hw/frame_sweep.py) found the dword at +32 kills the PSP. That offset is not reserved:

offset field
+24 / +28 sid_lo / sid_hi
+32 byte 0 vmid
+32 byte 1 frame_type
+36.. reserved2[7]

Confirmed on hardware

hw/vmid_test.py <vmid> <frame_type>, one measurement per fresh boot:

frame setting response fence lands heartbeat
vmid default or 0 0x0 yes advancing
vmid = 1, 8, 15, 255 TIMEOUT no frozen
frame_type = 1 or 255 0x0 yes advancing
  1. Any non-zero vmid wedges the PSP — a seventh host-triggerable DoS trigger, requiring work/atx_power.py cycle 25. frame_type is ignored entirely.
  2. The host-supplied value is propagated into the PSP's own memory accesses. The interrupt log during the attempt shows VMC protection faults tagged with the host-supplied VMID, at the PSP's own command buffer address:

IH client=SOC21_IH_CLIENTID_VMC src=(0) vmid=1(1) pasid=0 ctx=[0x8000088, 0x40, 0x0, 0x0]

The PSP fetched its command through VM context 1 because the frame said so. Nothing validates the field, and the failure mode is death rather than an error status.

Fault-level characterisation (strengthens point 2)

hw/vmid_fault.py reads MMVM_L2_PROTECTION_FAULT_STATUS after a single attempt:

control vmid=0 vmid=1
response 0x0 TIMEOUT
FAULT_STATUS 0x0 (no fault) 0x100d33
FAULT_ADDR_LO32 0x0 0x8000088 (= 0x8000088000, the PSP's command buffer)

0x100d33 decodes as a read fault with MORE_FAULTS, WALKER_ERROR and MAPPING_ERROR set. So the PSP issued a read of its own command buffer through VM context 1, and the page-table walk for that context failed. The control leaves the status register at zero, so the fault is attributable to the frame byte and nothing else.

What was NOT achieved

Configuring the MM hub's VM context 1 with the same root page table as context 0 and flushing its TLB does not make vmid=1 work (hw/vmid_proof.py conf: vmid=0 succeeds, vmid=1 still fails). Two obvious explanations are ruled out by measurement:

  • the configuration did not apply — hw/vmid_ctx_verify.py reads the registers back: after programming, context 1 matches context 0 exactly (base=0x1, cntl=0x1fffe07, start=0, end=0xffffffff) on the single MM hub (mm_insts=[0], vmhubs=1);
  • contexts 1-15 disabled at L2 — regMMVM_CONTEXTS_DISABLE reads 0x0.

So either the PSP's accesses are translated through a hub or context set other than the MM contexts, or something further is required that is not visible from these registers. A controlled access under a host-chosen context was therefore not demonstrated, and no memory-corruption primitive follows from this on a bare-metal part.

A first version of this experiment did before/after inside one boot and reported "not a translation-context selector"; that verdict was void because one vmid=1 attempt kills the PSP, so the "after" control inherited a dead PSP. Restructured to one measurement per boot.

Why it still matters

Even a success here would not be a bare-metal primitive, and that is worth stating plainly: the host already owns context 0's page tables and already chooses the addresses in the frame, so directing the PSP through a different host-owned context grants the host nothing it did not have. On a bare-metal part an unvalidated vmid is therefore only a DoS. The concerning shape is a virtualized (SR-IOV) configuration: a frame field naming the translation context, honoured by trusted firmware and unchecked, is the classic confused deputy — a guest naming another guest's context. This is a hypothesis about virtualized parts, not a demonstrated result; SR-IOV is not available here (already recorded as a blocker for the RLCV work).

Suggested fixes

  1. Validate vmid against the context the ring's owner is entitled to; reject anything else with a status.
  2. Fail with a status rather than dying when translation faults.
  3. Reject or ignore frame_type explicitly rather than silently.