<feed xmlns='http://www.w3.org/2005/Atom'>
<title>amd-psp-re.git/hw, branch main</title>
<subtitle>AMD PSP reverse engineering, emulator and vulnerability research</subtitle>
<id>https://git.sl0p.foo/amd-psp-re.git/atom/hw?h=main</id>
<link rel='self' href='https://git.sl0p.foo/amd-psp-re.git/atom/hw?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/'/>
<updated>2026-10-09T14:11:31Z</updated>
<entry>
<title>Record PSP security investigation artifacts and probes</title>
<updated>2026-10-09T14:11:31Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-09T14:11:31Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=bc43149d914325e15dc0490dee9d982e21287623'/>
<id>urn:sha1:bc43149d914325e15dc0490dee9d982e21287623</id>
<content type='text'>
</content>
</entry>
<entry>
<title>crypto ROP harness: multi-op (two-svc) validated on silicon + usage/status doc</title>
<updated>2026-10-08T11:50:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T11:50:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=e76fd3a5d5bbabdc77ce9d559bee1a93ce4eac72'/>
<id>urn:sha1:e76fd3a5d5bbabdc77ce9d559bee1a93ce4eac72</id>
<content type='text'>
gcp_rop_crypto2.py: issues TWO svc 0xf2 service-1 calls inside ONE FE0A-hijacked chain (the FE0A-&gt;ROP
hijack is one-shot per boot, so stateful sequences must be in one chain). Hardware-validated:
- create SHA256 -&gt; handle 0x10 (status 0, dumped back)
- create -&gt; create -&gt; handles 0x10 then 0x11: multi-op sequencing + crypto CONTEXT STATE PERSISTENCE
  proven on silicon
- create -&gt; digest(0x08): input 32B placed in the GCP-window scratch and dumped back correctly; the
  transform returned 0xffff0006 because op 0x08 reads ctx[5]&gt;&gt;3 (digest len) which create leaves 0
  (it stores diglen at ctx[3]) -- op 0x08 expects the operation 'started' (ctx[5] set by a
  digest-init step). Precisely characterized via sub_E0B148/sub_E1CDEC/sub_E055D0; documented as the
  next lead.

Added fw/HARNESS-GCP-CRYPTO-ROP.md (mechanism, message layout, AllocateOperation tuple, buffer-
direction table off_E0AB70@0xe25850, validated results, the digest state-machine lead) and mirrored
hw/gcp_rop_crypto.py, hw/gcp_rop_crypto2.py + runners into the repo.

The harness is clean, parameterized, and hardware-validated driving real crypto operations (context
creation with state persistence) with input/output buffer plumbing and result dump-back.
</content>
</entry>
<entry>
<title>add clean GCP-&gt;SYS_DRV crypto ROP harness (hw/gcp_rop_crypto.py)</title>
<updated>2026-10-08T11:15:06Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T11:15:06Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=157cc02d58e24eb35efb9ee6e3e13a705984e8d1'/>
<id>urn:sha1:157cc02d58e24eb35efb9ee6e3e13a705984e8d1</id>
<content type='text'>
Parameterized crypto-op harness built on the proven stack-migration svc 0xf2 service-1 chain. Takes
OPCODE + MSGSPEC (JSON dword_index-&gt;value) to drive the SYS_DRV GP-TEE crypto dispatcher (sub_E1DC30,
opcodes &lt;0x1000, ACL-free) from a ROP'd GCP TA, and adds a SECOND in-chain memcpy that dumps the
post-op message region (STACK_DST+0x300, 0x80 bytes) back to host-readable params+0x3010 -- so inline
results (e.g. a created context handle at msg[14]) are observable. Status r0 exfil'd to params+0x3000;
graceful-return marker to params+0x3100.

