aboutsummaryrefslogtreecommitdiffstats

FINDING: the PSP bootloader command interface is still live after sOS has booted

Evidence class: [HW] — Navi 31 / RX 7900 XTX, sOS/SYS_DRV 0.49.0.54. Status: interface liveness CONFIRMED. Consequence CLOSED NEGATIVE - component loads are refused post-boot.

The interface

The PSP bootloader loads the sOS components at boot through a mailbox pair completely separate from the GFX ring:

  • regMP0_SMN_C2PMSG_35 — component id in, status out, bit 31 = "bootloader ready"
  • regMP0_SMN_C2PMSG_36 — payload address >> 20 (1 MB-aligned)

Component ids (from the driver headers): LOAD_SYSDRV 0x10000, LOAD_SOSDRV 0x20000, LOAD_KEY_DATABASE 0x80000, LOAD_SOCDRV 0xb0000, LOAD_DBGDRV/HADDRV 0xc0000, LOAD_INTFDRV 0xd0000, LOAD_RASDRV 0xe0000, LOAD_IPKEYMGRDRV 0xf0000, LOAD_TOS_SPL_TABLE 0x10000000, DRAM_LONG_TRAIN 0x100000, DRAM_SHORT_TRAIN 0x200000.

Nothing in this audit had ever touched this interface — all 140 prior passes drove the GFX ring, the TA command space, or the ring control registers.

Confirmed on hardware

With sOS alive, the ring created (_64 = 0x80020000, _71 = 0x10000) and the heartbeat advancing, the bootloader's ready bit is asserted: _35 = 0x80000000.

A latched idle bit would prove nothing, so the test used an invalid component id (0x44000000) with the payload address pointing at a zeroed page — asking the bootloader to answer without asking it to load anything (hw/bl_runtime_probe.py):

_35 _36 _64 sOS
before 0x80000000 0x0 0x80020000 alive
after 0x8000000a 0x80016 0x80020000 alive, heartbeat advancing

The bootloader responded with status 0xa. So the bootloader mailbox is live and processing host commands at runtime, concurrently with sOS and the ring, and rejects a bad component id cleanly rather than hanging.

The consequence: CLOSED NEGATIVE

The question was whether it will act on a valid component load post-boot. It will not.

hw/bl_runtime_load.py submits the exact bytes this boot already loaded for a component, placed 1 MB-aligned with the address in _36, so acceptance would change no content and would only demonstrate that the path acts. Components are limited to those this boot actually loaded (SYS_DRV, SOS, TOC, KDB, SPL, RL) - the harness refuses to run for anything else rather than produce a row:

request payload _35 status sOS after
invalid id 0x44000000 zeroed page 0xa alive, heartbeat advancing
LOAD_SYSDRV 0x10000 161,680 B, the running build 0xa alive, heartbeat advancing
LOAD_KEY_DATABASE 0x80000 5,888 B, the running build 0xa alive, heartbeat advancing
LOAD_TOS_SPL_TABLE 0x10000000 672 B, the running build 0xa alive, heartbeat advancing

Uniform refusal. A valid component load gets exactly the same status as a nonsense component id, so 0xa here means "not permitted in this state" rather than "bad parameter". The bootloader answers at runtime but is state-gated closed: it will not reinitialise a component once sOS is up.

So there is no rollback primitive and no in-place reinitialisation - the interesting case (loading one of the corpus's 31 older signed SYS_DRV builds over the running one) cannot even begin, because the current build is refused. This is correct behaviour, recorded as a boundary that holds.

The anti-rollback question: BLOCKED, not tested

Component loads are refused after boot, but the driver itself drives the bootloader during init, and in that window loads are accepted - that is how the running firmware got there. So the real question is whether the bootloader enforces anti-rollback: would it accept an older signed SYS_DRV during our own boot?

It cannot be tested with the material available, and the reason is recorded so nobody mistakes a confounded result for an answer:

length container version family
live SYS_DRV (this part) 161,680 $PS1 magic at +16 -
corpus, 5 images 116,624 same $PS1 container 128-72-1-0
corpus, 17 images 116,624 same $PS1 container 201-73-2-0

The container format matches, so the images are structurally comparable - but neither corpus family is an older build of the component this part runs, and no historical psp_*_sos.bin for this ASIC is present on the test box. Substituting a corpus image would be rejected for ASIC/family mismatch, and that rejection would say nothing about rollback protection.

Running it anyway would have produced a "rollback blocked" row that the evidence does not support - the same error class as the vacuous capacity detector (pass 18) and the vacuous GBR_IH_SET probe (pass 140). What is needed is an older signed SYS_DRV for this ASIC family; that is an access problem, not an analysis one.

Safety notes for whoever continues

  • LOAD_IP_FW is a known PSP wedge with junk payloads — do not include it in sweeps.
  • The two DRAM training ids would retrain memory on a running system.
  • Recovery from any PSP wedge here needs work/atx_power.py cycle 25; GPU/AM reset is insufficient.

Why it is still worth recording

An interface that can still be addressed after boot is surface that secure-boot reasoning usually assumes is gone, and the distinction matters: the mailbox is reachable but refuses to act, which is a state-machine gate doing its job rather than an absence of surface. That is worth knowing precisely because a future firmware could relax the gate without anyone noticing the mailbox was reachable all along.

The four-row table above is the control behind that claim: the refusal is uniform across a nonsense id and three genuine components, and sOS survives every attempt.