<feed xmlns='http://www.w3.org/2005/Atom'>
<title>amd-psp-re.git/dmcub, branch main</title>
<subtitle>AMD PSP reverse engineering, emulator and vulnerability research</subtitle>
<id>https://git.sl0p.foo/amd-psp-re.git/atom/dmcub?h=main</id>
<link rel='self' href='https://git.sl0p.foo/amd-psp-re.git/atom/dmcub?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/'/>
<updated>2026-10-07T17:45:14Z</updated>
<entry>
<title>LSB-zero ARM-mode re-entry: r8 is the one unswept gadget base, route 1 closed by call ordering</title>
<updated>2026-10-07T17:45:14Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-07T17:45:14Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=89b342ab8dd6e8ed16b952836f8f607772a00e6f'/>
<id>urn:sha1:89b342ab8dd6e8ed16b952836f8f607772a00e6f</id>
<content type='text'>
Add.6 proved the saved-LR overwrite on silicon but never evaluated its own aside that P=Q-3 yields
PC = LR &amp; ~0xFF with bit0 cleared, i.e. an ARM-state entry in the same 256-byte block. Evaluated it.

0x405000 ARM-decodes to ldmdaeq r8,{...,sp,...,pc} -- pc=*(r8), sp=*(r8-8), idx-0, executing under
the measured flags, the only aligned entry in the build that loads both pc and sp off a non-stack
base. It matters because Add.132/133/134 closed the ldm class only for bases {r4=ctx, r0/r5/sp} on
the ctx+0x4f carve floor; r8 was never swept, being 0 on the UNLOAD path, and an r8 base sources
from the host command buffer, which is below no floor.

The route does not close the gap, for a reason worth recording. A later PUSH rewrites the word, so
the write and the corrupted return must share a call chain. The FE09 zero-store's live frames yield
only blocks 0x404000 and 0x402a00, neither a pivot. The CodecToken stamp cannot help either, and not
for the frame-destruction reason the report gave: 0x404828 calls 0x404fe8 at 0x404962 and the stamp
fn 0x404b5c at 0x404988, so the frame holding the 0x4050xx call sites is a sibling and already dead.
The UNLOAD shortcut is separately closed -- no function on that path pushes or pops r8.

Qualified the report's 'nothing reads the memory we fully control' to the states we can reach, and
promoted re-entrancy to lead 0. Tooling note recorded: the first call-graph pass used recursive
descent, silently missed the 0x404962 -&gt; 0x404fe8 edge and returned a false EMPTY; rebuilt on a
global over-approximating scan validated against the direct disassembly.
</content>
</entry>
<entry>
<title>Reach the PSR-SU dirty-rect path by state injection; writes stay bounded</title>
<updated>2026-09-28T14:07:42Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:07:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=553bc2fd0e1cef23bdeb133eb7b15125127cf46d'/>
<id>urn:sha1:553bc2fd0e1cef23bdeb133eb7b15125127cf46d</id>
<content type='text'>
The earlier user-reachability test was weak because the DMCUB was idle. PSR is
an eDP feature and this is a desktop card, so a real panel session is not
available.

Driving the firmware's own ABI first, and reporting that it failed:
PSR_SET_VERSION -&gt; COPY_SETTINGS -&gt; ENABLE are all consumed but the state never
leaves 0, read back through GPINT 7 with GET_FW_VERSION (0x07003300) as the
oracle's positive control. Command injection alone does not open a session
without a link.

The state word is instead located at ctx + 0x7b8*panel + 0x730 -- the address
the GPINT handler reports from, and the value the dirty-rect handler compares
against 0x30/0x31/0x44/0x60 -- and injected in host-owned CW6. Three
write/read-back round trips through the firmware's own reporting path confirm it
takes effect, so the session is injected but not merely assumed.

With PSR_STATE3 set the same nine user-reachable coordinate cases produce much
larger state writes (4-8 bytes idle, 20-122 bytes now), proving the deeper path
executes. Dumping where the bytes change rather than how many shows the offsets
never move with the input: two fixed 48-byte arrays at ctx+0x2cc and ctx+0x3ec,
which are exactly 3 rects x 16 bytes, plus small fixed fields, all inside the
fw_state window. No user-reachable OOB write.