Hardware-validated mechanics (op 0x01 create, algo 0x50000004): graceful marker 0x5a (clean return,
ring survived), dump correctly echoed M[0]=opcode and M[15]=algo, svc reached the dispatcher (status
0xffff000a = handler reject of that algo, NOT ACL 0xffff0001). Confirms: harness drives crypto ops and
dumps output buffers back. Algo IDs are standard GP TEE (dword_E023FC=0x50000005=SHA384, statically
init'd); sweeping recognized algos to land a successful create. hw/crypto_run.sh + crypto_algo_sweep.sh
are the runners.
</content>
</entry>
<entry>
<title>[HW] round-2 sweep: OTP-read (0x6001), MMIO-write (0x6002), reg-access (0x70xx) all ACL-rejected from GCP</title>
<updated>2026-10-08T09:34:50Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T09:34:50Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=1cc91efcf68afacceea646d776b419a1326d0b4c'/>
<id>urn:sha1:1cc91efcf68afacceea646d776b419a1326d0b4c</id>
<content type='text'>
Prompted by FINDING-SYSDRV-ACL-RANGE-TABLE.md (which documents the 0x6000/0x7000/0x8000 families the
first sweep missed -- 0x6001 otp_block_read = FUSE read, 0x6002 = SMN MMIO-write, 0x70xx = TA reg
access). Swept them from GCP: 0x1021 (free) REACHED; 0x6001/0x6002/0x7001/0x7002/0x8001 all
0xffff0001 ACL-REJECTED.

So all 8 distinct ACL range masks (0x200/0x40/0x1000/0xffff0/0x2000/0x4000/0x8000/0x10000) are denied
to the GCP caller -- GCP's off_6428 entry grants none of the gated ranges. Definitive: from a ROP'd
GCP TA, svc 0xf2 service 1 reaches ONLY the crypto dispatcher (&lt;0x1000) and free high opcodes
(outside all 10 ranges). The overflow (0x3100), OTP/fuse read (0x6001), MMIO-write (0x6002), and reg
access (0x70xx) are ALL unreachable from GCP.

Definitive answer to the fuse question: fuse/OTP read 0x6001 is ACL-gated, NOT reachable from GCP.
Strategic note: the ACL-table doc shows POWERPLAY emits 0x60xx and RAS emits 0x70xx, so those TAs
hold the caps GCP lacks -- reaching OTP-read/MMIO-write needs compromising POWERPLAY or RAS, not GCP.
GCP ROP is a dead end for the gated SYS_DRV surface. Updated the audit doc.
</content>
</entry>
<entry>
<title>[HW] measured svc 0xf2 SYS_DRV reachability: ALL ACL ranges rejected, only free opcodes pass</title>
<updated>2026-10-08T09:29:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T09:29:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=896b1c2f0cd54003a9edc63a5887b027620b1dff'/>
<id>urn:sha1:896b1c2f0cd54003a9edc63a5887b027620b1dff</id>
<content type='text'>
Swept one opcode per ACL range through the stack-migration chain on silicon. 0x1021 (free) REACHED
(status 0x0); 0x3103/0x3005/0x2005/0x1105/0x4001 (ranges 0x3100/0x3000/0x2000/0x1100/0x4000) all
0xffff0001 ACL-REJECTED. So off_6428 grants the caller NONE of the range masks.

This settles the confused-deputy accurately: svc 0x76 checks the REAL caller's (GCP type-5) minimal
caps, not SYS_DRV's -- the opcode-range ACL is NOT fooled by the nested RPC. The confused deputy only
defeats sos_proc_switch's permission bit (dest-1 exempt); the ACL stands.

Accurate reachable SYS_DRV surface via svc 0xf2 service 1: crypto opcodes &lt;0x1000 (sub_E1DC30, no
ACL) and free high opcodes &gt;=0x1000 outside every ACL range (sub_E105A8, incl. the ASD-emitted
0x1021/0x103B.. space). The 10 ACL ranges are UNREACHABLE. Consequence: the #9 overflow
(0x3102/0x3103, range 0x3100) is confirmed UNREACHABLE on silicon -- that escalation is closed. The
audit now targets the reachable set (crypto dispatcher + free high opcodes), much of which overlaps
fuzz_sysdrv's ASD-emitted coverage. Updated fw/AUDIT-GCP-ROP-TRIGGERABLE-SVCS.md; harness
hw/gcp_rop_sweep.py.
</content>
</entry>
<entry>
<title>[HW] CORRECTION: version is right (.54 confirmed), but the opcode ACL rejects 0x3103 on silicon</title>
<updated>2026-10-08T09:18:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T09:18:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=093401b0d9707669ee571b82204378759214e7f9'/>
<id>urn:sha1:093401b0d9707669ee571b82204378759214e7f9</id>
<content type='text'>
The operator's version question prompted verifying the live SYS_DRV: C2PMSG_58=0x00310036 = 0.49.0.54
= sys_drv2.elf, so all the .54 offsets ARE valid for the card. Not a version error.

Ran the service-1/0x3103 chain on hardware. The transport works: the ROP migrated to the GCP stack,
issued a real svc 0xf2 service 1, and got back a SYS_DRV error code 0xffff0001 (not a sos_proc_switch
reject 0x2f/0xe) -- so the call passed the svc 0xf2 gate (dest-1 exempt, SP+msg in the type-5 window)
and reached SYS_DRV's dispatcher. The svc-issuing ROP machinery (stack migration, exfil, graceful
return) all worked and the ring survived.

