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
|
# Hardware validation against an RX 7900 XTX (navi31 / psp_13_0_0)
Card driven directly over a USB<->PCIe bridge (`root@192.168.1.200`, tinygrad,
no amdgpu binding). Four open questions were taken to silicon. Two produced
results, one is **not host-observable**, one needs a build that does not exist
yet. Recorded with the boundary of each stated, because "checked on hardware"
means nothing without saying what the host can actually see.
## 1. Ring status codes: emulator vs silicon -- 7/9 exact
The host's only PSP interface is the GFX ring (`LOAD_TA`, `INVOKE_CMD`,
`GET_FW_ATTESTATION`, ...). Submitting commands and reading `resp.status`:
| command | hardware | emulator | |
| --- | --- | --- | --- |
| `GET_FW_ATTESTATION` (0x0f) | `0x0` | `0x0` | match |
| invalid `0x00` | `0x100` | `0x100` | match |
| invalid `0x55` | `0x100` | `0x100` | match |
| invalid `0x99` | `0x100` | `0x100` | match |
| invalid `0xfe` | `0x100` | `0x100` | match |
| invalid `0x1000` | `0x100` | `0x100` | match |
| `INVOKE_CMD` (0x03) | `0x117` | `0x117` | match |
| `LOAD_TA` (0x01) | `0x5` | `0x116` | differs |
| `UNLOAD_TA` (0x02) | `0x117` | `0x11a` | differs |
`0x100` = unknown command id, `0x117` = bad session/TA, `0x5` = bad parameter.
**The two differences are NOT reportable as firmware divergence.** They are the
only two state-dependent paths in the set: hardware has a live TMR and loaded
TAs, while `fuzz_sos_ring`'s harness is a bare sOS with neither. Inputs were
equalised (`buf_size=0x400` on both sides, after finding tinygrad leaves it 0)
and they still differ, which is consistent with differing *internal* state, not
differing code. Equalising that state is what would make the comparison sound.
### Extraction trap worth keeping
Three successive attempts to read the emulator's status were wrong, and each
produced a confident, entirely fake comparison table:
* `R0` after `tee_cmd_dispatch` -- returned `0x206164`, a code address;
* ring frame `+20` -- gave a clean `0` for every command, which was **my own
input echoed back**. A sentinel (`0xDEADBEEF`) proved sOS never writes
there, and would have been reported as "emulator says 0 for everything,
hardware says 0x100" -- a fabricated divergence;
* the real location is `resp.status` at **offset 864** of the 1024-byte host
command buffer, confirmed independently on both sides (ctypes offset on the
host, sentinel-overwrite in the emulator).
The sentinel is the whole reason this table is trustworthy. Any future
emulator-vs-hardware comparison must sentinel the output location first.
## 2. TA versions: our RAS is the card's RAS, byte for byte. The others are not.
`/lib/firmware/amdgpu/psp_13_0_0_ta.bin` carries four TAs. Comparing each
container body (strip the `0x100` header) against our extracted images:
| TA | card version | card body sha256 (head) | matches our image? |
| --- | --- | --- | --- |
| RAS | 27.0.2.5 | `db2ed53ef8e5c92f8a01...` | **YES, byte-identical to `ras2.elf`** |
| HDCP | 23.0.0.63 | `edab4e4f61c67e922f4f...` | no -- ours is `0x7000`, card's `0x6000` |
| DTM | 18.0.0.22 | `adbf8c8badaab3e4208c...` | no |
| ASD | 33.0.0.217 | `c4c1e5bc9feedccb7fb8...` | no -- ours `0x33000`, card's `0x2f000` |
Full RAS hash, both sides:
`db2ed53ef8e5c92f8a011219191931cd93a6b473d42470a9dd1d4d2056b58385`
This cuts both ways and both directions matter:
* **RAS findings apply to this silicon directly.** The TA we have been
emulating is bit-for-bit the one this card loads.
* **HDCP, DTM and ASD findings are against versions this card does not run.**
That is a scope qualification on the largest body of work in the tree (the
HDCP fuzzing), and it was not previously stated anywhere. Our `vdiff`
directory holds HDCP 23.0.0.49/.70/.82 -- the card runs .63, which we do not
have in the tree at all.
The card's own HDCP/DTM/ASD bodies are extractable from this blob, which is the
cheap way to make the existing harnesses hardware-relevant.
## 3. SYS_DRV RPC opcode 0x3141 -- not decidable from the host
The remaining emitter candidate was the host driver. It is architecturally the
wrong place: the host speaks GFX ring commands, while `0x3141` is a *TA ->
SYS_DRV* RPC internal to the PSP, on a path the host never addresses.
Checked anyway. `amdgpu.ko` (28 MB, decompressed -- the first attempt ran
`lzma` on a `.zst`, silently searched compressed bytes and returned a
meaningless 0) contains the dword 37 times, all in `mov eax,[rax+8];
add eax,0x3141` register-offset arithmetic, none string-like, none an RPC
emission. Controls passed (`amdgpu` 11354 hits, `psp` 696).
So the static negative from the module is real but does not bear on the
question, and the dynamic question is **unanswerable from the host**. The
emitter search result stands as recorded in
`OBSERVATION-SYSDRV-3141-NULL-PTR-NONZERO-LEN.md`.
## 4. sOS gate semantics -- not host-observable at all
SVC permission checks and pointer region validation happen inside the PSP,
between a TA and sOS. The host cannot issue an SVC, cannot observe one, and
cannot read PSP SRAM. There is no host-side oracle for these, so hardware
cannot confirm or refute the `gate.check_ptr` / `gate.check_svc` models. Stated
plainly rather than substituting a weaker check and implying it covered them.
The same applies to the `0xE575B8` capacity blocker: it is SYS_DRV-internal
state. Driving RAS commands over the ring would be *indirect* evidence (success
would imply the context gets populated on a real boot), but tinygrad has no
`LOAD_TA` path and building one is a real piece of work with a wedge risk.
Feasible -- the signed RAS blob is present and matches ours -- but not done.
## Incidental fix
`vram_probe.py` reported `PSP heartbeat -> FROZEN` on a perfectly healthy card:
it sampled the counter three times back-to-back, and the counter ticks ~132/s
(~7.6 ms per tick), so all three reads landed inside one tick. Measured with
500 ms spacing: deltas 68/57/71/69. Patched on the box to space the samples;
backup at `vram_probe.py.bak`. A detector that reports a working card as dead
invites pointless cold-cycling.
|