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
|
# MES — API packet surface: dispatch mapping and copy audit
Evidence class **[ST]** static only. Image `fw/gc_11_0_0_mes_2.bin` (gfx11 / Navi 31).
Follows `AUDIT-MES-01.md`, which established the container, RV64 and dispatch bounds
but explicitly left the *packet fields* unaudited. Reference enums read from a real
kernel tree at `/home/user/src/linux-7.2.5/drivers/gpu/drm/amd/include/mes_v11_api_def.h`
(not from memory).
## Packet plumbing, recovered
The ring walk in the dispatcher gives the packet geometry directly:
```
f001914c lui s1, 0xf013b
f0019150 ld a3, -0x4c0(s1) ; global context base
f001915c add a5, a3, a5 ; + 0x3000
f0019160 lwu s0, 0x1a8(a5) ; current slot index
f001916c slli s0, s0, 8 ; * 256 <-- packet stride
f0019170 addi s0, s0, 8
f0019174 add s0, a3, s0 ; s0 = API packet
```
A **256-byte packet stride** matches `MES_API_MAX` (64 dwords). `s0` is the packet
base used by every handler, which is what makes the field offsets below meaningful.
## Two dispatchers — bounds solid, opcode mapping RETRACTED
**Verifiable, read straight out of the code:** there are two indirect dispatch sites
that index an adjacent pointer array in the data image at `0xf0100000`:
| site | index source | bound | table base | entries |
| --- | --- | --- | --- | --- |
| `0xf001919c` | `lw 4(s0)` (whole word) | ≤ 10 | `0xf0100014` | 11 |
| `0xf00196b0` | `andi a5, 0xff` | ≤ 19 | `0xf0100040` | 20 |
Every index is bounds-checked before the `jr`. That part is unconditional.
### What I retract
An earlier revision of this document identified these as `MESAPI_MISC_OPCODE`
(≤ 10 = 10 ops + `MAX`) and `MES_API_HEADER.opcode` (≤ 19 = `MES_SCH_API_SET_HW_RSRC_1`)
on the strength of those bounds being exact fits for the kernel enums. **That
identification does not hold up and is withdrawn.** Disassembling each table target
shows they are not handler entry points:
```
idx 3 ("READ_REG") -> f0016360 jal 0xf0020074 ; a *write* helper, mid-sequence
idx 5 ("SET_SHADER_DEBUGGER") -> f0016404 and a0,a0,s0 ... addi sp,sp,0x30 ; ret ; an epilogue
idx 6 ("NOTIFY_WORK_...") -> f0016448 addi a1,a1,-2 ; seqz a0,a1 ; ret
idx 9 ("LAUNCH_CLEANER_SHADER")-> f00161a0 li s3,1 ; j 0xf0015b10
```
Those are mid-block fragments and function tails, not per-opcode handlers. The
numeric coincidence (11 and 20 entries matching the two enum sizes) is exactly the
kind of thing that makes a wrong model feel confirmed, and it fooled me.
Two structural facts now conflict and I cannot yet resolve them:
* the targets read as continuations that consume `s0/s1/s2/s4` without setting them,
i.e. they belong to the frame of the large routine at `0xf0015a88` (frame `-0x5c0`);
* the dispatchers sit in code with different frames (`-0x90`, `-0x50`), and a `jr`
into another function's frame cannot work.
So either the 36-word array is a *different* switch's jump table that the two
dispatchers also index (unlikely), or my function boundaries are wrong. They are
derived from an `addi sp,sp,-N` prologue scan, and **that heuristic is not reliable
on this image** — which also undercuts `AUDIT-MES-01`'s claim that the 36 entries are
"jump-table targets inside one big routine". Sliding the table base over ±0x80 makes
**0 of 20** entries land on a prologue, so the array is not a table of function
pointers under any offset; the mapping question is genuinely open.
## Register access: how it works, and the question left open
Register access is centralised in locked wrappers, which is useful regardless of the
opcode mapping:
```
f0020074 WRITE(a0 = reg dword index, a1 = value, a2 = selector)
-> lock 0x530 -> slli a0, s2, 2 -> raw write 0xf001f404
f0020358 READ / f00200f8 READ -> same shape -> raw read 0xf001f500
```
`a0` is a **dword index**, scaled by 4 inside the wrapper. 30 call sites (21 write,
9 read); the raw primitives are reached only through these wrappers.
At the most WRITE_REG-looking site, the register index is *not* host-chosen:
```
f001627c add a5, a5, s4 ; a5 = ctx + 0x3000
f0016280 lw a5, 0x1dc(a5) ; register segment base, held in context
f0016284 lw a2, 0x1c(s0) ; packet field -> third arg (selector), NOT the index
f001628c addw a0, s1, a5 ; index = segment base + s1, and s1 = lui 1; addiw 0xa0 = 0x10a0
f0016298 jal 0xf0020074
```
So: **firmware-constant offset over a segment base taken from the context**, with the
packet supplying only the selector. Note the segment bases themselves arrive from the
host, via `MESAPI_SET_HW_RESOURCES.gc_base[] / mmhub_base[] / osssys_base[]` — by
design, the driver tells MES where the register apertures are.
**Open, and explicitly not answered:** amdgpu *does* send `MESAPI_MISC__WRITE_REG`
with a driver-chosen `reg_offset` on gfx11 (`mes_v11_0.c:626`), so a host-offset
register write must exist in this firmware, yet none of the 30 wrapper sites takes
its index from a packet field. Either my sink list is incomplete or the offset
arrives through a wrapper argument. My backward-scan dataflow is **not call-clobber
aware** (a `jal` between definition and use silently breaks the chain), so *absence
of evidence here is weak evidence* and must not be read as "the offset is bounded".
Answering it needs one of:
1. recursive-descent disassembly from real entry points plus call-clobber-aware
dataflow (the DMUB tooling in `../dmcub/dmcub_dis.py` does this; MES has no
equivalent yet), or
2. emulation — MES is RV64, so Unicorn/QEMU can run it, unlike DMUB's Xtensa.
## Copy audit: the DMCUB FINDING-1 pattern is absent
`mes_audit.py <img> copies` is the MES analogue of `dmcub/dmcub_copyaudit.py`, the
pass that found DMCUB's `ABM_INIT_CONFIG` overflow (an unchecked host `u16` length
reaching `memcpy`). memcpy is `0xf0021b28`, identified structurally (`xor a0,a1`
alignment test, `a7 = dst + len`, word loop with byte tail), 33 call sites.
**Result: 31 of 33 sites use a compile-time constant length.** The 2 remaining are
both inside the printf-family helpers `0xf002105c` / `0xf0021144`, where the length
is `s1 >> 38` — a bitfield extract from an internally-built descriptor, reached only
with firmware-owned format data. **No site takes a length from a host packet field.**
Two corrections worth recording, because the first draft of this pass got them wrong:
* `0xf0014804` first reported as host-derived `len = a2 + 0x154`. It is actually the
RISC-V `lui`/`addi` constant pair `0x2a000 + 0x154` = **`0x2a154` constant**. The
detector now folds `lui`/`addi`.
* `0xf001aa1c` first reported `unknown` at a 60-instruction lookback; at 200 it
resolves to a constant `0x80`. The default window is now 200.
Negative control: the same pass on `gc_11_0_0_mes1.bin` finds **0** sites, because
`MEMCPY_VA` is per-image — a control for the address, *not* evidence that MES1 has no
copies. MES1's memcpy has not been located.
## One observation kept as an observation, not a finding
`0xf00147a0` copies **`0x2a154` bytes (172 372) from an internal pointer to a 64-bit
address taken out of the current API packet** (`lwu 4(s0)` | `lwu 8(s0) << 32`),
reached from `0xf0019ba0 → 0xf0019624 → 0xf00147a0` on an event path that logs event
id `0xf` immediately before:
```
f0019b00 li a0, 0xf
f0019b04 jal 0xf0011400 ; event log
f0019b08 jal 0xf00147a0 ; -> memcpy(packet_addr, internal, 0x2a154)
```
So MES will write 172 KB of its internal state to a GPU address the packet names,
and **the packet carries no capacity field** — the size is an implicit ABI contract
between driver and firmware. That is a design note, not a vulnerability claim:
* The submitter is the kernel driver, so this is not a privilege boundary (same
framing as every DMCUB finding — see `../dmcub/AUDIT-DMCUB-03-COMMANDS.md`).
* Whether the write is confined by MES's address translation is **untested**. The
analogous DMCUB question (FINDING-7) resolved *negative* on hardware because the
window hardware confined it, so the honest prior here is "probably confined".
* The exact opcode is **unconfirmed**. The shape matches `MESAPI__AMD_LOG`
(`p_buffer_memory` + `p_buffer_size_used`, no capacity), but the field offsets sit
4 bytes off a naive `sizeof` layout of that struct, so the identification is not
claimed.
## Not done
* No hardware verification of anything here. MES is **not loaded by the tinygrad AM
driver** at all, so testing needs either MES bring-up (GFX is live on this card —
risky) or an amdgpu host. Note `CP_MES` load acceptance was proven in
`AUDIT-MES-01.md`, so bring-up is at least reachable.
* `ADD_QUEUE`'s address fields (`mqd_addr`, `wptr_addr`, `doorbell_offset`,
`trap_handler_addr`) are read and stored but their validation was not traced —
675 sites reassemble 64-bit addresses from structures in this image, and only 2
reach memcpy, so the remaining sinks are register writes and pointer stores that
need per-opcode work.
* `MESAPI_MISC__WRITE_REG` remains **unresolved** (see above): the handler was not
located, and the negative result from the register-sink scan is not trustworthy
because that pass is not call-clobber aware.
* No audit of the 5-entry group, MES1/KIQ, or the doorbell paths.
|