aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/DISCLOSURE-SUMMARY.md
blob: 3579726607d4c0a1c64105c3997f68f30f852c4e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# AMD PSP firmware audit — authoritative consolidated findings

> **Not the authoritative document for this topic.** See **`VULN-AUDIT-WHAT-WE-ACTUALLY-HAVE.md`**,
> and `INDEX.md` §3 for why. Kept for its detail and history.


> **Scope note (2026-09):** this file covers the **PSP** components only. The later
> campaign against the GPU microcontrollers (DMCUB, SDMA, MEC/PM4, MES, SMU) is
> consolidated in `VULN-AUDIT-WHAT-WE-ACTUALLY-HAVE.md`. Reportable items from it, for a
> vendor reading only this file:
> * **DMCUB `ABM_INIT_CONFIG` unchecked u16 -> ~32 KB OOB write** [HW]
> * **DMCUB type-8 unbounded `panel_inst` -> 8-byte leak** [HW]
> * **DMCUB `REG_REG_WAIT` arbitrary-address read oracle** [HW] (reads the signed image
>   through a timing channel; host reads of the same physical address return zeros)
> * **DMCUB firmware rollback** [HW] — authentication is neither version- nor ASIC-bound
> * **DMCUB inbox wedge DoS** [HW]
>
> Reachability framing, stated up front: the DMCUB inbox is **driver-owned**, so these are
> **loss-of-isolation / defence-in-depth**, not privilege escalation from an unprivileged
> process. **None of the GPU-side findings is reachable by an unprivileged process.**
>
> **Twelve-TA round (2026-09), also outside this file:** harvesting AMD's *Windows* driver
> train found twelve TA types absent from `linux-firmware` that this card nonetheless
> **loads and invokes** (GCP, OPM, MERIT, POWERPLAY, XGMI, FP, AUC, WSPE, RAP, OC, CSU,
> TMZ). Two confirmed defects, both in GCP and both host-reachable:
> * **`0xFE0D`** decrements the session refcount and triggers the ASD release on a
>   *rejected* handle — its sibling `FE0B` is correct, which is what makes it a defect;
> * **`0xFE0F`** returns handles for crypto slots that are not live.
>
> Also: **GCP reaches ASD `0x202` and OPM reaches ASD `0x301`**, both previously dismissed
> as unreachable on a scan covering only the four then-known TAs — so that negative is
> false on this part. Full detail in `../fw/AUDIT-NEW-TAS-RE.md` and the two
> `fw/FINDING-GCP-*` files.

**Target:** AMD Navi 31 / gfx1100 (Radeon RX 7900 XTX), PSP `psp_13_0_0`
**Components:** sOS (`sos.elf`), SYS_DRV, ASD, HDCP TA (23.0.0.82), DTM, RAS
**Method:** static RE (IDA 9.4) + Unicorn emulation (`psp_emu/`) + live hardware
(Mac mini M4 host, card behind an ASM2464PD USB4/PCIe bridge, tinygrad AM driver)
**Oracle:** PSP crash record via `C2PMSG` 82–88 (see `CRASH-RECORD.md`)

> **THIS FILE IS THE SINGLE SOURCE OF TRUTH.** The other ~30 notes in this
> directory are working logs that contain *superseded and retracted* claims,
> sometimes in their titles. Where any file disagrees with this one, this one
> wins. Retracted claims are listed in section 4 — do not cite them.

Evidence tags: **[HW]** reproduced on hardware · **[EMU]** proven in emulator ·
**[ST]** static analysis only.

---

## 1. Host-triggerable findings (reportable)

### 1.1 HDCP TA — unauthenticated session-state forgery  **[HW]**
`hdcp_cmd0E_open` commits `state=2` / `active=1` **before** any HDCP handshake or
authentication occurs, and never rolls the state back on failure. An
unauthenticated host can therefore drive a session into "authenticated" state
with a single `INVOKE_CMD`, with no display and no HDCP sink attached.
*Impact:* authentication/state bypass; also the enabler that makes 1.2 reachable.
*Detail:* `VULN-HDCP-SESSION-STATE-BYPASS.md`

