aboutsummaryrefslogtreecommitdiffstats

AMD PSP (Navi31 / RX 7900 XTX) — Security Review Findings

Consolidated writeup of a reverse-engineering and vulnerability-hunting effort against the AMD Platform Security Processor firmware on a Radeon RX 7900 XTX, driven end-to-end on real hardware via tinygrad's userspace AMD driver (USB4 / ASM2464PD PCIe bridge).

This is the top-level summary. Each claim links to a detailed doc in the parent psp_out/ directory. Where a claim was later corrected, the correction is noted inline — several early hypotheses were disproved by hardware and are retained as retracted docs so the reasoning trail survives.


1. Scope and what was reversed

Six executable PSP modules were fully reverse engineered (0 unnamed functions each), with structs, prototypes, segment splits and per-function comments:

module role functions ELF base
sos (sOS) PSP microkernel + boot/TEE task 526 0x0
sys_drv (SYS_DRV) privileged system driver 1141 0xE00100
asd (ASD) display/HDCP service driver 1562* 0xD00100
hdcp (HDCP TA) HDCP trusted application 109 0x400100
dtm (DTM TA) display topology manager TA 14 0x400000
ras (RAS TA) RAS/error-reporting TA 119 0x400000

*ASD total includes ~878 vtable veneers; ~684 are real functions.

