| Mode | Name | Size | |
|---|---|---|---|
| -rw-r--r-- | EVIDENCE-finding10-regseq.txt | 1887 | logstatsplainblame |
| -rw-r--r-- | EVIDENCE-finding10-sweep.txt | 1435 | logstatsplainblame |
| -rw-r--r-- | EVIDENCE-type8-poc.txt | 9620 | logstatsplainblame |
| -rw-r--r-- | README.md | 6364 | logstatsplainblame |
| -rw-r--r-- | dmcub_bringup.py | 19584 | logstatsplainblame |
| -rw-r--r-- | dmcub_hwregs.py | 15169 | logstatsplainblame |
| -rw-r--r-- | dmcub_poc_dirtyrect.py | 7937 | logstatsplainblame |
| -rw-r--r-- | dmcub_poc_regseq.py | 16601 | logstatsplainblame |
| -rw-r--r-- | dmcub_poc_type8.py | 16444 | logstatsplainblame |
| -rw-r--r-- | dmcub_probe.py | 5921 | logstatsplainblame |
| -rw-r--r-- | dmcub_psr_bringup.py | 6441 | logstatsplainblame |
| -rw-r--r-- | dmcub_regseq_sweep.py | 10000 | logstatsplainblame |
dmcub/hw — hardware PoC path for FINDING-2 (Option A: bring up the DMCUB)
Status: RUN ON HARDWARE, 2026-09 (Navi 31 over USB4, tinygrad AM). The DMCUB
was brought up from a cold, uninitialised display block and FINDING-2 was
reproduced: see EVIDENCE-type8-poc.txt. Every script also keeps its offline
--selftest (header parsing, address arithmetic, window layout, register write
order, frame encoding, and a model of the target firmware case).
Results in one line each:
dmcub_probe.py— DCN is powered and DMU registers are live; DMCUB was halted.dmcub_bringup.py— backdoor load is blocked on this part (CW0/CW1 and SEC_CNTL writes are dropped);--phase psp_loadboots the core (SCRATCH0 = 0x3).dmcub_poc_type8.py— 5/5 out-of-rangepanel_instvalues returned planted bytes; confirmed to k = 227.
Why bring-up is needed at all
The rig drives the card through tinygrad's AM userspace driver, which
initialises no display IP (psp_out/ATTACK-SURFACE.md:190 — the same blocker
that stalled the HDCP display-state path). So there is no DMCUB firmware loaded,
no cache windows and no inbox1 ring to submit a command to. This directory ports
dmub_srv_hw_init + dmub_dcn32_* (vendored in ../ref/) onto that stack.
Corrected on hardware: the plan was to avoid the PSP entirely via
dmub_dcn32_backdoor_load, on the assumption that the instruction window is
fetched from framebuffer with no signature check in our path. That is wrong on
Navi 31 — DMCUB_REGION3_CW0_*, CW1_* and DMCUB_SEC_CNTL silently drop host
writes and read back 0, while CW3/4/6, INBOX1_* and DMCUB_CNTL retain them.
The backdoor sequence therefore runs to completion and the core never boots.
The working path is --phase psp_load: GFX_CMD_ID_LOAD_IP_FW with
GFX_FW_TYPE_DMUB and the signed $PS1 payload (status 0), then windows,
mailbox, release — after which DMCUB_SCRATCH0 = 0x3 (dal_fw,
mailbox_rdy). The stock signed image is used unmodified throughout. A hung
DMCUB is revived by re-running psp_load + release; no ATX cycle was needed at
any point, and the rest of the GPU stayed healthy.
Order of operations
# 0. offline, no hardware:
python3 dmcub_hwregs.py --selftest
python3 dmcub_bringup.py --selftest
python3 dmcub_poc_type8.py --selftest
# 1. READ-ONLY gate: is DCN powered, is the DMCUB already running?
python3 dmcub_probe.py --json probe.json
# 2. review the exact register sequence (writes nothing)
python3 dmcub_bringup.py --state-out bringup.json
# 3. bring the core up
python3 dmcub_bringup.py --state-out bringup.json --apply
# 4. the PoC: plant markers, submit type-8, check the reply
python3 dmcub_poc_type8.py --state bringup.json # dry run
python3 dmcub_poc_type8.py --state bringup.json --apply
dmcub_probe.py decides whether steps 2-3 are needed at all: if something has
already brought the DMCUB up, the PoC can inject into the existing ring.
Observed safety (and one PoC artifact)
Reads whose source leaves every mapped window return zeros and do not fault
the core (verified at panel_inst = 300, 1000, 0x4000, 0xFFFF), so the
"unbacked access" hazard this PoC was designed around did not materialise. One
DMCUB hang was seen, after a sweep that planted gate bytes across the whole
fw_state window — i.e. the PoC corrupting live firmware state, not the bug. Plant
only what a run needs.
Why this PoC cannot wedge the card
The leak is memcpy(frame + 4, ctx + 8*panel_inst + 0xaea0, 8) with
ctx = 0x66000300 — inside cache window 6, whose backing VRAM we allocate.
So the PoC:
- plants known 8-byte markers at
ctx + 8*k + 0xaea0and sets the stride-0x7b8 gate byte, both from the host, forkin the derived deterministic range 2..23 (source and gate mapped); - treats
k = 0, 1as the baseline — those are the only legitimate values, since the part has two panels; - refuses any
kwhose source leaves the window (k > 227for this image) unless--allow-wildis passed. That is the unbacked-access casepsp_out/CONSTRAINTS.mdwarns about ("any probe that makes an on-die engine access unbacked memory can take out the SMU"); - re-checks liveness (
DMCUB_SCRATCH0.dal_fw+DMCUB_TIMER_CURRENTadvancing) after every submission and stops at the first anomaly.
Every address touched stays inside a window the host mapped. The evidence is therefore stronger and safer than a wild read: the reply must contain our bytes from an offset the command is never supposed to reach.
Expected result, and what would falsify the finding
| panel_inst | expectation if FINDING-2 is real |
|---|---|
| 0, 1 | firmware's own per-panel confirm colour (no marker planted) — observed 0000000000000000 / 0000000000000100 |
| 2..23 | the planted marker returned verbatim in frame[4:12] — observed |
| 24..227 | leaks too, once a gate is satisfied; the per-index gate is unmapped there, so set [ctx+0xab28] instead — observed |
| >227 | source outside every window: returns zeros, core stays alive — observed |
If indices ≥ 2 come back as zeros, or identical to k = 1, or the frame's
ret_status bit is never cleared, then the index is bounded somewhere the static
audit missed and FINDING-2 must be retracted.
Known unknowns (updated after the run)
- ~~DCN power/clock~~ — resolved: DCN is already powered on this card after ATX-on; DENTIST/DCCG read real values and DMU registers are live. No display power work was needed.
- Register access. Use
adev.rreg/wreg(idx); they pick BAR vs RSMU indirection themselves. Do not hand-roll awreg(14)/rreg(15)window (see the mistake recorded indmcub_hwregs.py).validate()now proves the path with a known-value control plus a scratch write/readback, because every DMCUB register reads 0 before initialisation. - ~~Backdoor load may be fused off~~ — resolved: it is blocked (FINDING-5).
- VBIOS window is a zeroed 64 KB stand-in; no VBIOS command is exercised, but if boot itself reads VBIOS tables this may need the real image.
DMCUB_SCRATCH15is written 0 (the field ispsp_version & 0x001100FF), which is what a non-PSP load implies; if the firmware gates behaviour on it, expect boot to differ.