### 1.2 HDCP TA — `subcmd12` out-of-bounds write / stack buffer overflow
Handler `hdcp_subcmd12_rxinfo` (`sub_402A88`) copies `5 * msg[2]` bytes (where
`msg[2]` is a full attacker byte) into the 504-byte `sess->rx_ids`, and a second
copy of `5*rx_count` into a stack buffer.
* Write is real, exact extent `5*rx_count+9`, boundary N=101 — **[ST + EMU]**
* Trigger path is hang-free and host-reachable (needs 1.1) — **[HW]**
* N≥53 overruns the stack frame and reliably faults on `__stack_chk_fail`
  (PC `0x40433c`, FAR `0xdeadbeef`) — **[HW]**
*Impact:* **DoS-grade memory corruption.** Escalation is canary-gated: the stack
guard is loader-randomised (decisive HW test — N=56 with a `0x33` fill landing
exactly on the canary still crashes), and no host-reachable guard disclosure
exists anywhere in the loadable-TA surface (see 3.2). The 11-byte OOB variant has
**no hardware-observable effect** (corrupted bytes are masked by the `err_state`
latch) — do not cite it as a hardware-verified write.
*Detail:* `SUBCMD12-RESOLVED.md`, `VULN-HDCP-TA-STACK-OVERFLOW.md`, `WRITE-PRIMITIVE.md`

### 1.3 ASD `0x308` — unauthenticated out-of-bounds READ  **[HW]**
Reachable with a **single `INVOKE_CMD`** — HDCP cmd `0x12` → `sub_403FD0` →
`SVC 0xF2` service 2 → ASD `sub_D13340`. **No session, no display, no handshake.**
The handler re-derives a copy length `a1'` from attacker-supplied header bytes
(`b5:b6:b7`) *after* the initial bounded copy, then uses it for a copy whose
source runs off the end of the 0x1400-byte stack buffer.
Observed: data abort, **DFSR 0x37 (WnR=0 → read)**, DFAR ≈ `0xd5b000`, PC in
`sysdrv_memcpy`. Card requires a PCIe power-cycle to recover.
*Impact:* unauthenticated **DoS** / memory-safety violation.
*Not a write:* see 4.1.
*Detail:* `VULN-ASD-308-BYPASS.md` (read its status banner), `VULN-ASD-308-REACHABLE.md`

### 1.4 `buf_list` bad-descriptor VMC fault storm → card wedge  **[HW]**
`INVOKE_CMD` carries a host-controlled `buf_list` of `{addr,size}` descriptors.
Count (≤64), size (≤0x4000000) and a protected-region exclusion are all correctly
enforced (`sub_3A84`/`sub_3880`/`sub_6CB0`), **but** a descriptor whose address is
*allowed yet unmapped* causes a sustained GPU VMC page-fault storm that wedges the
device (SMU stops responding; `mode1_reset` times out; only a PCIe power-cycle
recovers it).
*Impact:* **DoS.** Not memory corruption — descriptor addresses are GPU-VM
translated, so they can only reach host address space, never PSP-internal memory.

### 1.5 `LOAD_TOC` null-address hang  **[HW]**
`GFX_CMD_ID_LOAD_TOC` with `toc_phy_addr = 0` hangs the PSP command ring
(fence never signals; crash record stays empty → a hang, not a fault). A valid
address with malformed content is cleanly rejected (`0xFFFF0008`).
*Impact:* **DoS.**