But 0xffff0001 = dword_E05A70, returned by sub_E05A24, the high-opcode ACL: it finds the {lo,hi,mask}
range for the opcode, issues svc 0x76 (caps check), and returns 0xffff0001 if the caps check fails.
sub_E105A8 propagates it (if ACL!=0 return it, skip dispatch). 0x3103 is in range 0x3100-0x31ff
(mask 0xffff0) and its svc 0x76 caps check FAILED for the GCP-&gt;service1 nested-RPC caller.

This retracts the emulator-based 'confused deputy reaches the overflow' claim for 0x3103. The
emulator demos called sub_E087E4 directly, so their svc 0x76 ran in a context that passed; the real
nested RPC evaluates caps that do NOT grant the 0x3100 range. The emulator never ran sos_proc_switch
and was infidelitous on exactly this gate.

Stands: GCP code exec; ROP issuing arbitrary svc 0xf2 (migration+exfil+graceful return); the call
reaching SYS_DRV past the svc 0xf2 gate. Blocked: 0x3102/0x3103 are ACL-gated out. Next: measure
which opcode ranges' masks pass for this caller (one opcode per range, read exfil status), and look
for an exploitable handler at an allowed opcode (incl. the freely-allowed non-range space ASD uses).
</content>
</entry>
<entry>
<title>[HW] svc 0xf2 issued from GCP ROP via stack migration; ASD survive-check passes</title>
<updated>2026-10-08T09:07:57Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-08T09:07:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=4d28e602394992e5ed349b6ce34debc9db3ec4c7'/>
<id>urn:sha1:4d28e602394992e5ed349b6ce34debc9db3ec4c7</id>
<content type='text'>
Ran the operator's stack-migration chain on silicon as the service-2 (ASD) survive-check. Result:
FE0A status 0x240 (planted), no fault; graceful-chain marker 0x5a at +0x3100 (migration+graceful ran);
exfil'd svc status 0x4 at +0x3000 (ASD returned 0x4); follow-up FE07 ta=0 (ring survived, no ATX).

So from a GCP ROP chain on hardware: pivot into the command buffer, memcpy the svc sub-chain to the
in-window GCP stack (0x410000), mov sp to it (SP now in-window), pop r0=2/r1=msg (msg in-window),
svc 0xf2 -&gt; ASD returns 0x4, str r0,[host_buf] exfil, graceful return. The svc went through the REAL
sos_proc_switch gate (SP and msg both in the type-5 window) -- the link the emulator never exercised
-- and the ring survived.

Complete svc-issuing + exfil + graceful-return machinery validated on silicon. Next: r0=1 (service 1
= SYS_DRV) + 0x3103 message, svc gadget entered at bare svc 0x4061ca to keep r0=1 -- the
confused-deputy confirmation. Harness hw/gcp_rop_svc.py.
</content>
</entry>
<entry>
<title>[HW] graceful ROP: chain runs, TA completes with chosen status, ring survives without ATX</title>
<updated>2026-10-07T22:12:53Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-07T22:12:53Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=0675bbad945a1805de281e1c7551781bd4cdb911'/>
<id>urn:sha1:0675bbad945a1805de281e1c7551781bd4cdb911</id>
<content type='text'>
Addendum G wedged on a deliberate fault; this returns cleanly. The write/pivot only corrupts the
stack at/below 0x41bfbc, leaving 0x404828's real frame (0x41bfc0+) pristine, so restoring SP to
0x41bfc0 and reaching 0x40498c lets the TA unwind normally. The return uses the 0x40498c error
branch (cbnz r0), because 0x4049d8 reloads r4 from a literal and needs only r0/r6 -- immune to the
register shuffle the pivot causes.

Chain: pivot into the buffer; strb x5 to (a) write the observable marker 0x5A to params+0x3000 and
(b) rewrite the popped-PC stack slot 0x41bfbc to 0x0040498d; pop{r0,..} to set r0=0x240; final
mov sp,r4;pop to put SP=0x41bfc0 and pc=0x40498d. Then 0x40498c -&gt; error path -&gt; 0x404926 pops
0x404828's real frame -&gt; returns to 0x40433c -&gt; TEE.