All symbols/structs/comments are exported in symbols/*.json and re-applied to a fresh IDB with import_symbols.py (see README.md).

Live firmware version: SYS_DRV/sOS 0.49.0.54, ASD 33.0.1.21, HDCP 23.0.0.82. Confirmed by the PSP version register C2PMSG_58 and by matching the container tinygrad loads. (An earlier claim of ".53" was a mistake — a stale cache file was misidentified as the loaded image; see FINDING-LIVE-VERSION-IS-53-NOT-54.md, retracted.)


2. Headline conclusion

No host-reachable, memory-corrupting, or disclosure primitive was found on the current firmware. The host-facing attack surface was fully enumerated and every entry point's handlers audited. The firmware is well-built on the reachable surface: bounded copies, per-use buffer validation, snapshot-then-parse, a hardware-fenced trusted-memory region, and a tight SVC type gate.

The only issues found are either rollback-only and DoS-class, or real bugs that are not host-reachable. Details below.


3. The host-facing attack surface (fully characterised)

entry point reachable? verdict
GFX ring / SVC 0xF2 RPCs yes thoroughly audited, well-defended
INVOKE_CMD to TAs HDCP yes, ASD no HDCP bugs display-gated / canary-blocked; ASD rejects host invokes (0x4)
C2P / AGESA mailbox (~45 handlers) no (runtime NAK stub) boot-only / vestigial on a dGPU — ATTACK-SURFACE.md
TMR / trusted DRAM fenced host reads 0xFF, writes dropped (hardware-confirmed)

Key structural facts established:

  • The 8-command GFX-ring allow-list is effectively dead code. The host ring is drained by bl_legacy_mailbox_loop, which hardcodes ring == 0xFFFF and so bypasses tee_cmd_allowed_on_ring. Confirmed on hardware (cmd 0x26 reaches SYS_DRV). The reachable surface is ~30 SYS_DRV RPCs, not 8. (ATTACK-SURFACE.md)
  • A compromised TA buys very little. sos_svc_dispatch restricts process types 5/6 (TAs) to SVC 0x75 (exit) plus SVC >= 0xF0. A TA cannot issue the memory-mapping or register SVCs at all, on any firmware version. The only type-ungated kernel entry, sos_svc_body_f0 (SVC 0xF0-0xF5), is clean. (AUDIT-TA-KERNEL-BOUNDARY.md)
  • The crypto service (SVC 0xF2 service 1) is DACR-confined to the caller's MMU domain, so crypto-driven overflows stay TA-side by design. (SERVICE1-CRYPTO-ANALYSIS.md)

4. Confirmed bugs and their true reach

4.1 HDCP TA stack overflow — real, host-reachable, but doubly blocked

memcpy(stack[268], sess+0x55, 5*N) with N an attacker byte; overruns the canary (N>=53) and saved LR. Single-path reachable via INVOKE_CMD(0xF). Blockers: (a) requires ctx[0x10]==6, populated by a display DDC read that tinygrad's driver never triggers (no display-core IP); (b) the stack canary is a per-TA-load TRNG value, and every wrong guess hangs the PSP (needs a power cycle) and re-randomises — so no leak and no brute force. Full detail: VULN-HDCP-TA-STACK-OVERFLOW.md, DISTINCT-WRITE-HUNT.md, READ-PRIMITIVE-HUNT-HDCP.md (both HDCP read/write sweeps came back negative — no canary-leak primitive exists in HDCP's reachable surface).

4.2 ASD 0x202 canary leak + 0x301 5 KB write — real, NOT reachable

0x202 returns an uninitialised stack src[4] (containing the ASD canary) to the caller; 0x301 is a clean content-controlled write. Both are excellent primitives, gated only by opcode plumbing: ASD refuses host INVOKE_CMD (0x4 for every id), and no TA forwards 0x202/0x301 in any of 10 archived builds. VULN-ASD-202-CANARY-LEAK.md, DISTINCT-WRITE-HUNT.md.

4.3 ASD 0x308 — validate-then-recompute OOB, gated

Length re-derived from copied bytes after a bounded first copy; the fail-open signature gate is unreachable in practice (hardware: the write branch never fires; the observable effect is a source over-read). VULN-ASD-308-BYPASS.md, VULN-ASD-308-REACHABLE.md.

4.4 HDCP session-state bypass (DTM→HDCP) — state confusion, not corruption

VULN-HDCP-SESSION-STATE-BYPASS.md, VULN-HDCP-BRANCHB-HANGFREE-OOB.md.


5. Firmware rollback and version-diff results

Firmware rollback works with no SPI access. The host supplies sOS/SYS_DRV to the PSP over the bootloader mailbox on every cold boot; substituting a signed older psp_13_0_0_sos.bin from linux-firmware and power-cycling boots the older build. Confirmed on hardware booting 0.49.0.51 (version register read back 0x00310033), reversible, no anti-rollback observed across ~12 cycles. CONFIRMED-FIRMWARE-ROLLBACK-WORKS.md.

A full symbol-port divergence diff across all archived builds (VERSION-AUDIT-STATUS.md) found exactly three security fixes between the oldest builds and .54:

fix reach when rolled back severity
SYS_DRV TMR range guard (sysdrv_tee_map_shared_mem) host-reachable DoS only — see 5.1
sOS CCP MMIO window guard (sos_check_va_window_request) needs a non-TA process near-zero (TAs cannot reach the SVCs it guards) — added in .52
sOS bl_derive_key zeroing TA-facing 4-byte kernel-stack leak to a TA

FINDING-SYSDRV-SHAREDMEM-RANGE-FIXED-IN-54.md, FINDING-SOS-CCP-WINDOW-FIXED-IN-54.md, METHODOLOGY-INLINING-VS-ADDED-GUARDS.md (re-audit of all three; all stand).

5.1 The TMR range primitive — fully characterised as DoS

On <= .51, SETUP_TMR accepts a TMR base overlapping the PSP's protected-DRAM window. Traced end to end on hardware: the accepting path DMAs SPI-ROM firmware regions to base + offset. But (a) on this consumer card those regions are empty (0xFF), (b) the destination is host-chosen but the content is not attacker-controlled, and (c) a 4 GB base sweep found every reachable-safe destination inert (uniform success, PSP alive). The register block that might differ is brick-risk and excluded. Net: DoS only, no disclosure, no useful corruption. VULN-CONFIRMED-TMR-RANGE-ROLLBACK.md.


6. DRAM trust model (hardware-verified)

Everything the PSP reads from host-mutable DRAM is protected:

  • TMR (persistent trusted state): memory-controller fence — host reads return 0xFF, writes are dropped (verified live).
  • Command/ring buffers: re-validated per use (validate_client_range, snapshot-then-parse in tee_cmd_dispatch).
  • TA / IP-FW images: signature-verified, code copied to SRAM before execution (no post-verify TOCTOU).

No path was found where sOS or a TA reads host-mutable DRAM and trusts its contents for a security decision without one of these protections. VULN-CONFIRMED-TMR-RANGE-ROLLBACK.md (fence test), AUDIT-SOS-SRC-POINTER-TOCTOU.md.


7. Retracted claims (kept for the reasoning trail)

Honesty matters more than a clean scoreboard; several hypotheses were disproved:

  • SYSDRV LOAD_IP_FW / VCCC "double-fetch" — the handlers dereference sOS's private snapshot, not host memory. No TOCTOU. FINDING-SYSDRV-VCCC-DOUBLEFETCH-UNDERFLOW.md (retracted), LEAD-SYSDRV-LOADIPFW-DOUBLEFETCH.md (retracted), AUDIT-SOS-SRC-POINTER-TOCTOU.md (the corrected conclusion).
  • "Live version is .53" — a stale cache file mistaken for the loaded image; it is .54. FINDING-LIVE-VERSION-IS-53-NOT-54.md (retracted).
  • C2P mailbox "reachable with corrected encoding" — falsified on hardware; the runtime responder is a NAK stub. ATTACK-SURFACE.md.

8. Tooling produced (reusable)

  • import_symbols.py + symbols/*.json — re-apply all reversed symbols/structs.
  • vport2.py / vport_emit.py / vdiff2.py — cross-version symbol-port diff with two false-positive modes fixed (index window + inline-literal-pool) and a fuzzy threshold. Note: it misses genuinely-new boilerplate (use an opcode census for templated functions).
  • probes/ — hardware probes and PoCs, each self-documenting.
  • psp_recover.py — PCIe power-cycle recovery (needed after a PSP hang and before any rollback/DMA experiment).

9. If continuing

The reachable memory-corruption surface is exhausted. Genuine next directions are changes of goal, not continuations:

  1. Port minimal display-core init to satisfy the HDCP overflow's ctx[0x10]==6 gate (large, but makes 4.1 live — still canary-blocked afterward).
  2. Physical / SPI attacks (flash the VBIOS directly) or the boot chain (SPL/KDB parsing, pre-fence) — different tooling than the runtime harness.
  3. Re-audit older TA builds' reachable handlers for a fixed bug (ASD version diff was negative; other TAs not fully swept per-version).