### 1.10 LOAD_IP_FW type `0x55`/`0x31`/`0x32` — unsigned, host-reachable register writes  **[HW]**  *(NEW; corrects my own refutation)*
`sysdrv_tee_load_firmware` (RPC `0x1007`) dispatches fw types 49/50/85 into paths that
**return before `verify_image_signature`**. On the consumer ring (`0xFFFF`, hardcoded by
`bl_legacy_mailbox_loop`), type `0x55` -> `buffer_copy_bounded` gives **up to 64
attacker-chosen 32-bit register writes per call**: per 8-byte element the low dword is the
**address** (constrained to `[0x21500,0x21B78]`) and the high dword is the **value, entirely
unconstrained**. Types `0x31/0x32` -> `region_window_open` push caller-chosen
*(16-bit index, 32-bit value)* pairs through an index/data port. Writes are issued by the
PSP (`SVC 0x7C`), so they carry **PSP initiator identity**.
*Window contents:* `bl_set_boot_stage` (sos2 `0x15A0C`) drives `0x215C0` and `0x2161C` via
`svc_A6_reg_rmw` — **both inside the attacker-writable window**; both it and
`buffer_copy_bounded` resolve addresses through `vawin_map_fixed`, so it is the same space.
*Credit:* independently identified as "Route C" in `new/ACTIONABLE-FINDINGS.md`.
*RETRACTION:* `AUDIT-ROUND-NEW-VULN-HUNT.md` previously recorded this path as **REFUTED**.
That was wrong — I never disassembled the `BLS loc_E1008A` branch and turned a "?" into a
verdict. This is the **only** host-reachable, unauthenticated write primitive found in the
project; everything else here is DoS-class, rollback-only, signature-gated, or latent.
*Hardware:* 6 rejecting controls reproduce exactly (window bounds `[0x21500,0x21B78]` confirmed
on silicon by the two boundary probes); address `0x21B40` returns **`0x0`** — the write executed,
sOS alive, card healthy after. `probe_routec_regwrite.py`.
*Capability:* fw types are `VCN0_RAM`/`VCN1_RAM`/`UMSCH_CMD_BUFFER`, so much of the window is
multimedia config the host can likely already program over MMIO. **But the window is mixed:**
an exhaustive scan of all six modules shows it is referenced by exactly two functions —
`buffer_copy_bounded` (UMSCH bases, `0x21B4x`) and sOS's `bl_set_boot_stage`
(`0x215C0`/`0x2161C`). Nothing **reads** either register in any image, so their consumer is
hardware or encrypted code. `bl_set_boot_stage`'s only caller is the C2P/boot mailbox
dispatcher, which is **not host-reachable on this part** (hardware-tested uniform NAK) — so
Route C reaches those two registers from a host-reachable path, with an arbitrary value
instead of a fixed RMW. What the bits gate is unknown and not statically resolvable.
*VCN half (types `0x31`/`0x32`), bounded by later measurement:* the index/data port is
identified from the firmware's own table as **`regUVD_DPG_LMA_CTL`/`_DATA`** — the VCN
**DPG shadow-register** port, not a general register file. Inverting the kernel's
`SOC15_DPG_MODE_OFFSET` shows the 8 firmware-overwritten entries are exactly the VCN ucode
window (`VCPU_CACHE_OFFSET0/1`, `CACHE_SIZE0/1`, `LMI_VCPU_CACHE_64BIT_BAR_LOW/HIGH`,
`CACHE1_BAR_LOW`), and the single bounded entry (index 195) is `VCPU_CACHE_SIZE1` capped at
1 MB — i.e. **the loader pins where the VCN VCPU fetches its firmware**. It leaves the
NC0/NC1 BARs and `LMI_VCPU_CACHE_VMID` host-controlled. On hardware, with VCN un-gated and
its register file proven readable/writable, an **accepted** call carrying 56 host-controlled
entries changed **nothing** (full-aperture diff, drift baseline 1; and direct LMA writes
latch nothing in five variants). So the writes take effect only via a **running** DPG state
machine — the realistic risk is injection into a legitimate VCN bring-up, not a standalone
write. Cite as "completes a register-write loop over attacker-controlled entries", never
"arbitrary VCN register write".
*Detail:* `VULN-LOADIPFW-UNSIGNED-REGWRITE.md`, `FINDING-VCN-GATE-OPENS-VIA-SMU-POWERUP.md`