Caveat kept: the trace buffer gained no entries and no DCN register programming
was monitored, so the final region-&gt;register stage, which needs a live OTG,
probably still does not run.
</content>
</entry>
<entry>
<title>Scope the DMCUB findings by reachability, and test the user-influenced path</title>
<updated>2026-09-28T14:02:36Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:02:36Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=58449918a9622de83709203bf96ae45a35fdea5b'/>
<id>urn:sha1:58449918a9622de83709203bf96ae45a35fdea5b</id>
<content type='text'>
None of findings 1-10 is exploitable by an unprivileged user: they all need the
DMUB inbox, which is kernel-only under amdgpu, and the hostile field is always
driver-chosen. Record that plainly so the count is not mistaken for ten
exploits, and keep the residual value where it actually is -- FINDING-10 crosses
a PSP-&gt;host confidentiality boundary, FINDING-8 breaks rollback, 1/2/6 are
vendor-fixable bugs in firmware meant to be isolated.

There is a user-influenced surface, traced in the kernel rather than assumed:
FB_DAMAGE_CLIPS is settable by any client with a DRM fd, DRM core validates only
the blob's element size, and amdgpu uses the raw blob rather than the clipping
iterator, computing x2 - x1 into a uint32_t. Arbitrary coordinates therefore
reach UPDATE_DIRTY_RECT verbatim, while the count stays bounded to 3 and the
instance fields stay driver-chosen.

Tested on hardware inside exactly that envelope: nine hostile-coordinate cases,
all consumed, firmware alive, no unexpected CW6 write, and input-dependent (8 vs
4 bytes changed) so the handler is not ignoring them. No user-reachable bug
demonstrated. The DMCUB is idle with no PSR session, so the deep region
programming likely never ran -- the same unlock FINDING-4 needs; type 68 is shown
user-influenced but not yet tested.
</content>
</entry>
<entry>
<title>Audit DMCUB end to end: ISA, command surface, and ten findings</title>
<updated>2026-09-28T13:47:09Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T13:47:09Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=de7dc6e0e485b6d5c363316634de87eba0f7a451'/>
<id>urn:sha1:de7dc6e0e485b6d5c363316634de87eba0f7a451</id>
<content type='text'>
DMCUB had been skipped for months. Identify it as Tensilica Xtensa (windowed
ABI, with in-tree positive/negative controls), correct the load base to
VA = 0x60000000 + file - 0x200 after finding the nested $PS1 header, and build
the tooling to read it: parser, CFG, disassembler with capstone-6 gaps patched,
register map, jump-table and copy-length auditors.

All 24 implemented DMUB_CMD__* types get a verdict, plus the GPINT doorbell
(114-entry table, 12 implemented, all bounded -- a clean negative).

Hardware, via PSP load on the 7900 XTX:
  FINDING-1  [HW] ABM_INIT_CONFIG's unchecked u16 writes ~32 KB past a 712-byte
                  slot
  FINDING-2  [HW] type 8 returns memory at an unbounded 16-bit panel_inst
  FINDING-5  [HW] CW0/CW1/SEC_CNTL are not host-writable; PSP signature enforced
  FINDING-6  [HW] one command wedges the inbox loop (display DoS)
  FINDING-7  [HW] no arbitrary write: writes follow the host-owned CW6 window
  FINDING-8  [HW] auth is neither version- nor ASIC-bound; rollback works
  FINDING-9  [ST] DCN4 LSDMA: host-programmed DMA with only a count != 0 check
  FINDING-10 [HW] REG_REG_WAIT is an arbitrary-address read oracle whose timing
                  leaks PSP-protected memory; a full CW0 sweep shows the TMR
                  image is byte-identical to public firmware, which bounds it

FINDING-3 and -4 are downgraded to latent with the evidence that did not
support them. Liveness must be measured by ring consumption, not SCRATCH0 --
that mistake silently voided ~120 negative measurements and is recorded in
place.
</content>
</entry>
</feed>