Measured: FE0A status=0x240 (our planted r0, returned to the host), FSR=0 no fault, marker present,
and follow-up FE07 returns 0 with NO ATX cycle. Three confirmations: marker = strb ran; status=0x240
= the whole real-stack return path ran and the TEE wrote our chosen value; FE07 ok = ring not wedged.

So the primitive is complete and repeatable: chosen PC, SP pivot into the host buffer, arbitrary ROP
with chosen write-what-where, and clean resumption with a host-chosen status. Remaining for
weaponisation: word-granular store (strd gadgets need controlled r0..r2) and a concrete payload;
code injection stays out (no W+X page). Updated both documents.
</content>
</entry>
<entry>
<title>[HW] a working ROP chain: 4 self-chaining strb gadgets write R0P! to the command buffer</title>
<updated>2026-10-07T22:01:59Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-07T22:01:59Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=5ade2e86a57ddae9cbd22433d8c09fb3b5ed3d49'/>
<id>urn:sha1:5ade2e86a57ddae9cbd22433d8c09fb3b5ed3d49</id>
<content type='text'>
Addendum F pivoted SP into the command buffer; this runs an actual chain from there with a readback
effect, not just a terminal fault.

Chain: first pop sets r4=params+0x2000, pc=0x40371d; the pivot gadget mov sp,r4;pop{..pc} moves SP
to params+0x2000; then the self-chaining gadget 0x402266 strb r4,[r5];pop{r4,r5,r6,r7,r8,pc} runs
four times, each fed a (byte,address) pair and the next pc from the buffer, then a terminal sentinel
pc 0x00bada54.

Result on silicon: marker @ params+0x3000 = b'R0P!' (was 0xBB fill), FSR=0x5 faultaddr=0x00bada54.
Both confirmations hold -- four strb executed in order with chosen value and chosen address each,
and control followed the planted sequence to its sentinel end. 0x00bada54 is only in the chain,
never on the stack.

So end to end, one FE0A selector-4 invoke gives: chosen PC (Add.E), SP pivot into the host command
buffer (Add.F), and an arbitrary-length ROP chain doing chosen write-what-where (Add.G). Code
injection stays out (no W+X page, report section 5), so payloads remain ROP, but 'arbitrary ROP
chaining is a structural ceiling' is now refuted with a running chain.

Limits kept honest: byte-store gadget (word-store needs controlled r0..r2, one more pivot); no
graceful TEE return yet so each run needs an ATX cycle; no weaponised payload chosen. Updated both
documents.
</content>
</entry>
<entry>
<title>[HW] full stack pivot into the command buffer: arbitrary ROP confirmed, report section 4 refuted</title>
<updated>2026-10-07T21:53:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-10-07T21:53:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=41ef82efc8400739f2a784f1f0cf210a52dde136'/>
<id>urn:sha1:41ef82efc8400739f2a784f1f0cf210a52dde136</id>
<content type='text'>
Addendum E gave a chosen PC plus chosen values in the registers 0x404d16 pops. That is enough to
pivot: GCP has 0x40371c mov sp,r4 ; pop {r4,r5,r6,r7,r8,sb,sl,fp,pc} with pc at sp+0x20. Point hop-1
PC at it and hop-1 r4 at the command buffer.

Measured, one ATX cycle: first pop loads r4=0x04026000 (params+0x2000) and pc=0x0040371d; the gadget
does mov sp,r4 then pops pc from params+0x2020, where we planted 0x00BADA54; fault is FSR=0x5
translation at addr=0x00bada54. That value exists only in the command buffer and nowhere on the
stack, so the fetch can only have arrived via first-pop -&gt; gadget -&gt; mov sp,r4 -&gt; second pop sourcing
PC from attacker memory. SP is now pivoted into the host command buffer, so an arbitrary-length ROP
chain can be laid out there and executed.

This refutes two of the three claims section 4 of the control-flow report rested on: chosen chain
words (the buffer is plain host data, no blob alphabet, no carve) and a controlled SP above the
ctx+0x4f floor (SP is in the command buffer, unrelated to the floor). Updated both documents.

Honest boundary kept: this proves the pivot and that chain memory is fully controlled, not yet a
useful computation (needs a GCP-resident gadget catalogue and a built chain), and code injection is
still blocked because no writable page is executable (report section 5, unchanged). But 'no arbitrary
ROP chaining' as a structural ceiling is now false.
</content>
</entry>
</feed>