### 1.9 sOS `SVC 0xF2` — insufficient headroom in the third `msg` range check  *(NEW)*
`sos_proc_switch` (`0x22B4`) validates the caller-supplied `msg` pointer against three
windows. Type 5 (TA) and type 1 reserve `0x1000` bytes of headroom (`sub.w r2,r2,#0x1000`
@`0x238E`). The third window, `[0xD00000,0xE00000)` (ASD's space), reserves only **`0x28`**
(`add.w r2,r6,#0x28` @`0x23C4`) — but the callee, SYS_DRV's `sysdrv_message_handler`
(`0xE087E4`), **reads and writes `0x7C` (124) bytes** at that pointer.
`0x28 < 0x7C`, so `msg` in `[0xDFFF85, 0xDFFFD8]` passes validation yet is accessed up to
**84 bytes past the window end** — at `0xE00000`, which is SYS_DRV's own load base.
*Verification:* `check_procswitch_headroom.py` extracts both constants from the shipped
images (slack from `sos2.elf`, access size from `sys_drv2.elf`) — not from the write-up.
*Reachability (honest):* **latent, not host-triggerable.** The caller must supply the
crafted `msg`; ASD's three task stacks top out at `0xD3D338`/`0xD38330`/`0xD42340` (read from its own
SVC 0x51 descriptors), all ~786 KB below the `0xDFFF85` danger zone, so normal ASD
frames can never reach it and the region is unmapped in the shipped configuration — the likely outcome is a data
abort inside SYS_DRV (privileged DoS), becoming an 84-byte OOB read/write only if that
region is ever mapped. Defence-in-depth / post-compromise, not a remote primitive.
*Novelty:* `AUDIT-TA-KERNEL-BOUNDARY.md` declared `SVC 0xF2` **"clean"** and enumerated
only two of the three windows; this corrects that verdict.
*Detail:* `VULN-SOS-PROCSWITCH-MSG-HEADROOM.md`

### 1.8 ASD SRM **v9** walker — OOB read, walk length decoupled from stored length  **[EMU]**  *(NEW)*
`asd_srm_sub_12DB0` (`0xD12DB0`) fetches the stored SRM length, tests it for
**nonzero only**, then discards it and takes the record count from blob bytes
`[8..9] >> 6` (0..1023). The walk covers `buf[12 .. 5126]`, past the end of the
5120-byte `asd_srm_buf`, and treats never-stored bytes as revocation records.
Distinct root cause from 1.7 (which bounds the offset but not the extent).
*Proof:* real code under Unicorn from a **48-byte** store: 1015 reads beyond the
stored data, 1 past the buffer end, max offset 5122 (`poc_srm_v9_walker_oob.py`).
*Reachability:* same call path as 1.7, and **equally signature-gated** — latent.
*Novelty:* this function had never been analysed in any prior document.
*Detail:* `VULN-ASD-SRM-V9-WALKER-OOB-READ.md`

