# 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 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.