PSP ring frame (psp_gfx_rb_frame, 64 bytes) — complete field map
Evidence class: [HW] — Navi 31 / RX 7900 XTX, sOS/SYS_DRV 0.49.0.54. Every row measured, each with a control re-run after the mutation so a wedged PSP cannot manufacture results.
The ring frame is entirely host-written and was not examined at all until late in the memory-safety hunt; every earlier pass varied command content. This is what each field actually does on this firmware.
| offset | field | behaviour | probe |
|---|---|---|---|
| +0 / +4 | cmd_buf_addr_lo/hi |
used. 0xFFFFFFFF or MC 0 ⇒ PSP wedge (VMC fault flood). TMR-range destinations rejected. |
frame_sweep.py, aperture_test.py |
| +8 | cmd_buf_size |
ignored entirely — 0, 1, 0x1001, 0x100000, 0x7FFFFFFF, 0xFFFFFFFF all complete 0x0, no guard writes. Command size is implicit. |
frame_cmdsize.py |
| +12 / +16 | fence_addr_lo/hi |
used as a write destination. Not range-checked against the TMR (unlike cmd_buf). 0 ⇒ PSP wedge. |
fence_tmr2.py, fence_land.py |
| +20 | fence_value |
the host-chosen value written to fence_addr. |
fence_write.py |
| +24 / +28 | sid_lo/hi |
no observable effect. | frame_sweep.py |
| +32 b0 | vmid |
honoured as the VM context for the PSP's own accesses, unvalidated. Any non-zero value ⇒ PSP wedge, with an L2 read fault (0x100d33) at the PSP's own command buffer tagged with that VMID. |
vmid_test.py, vmid_fault.py |
| +32 b1 | frame_type |
ignored (1, 255 harmless). |
vmid_test.py |
| +34..35 | reserved1 |
no effect. | frame_sweep.py |
| +36..+60 | reserved2[7] |
no effect. | frame_sweep.py |
The ring index
Separate from the frame: the write pointer C2PMSG_67 is bounded to the ring size
(hw/ring_wptr_oob.py). A valid frame planted one ring-length and 1 MB past the end is not
executed, and the PSP stays healthy — out-of-range indices are ignored rather than
faulting. That graceful handling is exactly what fence_addr = 0 and a non-zero vmid lack.
Ring creation
The base/size given at ring creation are well validated — hw/ring_create_fuzz.py:
size 0/1/0xFFFFFFFF rejected 0x4, size 2 GB rejected 0x5, and a base inside the
trusted region rejected 0xa. The one gap is alignment: base + 1 is accepted and then
wedges on first submit (FINDING-PSP-RING-BASE-ALIGNMENT-UNCHECKED.md, eighth DoS trigger).
Security summary
- Two unvalidated host-controlled fields:
fence_addr(a write destination not checked against the trusted region, wherecmd_bufis) andvmid(a translation context used by trusted firmware). - Three of the seven known DoS triggers live in this structure: bad
cmd_buf_addr,fence_addr = 0, and any non-zerovmid. - No memory corruption: the fence write does not land in the TMR (dense functional test,
fence_spray.py), and a host-chosen VM context grants the host nothing on a bare-metal part because it already owns its own page tables and the frame's addresses.
Details: fw/FINDING-PSP-FENCE-ADDR-UNVALIDATED.md,
fw/FINDING-PSP-RING-FRAME-VMID-UNVALIDATED.md, fw/REPORT-PSP-DOS-TRIGGERS.md.