### 1.7 ASD SRM revocation walker — out-of-bounds read  **[EMU]**  *(NEW)*
`asd_srm_sub_12D4C` (`0xD12D4C`) walks the stored SRM receiver-ID list. The loop
guard `v5 < len-43` bounds the **record offset only**; the body then reads
`buf[v5+9 .. v5+9+5*count-1]` where `count = buf[v5+8] & 0x7F` (<=127), i.e. up to
`v5+643`. With a **legitimate, in-bounds, maximum-size** SRM (`len = 5120`, the true
size of `asd_srm_buf` at `0xD1F038`), the walk reads to offset ~5719 — ~600 bytes
past the buffer, into `asd_srm_stored_len` and following globals. No malformed or
oversized store is needed.
*Reachable (path):* host `INVOKE_CMD` -> HDCP cmd **3** or **4** -> `disp_srm_query`
(SVC 0xF2, opcode `0x302`) -> `asd_srm_query` -> this walker.
*CORRECTED:* an earlier revision of this entry claimed the blob is planted "via the
already host-reachable `0x308` store". That was **overstated**. Both writers of
`asd_srm_buf` (`asd_srm_store` 0x308 and `asd_srm_op_301`) require
`asd_srm_header_check(...) == 0`, i.e. an RSA verify via `asd_srm_verify_t8/t9`.
Only the 0x308 OOB *read* (1.3) is unauthenticated; the *store* is signature-gated.
This finding is therefore **latent** (content-gated), not freely host-triggerable.
*Proof:* real ASD code executed under Unicorn — 120 reads at/beyond +5120, max
offset 5715 (596 bytes past), at the expected 5-byte record stride
(`poc_srm_walker_oob.py`).
*Impact:* memory-safety violation + weak 1-bit disclosure oracle (the OOB bytes feed
`memcmp_be` whose result is returned: 13 = match, 0 = no match). Not control-flow:
the region after the buffer is pointer-free (per 4.2).
*Corrects a prior assessment:* `VULN-ASD-308-BYPASS.md` recorded this same function
as *"v6 bounded by a length cap"*. The cap bounds the offset, not the read extent.
*Detail:* `VULN-ASD-SRM-WALKER-OOB-READ.md`

### 1.6 `err_state` latch — permanent per-slot DoS  **[HW]**
Several HDCP error paths set `sess->err_state = 7/8` with no reset path, so a
single malformed command permanently disables that session slot for the lifetime
of the loaded TA.
*Impact:* **DoS**; also confounds testing (one observation per freshly-opened slot).

---

## 2. Latent findings (real bugs, NOT host-reachable)

### 2.1 SYS_DRV opcode `0x3103` — controlled ~15 KB overflow  **[EMU]**
Two calls to `sub_E0F364`: the first stores an attacker dword as a slot
copy-length (`v3[10]`), the second copies `v3[10]+76` bytes → 3840 OOB writes,
`+0x4048` past the slot table (~15,356 bytes) of attacker-controlled content.
**Definitively NOT host-reachable** — closed by a whole-image scan of **all 89
`SVC 0xF2` call sites across all four loadable TAs** (HDCP 49, ASD 25, RAS 12,
DTM 3):
1. **every** site loads its opcode from an **immediate** — no TA ever forwards a
   host-controlled opcode; and
2. **no `0x31xx` immediate exists anywhere in any TA image.**
**Correction (later round):** the ACL capability check (`sub_E05A24` entry [4], mask
`0x000ffff0` via `SVC 0x76`) was previously cited here as an additional gate. That is
**wrong** — the host-request context demonstrably *holds* that capability (GFX cmd 15
reaches SYS_DRV opcode `0x3127`, inside the same ACL range, and returns real data). The
verdict survives on two stronger grounds:
1. **Emission** — no host path selects a SYS_DRV opcode (GFX commands map to *fixed* rpc
   stubs), and no TA issues `0x31xx` or forwards a host opcode.
2. **The length is pinned at zero** — the slot allocator zeroes the stored copy length at
   `+0x28`, the enqueuer never writes it, and the only emitter (an sOS service task) hands
   `0x3103` the very buffer `0x3102` filled from that same field. It is a fixed point at
   zero, so the copy is invariantly 76 bytes. The same argument closes the `0x3102`
   direction (sOS task-stack overflow).
Arming the overflow requires an actor who can already emit arbitrary SYS_DRV opcodes, i.e.
one who has already compromised sOS. Report as latent / defence-in-depth only.
*Detail:* `VULN-SYSDRV-3103-OVERFLOW.md`

