# amd-psp-re Reverse engineering, emulation and vulnerability research on the **AMD Platform Security Processor (PSP)** as shipped on Navi 31 (Radeon RX 7900 XTX), plus the neighbouring on-die co-processors (SMU, MEC). Split out of a `tinygrad` working tree; `tinygrad` remains a *dependency* for everything that talks to real hardware. --- ## Start here `psp_out/INDEX.md` is the map: which documents are authoritative, which are retracted, which questions are settled negatives that should not be re-run, and the current priority list. Several filenames outlived their conclusions, so **check a document's row in the index before citing it**. The four governing documents are `CONSTRAINTS.md` (standing rules, read before touching hardware), `VULN-AUDIT-WHAT-WE-ACTUALLY-HAVE.md` (the vulnerability register), `ATTACK-SURFACE.md` (the surface map) and `INDEX.md` itself. ## What is actually in here Two things, and it is worth keeping them apart: **1. Static reversing output** (`psp_out/`) — all six PSP firmware modules fully named and structured, a cross-version diffing pipeline over 34+ firmware builds, per-handler annotated analysis, and ~125 findings documents. | module | functions named | | --- | --- | | `asd` | 1562 | | `sys_drv` | 1141 | | `sos` | 527 | | `ras` | 119 | | `hdcp` | 109 | | `dtm` | 14 | | SMU (Xtensa) | 1033 | **2. A working PSP emulator** (`psp_emu/`) — boots the real signed firmware under Unicorn, from the ARM reset vector, with a CCP (crypto co-processor) device model faithful enough that the firmware's own drivers run against it. It currently runs the **whole system in a single boot** (`harness/full_system.py`): sOS → SYS_DRV → HDCP, DTM and RAS, plus the ASD driver arriving over the host ring, with every module's execution attributed by address space *and* live physical page rather than by a shared virtual address. A simulated host can drive the real mailbox/ring command path end to end: ``` host writes mailbox header + ring frame -> IRQ 80 (doorbell) -> bl_legacy_mailbox_loop -> tee_ring_drain -> tee_cmd_dispatch -> tee_load_ta -> bl_load_ta_image (full RSA-PSS/SHA-256 on the CCP model) -> session slot -> tee_ta_command -> TA_InvokeCommandEntryPoint ``` ## Honest status of the security work No host-reachable memory-corruption primitive has been found, and the reason appears to be **structural** rather than "we did not look hard enough": the host-invokable TA surface is confined to three syscalls, and an exhaustive audit of all 37 host-reachable ASD opcodes plus a four-class write-shape scan over a 988-function closure came back bounded. What *was* found: * **One proven heap overflow** — `gp_operation_copy_state` (GP opcode 0x37), byte-exact reproduction in-emulator. **TA→kernel only, with no emitter**, so it is not host-reachable. * **One real firmware bug found by emulation** — `ccp_hmac_sha2` computes HMAC-SHA512 incorrectly (the opad IV memcpy clobbers the last 16 bytes of the inner digest). Dead code in-tree: both callers pass alg=4. * **Four latent unbounded-index / uncapped-counter defects**, all currently inert. * An HDCP subcommand-12 overflow proven to be a **DoS only**. Several earlier conclusions were **retracted** after better evidence; retractions are recorded in place rather than deleted. If a document contradicts a hardware-confirmed note, the hardware note wins. ## Layout ``` psp_emu/ emulator pspemu/ core: CPU/bus/MMU (armmmu.py), CCP model (ccp.py), SVCs harness/ runnable experiments (boot, fuzzers, PoCs, self-tests) tests/ test_smoke.py symbols/ symbol maps consumed by the emulator psp_out/ static analysis *.md findings, audits, vulnerability write-ups *.py analysis tools + hardware probes handlers/ per-command annotated analysis (hand-written) phase3/ vdiff/ sigs/ cross-version symbol maps and diff output handover/ importable symbol sets + import_symbols.py data/MANIFEST.sha256 SHA-256 of every firmware blob results were derived from ``` ## Data policy — what is and is not in the tree **Tracked:** the firmware images themselves (`*.bin`, `*.elf`, 301 files, ~33 MB). The PSP and SMU module images come from `linux-firmware` and publicly mirrored VBIOS images, so they are already public. **Nothing here was decrypted** — the encrypted on-flash stages (`BOOT_LOADER#1`, `TRUSTED_OS`, entropy ~8.0) remain encrypted and unanalysed; sOS is the earliest stage we possess. The whole-flash dumps carry board SKU strings only (`XFX D702_XTX`, `113-31XFSHBS1`) — no serials, MACs or unique identifiers. `data/MANIFEST.sha256` pins the SHA-256 of all 301 images, so a result can be tied to the exact bytes it came from even after a re-dump. **Not tracked:** * `*.i64` — IDA databases (228 MB), regenerable from the blobs plus `psp_out/handover/import_symbols.py`, and they churn on every incidental click. The reversing work itself is preserved as symbol maps in `psp_out/phase3/`, `vdiff/`, `sigs/` and `handover/symbols/`, which is the part with actual value. * `psp_out/decompiled/` — Hex-Rays output mechanically derived from AMD code (26 MB). * AMD-internal register maps (`nv22_a0_memmap.txt`, `map2.txt`) — vendor documents, a different provenance from public firmware. ## Setup ```sh cd psp_emu python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt ./.venv/bin/python tests/test_smoke.py # 5/5 ./.venv/bin/python harness/ccp_selftest.py # 11/11 ./.venv/bin/python harness/verify_real_image.py # 5/5 + 5/5 negatives ``` Hardware scripts in `psp_out/` additionally need `tinygrad` on `PYTHONPATH` and a real GPU; they are the only things here that touch a device. ## Working rules this project runs on Earned the hard way, mostly by being wrong first: * **Validate every detector against ground truth with positive *and* negative controls.** A negative result from an unvalidated oracle is not a result. The TA fuzzer refuses to report "no crashes" until its positive control fires. * **A negative is only meaningful if the harness demonstrably reaches the code** — measure coverage, not the pass counter. * **Verify the emitter, and the values it supplies, before valuing the handler.** Static reachability is not reachability. * **Model SVCs faithfully rather than patching validators**, and prefer running the firmware's own kernel over stubbing it. A stub returning 0 is not neutral; it is a silent lie that surfaces much later. * **Self-consistent tests can be wrong together** — the CCP RSA engine passed 6/6 only because the harness shared its mistake. * **Deterministic execution before bug-hunting.** A flaky harness produces confident wrong negatives. * **Record retractions in place** and propagate them to referencing documents. ## Known tech debt * ~86 hardware scripts in `psp_out/` hardcode an absolute repo path (rewritten on the move from the tinygrad tree). They should resolve paths from a shared module or `$PSP_OUT`; the emulator harnesses already resolve relatively. * 16 scripts hardcode the tinygrad checkout path for imports; should honour `$TINYGRAD_ROOT`. * `psp_out/` mixes offline analysis tools with hardware probes; these deserve separate directories.