### 2.2 ASD opcode `0x202` — uninitialised-stack canary disclosure  **[EMU]**
`sub_D14948` copies a never-initialised `int src[4]` to the caller's output
buffer. Priming with op `0x308` first (whose 0x1400-byte frame saves the guard)
deterministically discloses `__stack_chk_guard` at `output+0x18` — proven in the
emulator with distinct sentinels.
**Not host-reachable:** ASD rejects direct `INVOKE_CMD` (GFX status 4 — it is an
RPC-only/system TA), and no host-reachable TA forwards opcode `0x202` (confirmed
by the same 89-site scan). *Detail:* `VULN-ASD-202-CANARY-LEAK.md`

### 2.3 Weak stack-pointer ASLR in sOS  **[ST]**
`sos_proc_switch` (`sub_22B4`) derives the per-process stack pointer from a
textbook MSVC LCG (`state = state*0x343FD + 0x269EC3`, state @ `0x80F0`):
`SP = stackbase + 0x4000 - (LCG & 0xF8)` → only **31 distinct 8-byte-granular
offsets (~5 bits)**, from a trivially predictable PRNG. This is *not* the stack
canary (that is loader-supplied and unpredictable), but it is a genuine ASLR
weakness. *Detail:* `GUARD-INIT-CHAIN.md`

---

## 3. Significant negative results (hardening that held)

These matter for a report: they bound the attack surface and show the failures
above are isolated rather than systemic.

**3.1 Every other host-reachable input path is correctly validated [HW]:**

| Surface | Result |
|---|---|
| `LOAD_TA` `$PS1` header | Signature/hash-verified **before** parse; every mutated size/offset field → clean reject (status 52) |
| `buf_list` structure | count ≤64 (status 42), size ≤64 MB (status 5), 8 MB protected region excluded, pool-bounded bookkeeping |
| DTM cmds 2/3/4 | Bounded (`a1[9] ≤ 0x17`, fixed-size fills) |
| ASD `0x100`/`0x101` | Index-bounded (≤63), source validated, fixed-offset copies |
| ASD `0x300` | Correctly bounded (`if (*v9 > a2) return 2`) — this is exactly the check `0x308` is missing |
| HDCP all subcommands | Bounded; `subcmd17`'s length is a byte load (`LDRB.W`), max `dst[257]` — 11 bytes short of the canary. 400-iteration HW fuzz: clean |
| `PROG_REG` (cmd 0x0B) | Table-validated (`reg_id` 3–15 accepted, others rejected); no OOB indexing |
| Session management | Liveness-validated — freed/double-unload/bad/cross `session_id` all rejected (279). **No UAF** |
| `GET_FW_ATTESTATION` | Robust to malformed output buffer addr/size |
| TA image sections | Segment content is AES-decrypted + hash-verified per segment |

**3.2 No host-reachable stack-guard disclosure exists.** All four loadable TAs
were audited: HDCP handlers write status ints only and `ta_memclr` their builder
buffers; DTM and RAS are status/telemetry only; every host-reachable ASD op is
length-bounded or writes only specific caller fields. The one real leak (2.2) is
unreachable.

**3.3 RAS error injection is non-functional on this SKU.** The RAS TA loads and
accepts cmds 0–4, but its command handlers are runtime function pointers
installed by `ras_drv` — and this card's firmware bundle contains **no
`PSP_FW_TYPE_PSP_RAS_DRV`** (`sos_fw` = SYS_DRV, SOS, TOC, KDB, SPL, RL only).
All RAS invokes return status 15. The classic RAS "inject error at host-supplied
address" write primitive therefore does not exist here.

---

## 3a. Coverage corrections (scope of earlier "clean" verdicts)

Two earlier verdicts were narrower than their wording suggested. Neither was wrong for what
it tested; both were over-generalised.

* **"C2P mailbox NOT host-reachable (uniform `0x8000000a` NAK)"** tested **C2PMSG_35/36**
  only. The **TSU flash/update mailbox is at C2PMSG_115/116** (since probed — see the TSU
  note in section 2; the mailbox is host-reachable but the disclosure chain behind it is
  **not**, on every path tested). Its
  host-access bits are opened by the firmware itself — verified in IDA: `|= 0x3C0` on the
  permission word in **`tsu_mailbox_task`** (`0xE1CAA4`) and `irq_enable_mask` (`0xE0CE46`),
  alongside `|= 0xF` (`init_mailbox_state`), `|= 0xC00`, `|= 0x3000`, `|= 0xC000000`.
  `tsu_mailbox_dispatch` (`0xE1C948`) is a real command dispatcher (cmd byte in BYTE2,
  unknown -> `0xFFFF000A`).
* **"SMU mailbox surface audited on hardware — CLEAN"** drove the **driver port**
  (C2PMSG_66/82/90). The **test port (72/96/98)** and its register-file message ids were
  never touched.

## 4. Retracted claims — DO NOT CITE

These appeared in earlier notes (sometimes in file *titles*) and are **wrong**:

1. **"ASD 0x308 gate fails open → controlled OOB write."** RETRACTED. The gate
   `sub_D134C4` performs a real check (accepts only `0` or `0xFFFF000A`). A
   **480-combination header fuzz on hardware** produced **zero** write-branch
   executions (`*a3` stayed `0xFFFF`, `*a4` stayed `0` in every case). Reaching
   the write needs a valid RXID MAC, which is unforgeable. `0x308` is a **read**
   fault (WnR=0) → DoS.
2. **"Function pointers live in the ASD 0x308 OOB region."** RETRACTED — false
   positives from a crude BLX-proximity heuristic. A rigorous load-then-call scan
   over `[0xd1f038,0xd2f038)` returns **zero**; the region is a pointer-free
   receiver-ID data buffer plus counters.
3. **"ASD 0x308 is an RCE primitive."** RETRACTED — follows from 1 and 2.
4. **"subcmd12 branch-B write is hardware-verified."** OVERSTATED — the write is
   [ST+EMU]; only the *trigger path* is [HW]. The corrupted bytes were never read
   back and have no hardware-observable effect.
5. **"The stack guard is the build-time placeholder `0x33333333` (→ predictable
   → RCE)."** RETRACTED by decisive HW test (N=56 fill landing exactly on the
   canary still faults). The guard is loader-randomised.
6. **"SYS_DRV 0x3103 may be host-reachable."** CLOSED as **not** reachable — see 2.1.

---

## 5. Overall assessment

The host-facing PSP surface is **broadly well-hardened**: length/count/size bounds
on essentially every copy, signature-verify-before-parse on TA images,
cryptographic gating on the one sensitive OOB write, table validation on register
programming, and session liveness checks. Three independent full-surface sweeps
found **no host-triggerable memory-corruption primitive beyond the canary-gated
`subcmd12` overflow**. A later targeted pass over the SRM/revocation opcode family
added one further host-reachable memory-safety defect (1.7, an OOB *read*), which
also corrects an earlier "bounded" assessment of that walker.

The defects that do exist cluster into two classes:
* **Missing state rollback** (1.1, 1.6) — commit-before-verify and latched error
  states.
* **Length re-derivation after validation** (1.2, 1.3) — a length is validated,
  then *recomputed from attacker-controlled data* and used for a second copy.
  This is the single recurring root cause and the most useful thing to fix:
  `0x300` gets it right (`if (*v9 > a2) return 2`) and `0x308` does not.
* **Bounding the index instead of the access extent** (1.7) — the SRM walker
  validates the record offset but not `offset + 9 + 5*count`.

Highest-value hardening recommendations, in order:
1. Re-validate any length that is re-derived from attacker data before use
   (ASD `0x308`; HDCP `subcmd12`).
2. Do not commit session state before authentication succeeds; add rollback
   (HDCP cmd `0x0E`), and provide a reset path for `err_state`.
3. Replace the MSVC LCG feeding stack-pointer ASLR with the hardware TRNG.
4. Bound/ratelimit `buf_list` descriptor faults and reject a null `LOAD_TOC`
   address so a malformed command cannot wedge the device.