aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAge
* Record PSP security investigation artifacts and probesHEADmainuser2026-10-09
|
* GCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers)user2026-10-08
| | | | | | | | | | | | gcp_asd_403_write.py runs real asd_topology_update(0x100) x2 + asd_platform_op_403(0x403) unpatched: 0x100(0) + 0x100(1,desc[0xC]=P) forge the op_403 allowlist entry via index-90 topology aliasing (count slot v6.u16[126]=1, pointer slot v6[91]=P confirmed), then op_403(90,4,TARGET,VALUE,0,4) fires SVC 0x7C with addr=0x5000000 value=0xC0FFEE42 width=4 = our CHOSEN operands. SUCCESS. Chain now: GCP->ASD svc2 reach (HW proven) + flat ungated dispatch (verified) + 0x100x2 forge (emu proven, real handlers) + op_403->SVC0x7C chosen write (emu proven) + SVC0x7C broad phys write GCP lacks (static). Only remaining: HW end-to-end = deliver 3 svc2 RPCs in one FE0A ROP chain (one-shot/ boot) writing a benign observable target. Harness-extension task, not a new unknown. Updated eval doc.
* GCP->ASD lead: aliasing resolved for our build (index 90) + HW service-2 ↵user2026-10-08
| | | | | | | | | | | | | | reachability PROVEN Aliasing: off_D024D4=0xD42798 (op_403 base), dword_D115E0=0xD42890 (topology table), 0xF8 apart. count slot v6.u16[a1+36]=0xD427E0+2a1 -> entry0+4 at a1=90; pointer slot v6[a1+1]=0xD4279C+4a1 -> entry1+0x14 at a1=90. 0x100 copies desc[0xC] into entry1+0x14 = the pointer op_403 derefs. Full our-build chain specified (index 90 = agent's 'later build'). HW WIN: GCP FE0A ROP svc 0xf2 SERVICE 2 opcode 0x100 a1=0xFF -> status 0x5 (asd_topology_update out-of-range return) => GCP reached ASD and ran 0x100. service-1 control -> 0xffff0009 (different TA). GCP->ASD reachability (the stated kill-switch) CONFIRMED positively on RX 7900 XTX. ASD dispatch is flat/ungated so 0x403+0x100 both reachable. Remaining: end-to-end seed->match->SVC0x7C (emu+hw).
* eval: GCP->ASD op_403 confused-deputy -> SVC 0x7C privileged physical write ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | (credible, verified core) Verified another agent's lead against OUR images. Confirmed in asd2/sos2: (1) asd_platform_op_403 (0xd023c8) uses UNBOUNDED index a1=req.byte[4] to select pointer slot v6[a1+1] + count slot v6.u16[a1+36]; on match of (addr=req[8], subtype=req[5]) it fires SVC 0x7C (val=req[0xC], width= req[0x14]); subtype 4 skips the extra check; GCP ROP controls all fields. (2) SVC 0x7C = sub_6F3A-> sub_3D8C->sub_6D20: broad physical/MMIO write below 0xCB00000 minus two protected windows -- exactly the MMIO-write cap GCP's ACL denies (0x6002) -> real lateral escalation. (3) service 2 == ASD; GCP already calls ASD service-2 legitimately (FE08 rpc_201) so it holds the permission; ASD dispatch is a flat opcode jumptable via BLX asd_dispatch;SVC 0x75 with NO session/ACL gate -> 0x403/0x100 directly reachable. (4) opcode 0x100 = asd_topology_update (0xd1061c) writes topology entry dword_D115E0+96*a1, sets +4=1 (active/count) and copies caller 0x40-byte descriptor from req[8] -> the seed material. NOT yet established: the aliasing in OUR build (agent's addrs/index 88 are card build 33.0.0.217; our op_403=0xd023c8 and 0xD0E038 is a vtable builder here -> re-derive the index that lands off_D024D4[a1+1] on dword_D115E0, runtime tables, resolve in-emulator); end-to-end seed->match->SVC0x7C; and HW service-2 reachability. Recommended: cheap HW service-2 reach test, emulator end-to-end, then HW. Full writeup in fw/EVAL-GCP-TO-ASD-0x403-KERNEL-WRITE.md
* hw: no deterministic crypto-output placement - SVC 0xB5 client check ↵user2026-10-08
| | | | | | | | | | | | | | (sub_1978) rejects Pinned the per-boot knob: SVC 0xB5 return gates sub_E0DEF4 and is sub_27F8->sub_1978(client), which returns OK only if client<0x20 AND proc_table[client] active-bit set; else 37 -> whole validation fails for ANY output buffer (buffer-independent). Hardware pass-rate test (op0x35 len4 @0x410380, dumped, fresh ATX/boot): ~25 boots across rnglen/rngdet/rngdet4/passrate ALL reject 0xffff000f, buffer never written; the lone acceptsweep 'pass' (11/11) did NOT reproduce - its exact config now rejects 10/10. Verdict: output-buffer crypto ops reliably reject from the GCP confused-deputy; no deterministic placement exists; reject is client/session state we don't control. Asymmetry: dir==1 INPUT buffers bypass validation (unvalidated reads) but dir==2 OUTPUT buffers are client-gated. Oracle closed; inline create-handle read-back remains the only reliable channel. Updated SIM doc.
* sim: confirm GCP crypto ROP message is correct; localize 0xFFFF000F to SVC ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | 0xB5 client-window check Operator asked whether our ROP sets everything up correctly and whether we can simulate + inspect args at call time. Built psp_emu/harness/gcp_crypto_argcheck.py: reconstructs the EXACT op-0x35 request our hardware harness builds and runs it through the firmware's own sub_E087E4 -> sub_E1DC30 dispatcher with real sub_E0DEF4/sub_E0AB44 validators, hooking dispatcher+validator+RNG leaf. Proven (not inferred): dispatcher sees opcode=0x35, buf0(ptr,len) from msg[2]/msg[3], dir=2 (output), sub_E04F38 reached with our exact (out,len), buffer written. len 4/16/32 ALL succeed with permissive SVC 0xB5 -> hardware len-sensitivity is NOT a message bug. Modeling SVC 0xB5 with limit=base+8 reproduces the hardware exactly: len4 passes (RNG runs), len16/32 return 0xffff000f with sub_E04F38 never reached. So the reject is the window bound check (ptr+len vs [base,limit]), not our setup. Ground-truth chain: sub_E0DEF4 passes iff [ptr,ptr+len] subset of [b1,l1]|[b2,l2]|[E25710,E25714]; SVC 0xB5=case100=sub_178C+sub_27F8 fills the descriptor from the CLIENT process entry (type5 mode1 -> [0x400000,0x800000]; else b1=proc[6] narrow). Accept/reject turns on client type/mode + the SYS_DRV global fallback region at validation time -- state our ROP does not set -> explains per-boot variance. Recorder sub_22B4 writes tcb[24]=*(tcb+0x34)-0x44, tcb[25]=tcb[16]. Full writeup in fw/SIM-GCP-CRYPTO-ROP-ARGCHECK.md.
* crypto output oracle: ABANDON - confused-deputy output-buffer validation is ↵user2026-10-08
| | | | | | | | | | | | | run-to-run nondeterministic Pinned the op-0x35 accept behavior across many boots: the IDENTICAL request (op 0x35, ptr=0x410380, len=4) was ACCEPTED in one boot-series (acceptsweep, status 0) and REJECTED 5/5 in another (rngdet4, 0xffff000f). Same opcode/ptr/len/harness -> SVC 0xB5 validation of confused-deputy output buffers is run-to-run non-deterministic (boot-series correlate; likely the recorded window tcb[24]/[25] from the type-5 gate varies with boot stack layout). Consequence: cannot reliably land a crypto DATA write -> seeded-RNG determinism question unanswerable via this path (never got one confirmed RNG buffer write; the earlier 'identical output' was stale GCP memory, disproven via create baseline). Reliable: only the inline create-handle read-back (not an engine oracle) + the GCP->SYS_DRV DoS. Recorded in harness doc.
* crypto output read-back: principle OK (create handle), but RNG 'identical ↵user2026-10-08
| | | | | | | | | | | | | | | | output' was STALE GCP memory Follow-up to the hijack retraction. Pointing a crypto op output at GCP memory clear of the ROP chain should let us read engine output back. Principle validated by op 0x01 create: handle 0x10 read back via the message copy-back = a confirmed crypto output read-back. RNG (op 0x35) data-buffer demo BLOCKED: output->0x410500 (len 4 and 32) gate-rejects (status 0xffff000f = dword_E1DF48 buffer-gate fail) -> RNG never wrote. Decisive control: a create baseline (writes nothing to 0x410500) dumped the SAME bytes bb18bb63... as the RNG runs across 3 boots. So the 'identical random-looking output' is STALE GCP memory at 0x410500, NOT RNG output -> the deterministic- seed hypothesis does not apply (RNG didn't run). 0x410500 is GCP-mapped (dump reads it) but SVC 0xB5 rejects it as an op-0x35 OUTPUT buffer; earlier single-op ptr_sweep accepted op0x35->0x410500 len4 at status 0, so the gate accept/reject is harness/len/state-sensitive, not yet pinned. Updated harness doc.
* re-evaluate GCP attack surface (post-retraction): capability-capped SYS_DRV ↵user2026-10-08
| | | | | | | | | | | | | | | | | | reach + ranked avenues fw/ASSESSMENT-GCP-ATTACK-SURFACE-CURRENT.md. Verified: GCP code-exec, svc 0xf2 -> SYS_DRV/ASD, crypto + free-opcode surface executes. Reachable but CAPPED: crypto (TA-scoped, no device key, bounded), free high opcodes (inert/getters/bounded = fuzz_sysdrv ground), confused-deputy buffer write (hits GCP's own shared buffer, or SYS_DRV DoS), ASD (validated). NOT reachable: all gated primitives (OTP 0x6001, MMIO 0x6002, overflow 0x3103, reg 0x70xx) -- opcode ACL, GCP lacks caps. Ranked remaining avenues: (A) GCP's OWN address space -- unexplored, needs NO escalation; GCP is the content-protection/crypto-session TA so its in-process keys/device-bound secrets are directly dumpable with our code-exec [highest value]; (B) lateral to POWERPLAY(0x60xx)/RAS(0x70xx) for the gated fuse/ MMIO primitives; (C) the confused-deputy GCP->SYS_DRV DoS (real, low sev); (D) exhaustive mem-safety of the reachable surface; (E) widen the svc-0xf2 window past 0x800000. Verdict: capability-capped into SYS_DRV; realistic next win = harvest GCP's own secrets; fuse crown jewels run through POWERPLAY/ RAS.
* overview: correct the retracted SYS_DRV-hijack claim -> GCP self-corruption ↵user2026-10-08
| | | | + SYS_DRV DoS only
* RETRACTION: the SYS_DRV control-flow hijack was GCP self-corruption of our ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | own ROP chain Reviewer skepticism confirmed correct. Self-test: RNG write to our POST-exfil gadget slots (0x410110/0x410148/0x410180) gave status=0x0 (exfil RAN -> svc returned, GCP chain executed), grace=0xbb, fault fr0=0x004061cc (GCP svc gadget, GCP .text) jumping to random. That is GCP self-corruption, not a SYS_DRV hijack. Also: the earlier 'GCP-invisible 0xAA' test used msg[2]=4261632=0x420700 (NOT 0x410500 -- arithmetic error); RNG wrote 0x420700 while the dump read an untouched 0x410500, so 0xAA 'survived' trivially. That evidence is withdrawn. Corrected mechanism: SYS_DRV maps the client (GCP) buffer region SHARED; our ROP chain/migrated stack live there (STACK_DST=0x410000), so crypto writes overwrite OUR OWN code -> the 'hijack PC=our bytes' was us jumping through our corrupted gadget. The genuinely-SYS_DRV results are the write-FAULTS at GCP-mapped-but-SYS_DRV-unmapped VAs (0x417000/0x5ff000/0x6ff000/0x430000..) where fr2=0xe04f4c (SYS_DRV RNG store) + DFAR=VA -> SYS_DRV data-abort = a real confused-deputy DoS (write to UNMAPPED SYS_DRV VA, a crash not a controlled write). Downgraded: NO SYS_DRV control-flow hijack, NO write into SYS_DRV private memory; prior PC-control breakthrough RETRACTED. Stands: GCP->SYS_DRV DoS, and crypto buffer writes otherwise hit GCP's own shared buffer. Distinguisher: fault fr0 0xE0xxxx=genuine SYS_DRV; 0x40xxxx/status0-then-crash=self-corrupt.
* overview: full GCP-invocable syscall/op surface by context + constraintsuser2026-10-08
| | | | | | | | | Synthesized fw/OVERVIEW-GCP-INVOCABLE-SURFACE.md: the L0-L4 context/privilege layers (GCP ROP -> SVC 0xF2 -> SYS_DRV/ASD service context -> RPC opcodes -> privileged SVCs), every gate (gadget avail, type-5 window, service exemption, opcode ACL via SVC 0x76, SVC 0xB5 buffer validation, confused-deputy context mismatch, per-op bounds), and what runs where. Includes the crypto dispatcher (~50 ops, no ACL), high dispatcher (164 free / 122 gated), the indirect privileged SVCs (0x68/0x7b/0x7c/0x7d/0x7e/ 0x8a/0x81/0xa4/0xa1/0xb5...), and a capability/wall summary table.
* op 0x3a pointer-relay NOT viable (metadata not code pointers); close ↵user2026-10-08
| | | | | | | | | | | | | | | | crypto-write line at PC-control-value-not-chosen op 0x3a copies obj metadata (+88=type const 0xa00000bf, +96=value, +100=-1, +104=size<=0x2000, +108=flags 0x10000) to output -- none a 0xE0xxxx code pointer; relaying any -> unmapped PC -> fault. So pointer-relay is dead. FINAL: PROVEN GCP->SYS_DRV control-flow hijack (PC=written bytes, reproducible, random via RNG) = real TA->fuse-owner control-flow redirect/crash. NOT achievable via this primitive: attacker-CHOSEN/useful PC -> arbitrary SYS_DRV code exec. Every output-reachable value is RNG(random) / constrained params+ctx(op0x36) / object metadata(op0x3a) / SYS_DRV-context buffer data -- confused deputy confines control to the inline constrained message. Escalation to chosen-PC needs a different vector (GCP-mapped input buffers [symmetry: no], window extension past 0x800000, or a host-controlled-output op). Closes the crypto-write line at PC-control-value-not-chosen. Updated lead doc.
* path-1 attempt via op 0x36 ctx-copy confirms the controlled-value barrieruser2026-10-08
| | | | | | | | | | | | | | | | Tried op 0x36 memcpy(output,ctx,32) across the RNG return-word region 0x410040..0x410078 (after creating a SHA256 ctx): all benign. Two findings: (1) the live return word is call-depth-specific -- op 0x36 (sub_E1E480->sub_E0192C) runs a shorter chain than the RNG (op0x35->sub_E1E184->sub_E04F38), so its return word is at a different offset; the 0x36 writes hit unused stack. (2) Even aligned, op 0x36 only writes ctx[0..7] (algo/class/mode/diglen/keysize/derived) = CONSTRAINED crypto values, never a 0xE0xxxx SYS_DRV code addr. Honest conclusion: controlled-value barrier holds. PROVEN = GCP->SYS_DRV control-flow hijack (PC = written bytes, reproducible, random via RNG) = reliable fuse-owner crash/redirect from a ROP'd GCP TA. NOT achieved = attacker-CHOSEN PC (arbitrary code exec): every data channel is inline-constrained params / ctx-derived / SYS_DRV-context buffers / RNG -- none delivers a chosen 32-bit value. To escalate to code exec: a pointer-relay op (op 0x3a object-field copy, untested), GCP-controlled input buffers (symmetry says no), or window extension. Updated lead doc.
* controlled-value analysis: chosen PC gated by confused-deputy marshaling ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | (PC-control proven, arbitrary-exec blocked) Audited every output-filling crypto op for a controllable data source: only a1[14..17] (validated AllocateOperation handle/algo/mode/keysize) feed output -> constrained crypto values, never a 0xE0xxxx SYS_DRV code addr; no op copies unvalidated inline a1[18..30] to output; 0x36 copies ctx[0..7] (constrained); 0x5b/0x3a copy object data loaded from input buffers; cipher/digest/mac transform an input buffer and need a key set. Decisive constraint: the confused deputy derefs crypto buffers (input AND output) in SYS_DRV's context (proven for output: 0x410500 write GCP-invisible, 0x5ff000 faults in SYS_DRV w/ DFAR=VA). By the same mechanism input reads return SYS_DRV memory, not attacker data. So the only attacker-controlled channel is the inline 124B message (constrained params); bulk data is SYS_DRV's own or RNG. Conclusion: PROVEN GCP->SYS_DRV control-flow hijack (PC = written bytes, reproducible); BLOCKED making that value attacker-CHOSEN -> reliable SYS_DRV crash/DoS + demonstrated PC-control, not yet arbitrary code exec. Remaining experiments: confirm input-buffer context, pointer-relay via a copy op (ret2somewhere with a valid SYS_DRV pointer), or extend the window. Updated lead doc.
* CONFIRMED reproducible SYS_DRV PC=written-data; return word at 0x410040-0x410080user2026-10-08
| | | | | | | | | | | | | | Len bisection at 0x410000: len 0x40 benign, len 0x80+ HIJACK. PC equals the random bytes written, and two identical len0x1000 runs gave different PCs (4042052e vs b7f69a86) -> SYS_DRV jumps to OUR data. Live return word at offset 0x40-0x80 (0x410040..0x410080); a precise ~0x40B write from 0x410040 suffices. GCP-ROP -> SYS_DRV PC control is PROVEN (crypto op output buffer on the SYS_DRV stack return word -> SYS_DRV jumps to written value). Missing step for arbitrary code exec = writing a CHOSEN value; today RNG writes random (PC=random->fault). Obstacle: confused-deputy marshals only the inline 124B message, so buffer input/output are both in SYS_DRV context -> can't trivially feed controlled plaintext. Listed options (controlled-inline-derived op / verbatim-copy op / known-input cipher search / partial-PC brute). Updated lead doc.
* BREAKTHROUGH: big-wipe reveals SYS_DRV control-flow hijack (PC = written data)user2026-10-08
| | | | | | | | | | | | | | | | Reviewer idea (wipe most of the region in one call) was decisive. RNG write 0x410000 len 0x1000 -> crash with fault PC=0x9ea0b953, DFAR=0x9ea0b952 = the RANDOM DATA WE WROTE (Thumb bit set) -> control flow JUMPED to our written bytes. So overwriting 0x410000..0x411000 clobbers a live SYS_DRV return address/code pointer. len 0xBF00 instead faulted at DFAR=0x417000 (an unmapped hole inside the region; coarse sweep stepped over it). Conclusion: the 48KB region is SYS_DRV's process stack (runtime, top ~0x41C000, SYS_DRV's own physical -> GCP-invisible). Small writes missed the saved return word; the 0x1000 write hit it. This is a SYS_DRV control-flow hijack from a ROP'd GCP TA: crypto op with output buffer on the SYS_DRV stack, length covering the return address -> PC = written data. RNG gives random PC (fault); a controlled- output op (cipher = AES(our plaintext,key)) gives a CHOSEN PC = arbitrary code exec in the type-1 fuse-owner. Corrects the earlier 'benign scratch' read -- it's the live stack. Updated lead doc; next = bisect the return-word offset, confirm PC tracks data, drive a controlled (cipher) write.
* (a) map in-window SYS_DRV mapping: benign ~48KB scratch/bounce region, not ↵user2026-10-08
| | | | | | | | | | | | | | | | | live control state Swept RNG output ptr across the window + fine-probed the high end of the mapped cluster. SYS_DRV maps 0x410400..0x41BFB0 (~48KB contiguous) + isolated 0x420000; everything 0x41C000..0x7F0000 is SYS_DRV- unmapped (STRB fault). Fine probe of 0x419000..0x41BFB0 (incl. right at the saved-LR slot 0x41bfbc): ALL benign (status 0, grace 0x5a, no corrupted-return crash). So the dual-mapped region is a benign scratch/bounce region, NOT SYS_DRV's active stack / code / globals -- random writes are inert and GCP-invisible. Net: the confused-deputy write is a reliable DoS (crash SYS_DRV via GCP-mapped/SYS_DRV-unmapped VA like 0x430000+) but NOT a useful write (the only in-window SYS_DRV memory is benign scratch; exploitable state is unmapped-in-window or out-of-window at 0xE00000/0xE5xxxx -> 0xffff000f reject). Remaining escalation path = (b) extend the validation window (tcb[16]=TA_end from the type-5 gate sub_22B4) past 0x800000 toward SYS_DRV's mappings. Updated the lead doc.
* attribution nailed: fault is the RNG STRB (0xe04f4c) in SYS_DRV context; ↵user2026-10-08
| | | | | | | | | | | | | | | confused-deputy validate(GCP)-vs-write(SYS_DRV) Full fault frame on crashers: DFAR = our target VA exactly; store-PC 0xe04f4c = STRB R0,[R4] in sub_E04F38 (RNG write loop); 0xe0df20 = after SVC 0xB5 in sub_E0DEF4 (validate_client_range). Faulting PC is SYS_DRV code (0xE0xxxx), not GCP -> SYS_DRV is the faulting entity, confirmed. Precise model: validation (SVC 0xB5) uses the client (GCP) page tables -> in-window GCP-mapped VAs pass; the handler's STRB writes the raw VA in SYS_DRV's context. So VA mapped in BOTH GCP+SYS_DRV (0x410500) -> write lands in SYS_DRV physical (GCP-invisible) = write-into-SYS_DRV primitive; VA GCP-mapped but SYS_DRV-unmapped (0x5FF000/0x7FF000) -> STRB faults in SYS_DRV -> PSP crash (GCP->SYS_DRV DoS). Confirmed a ROP'd GCP can make SYS_DRV store to an attacker-chosen VA in SYS_DRV's context. Updated the lead doc; next = map dual-mapped extent + controlled-data (cipher) write.
* LEAD: crypto buffer-write is a confused-deputy write into SYS_DRV address spaceuser2026-10-08
| | | | | | | | | | | | | | | | | | | | | Empirical ptr sweep of op 0x35 (TEE_GenerateRandom) output buffer answers two reviewer questions: (1) Bounds are NOT a fixed [0x400000,0x800000): 0x400000 and 0x800000 are BOTH rejected (0xffff000f), while 0x410500/0x5FF000/0x7FF000 are accepted. Matches the type-5 gate recording [msg-68, TA_end]; with msg at 0x410400 the lower bound is ~0x4103BC, so the window tracks the message location. Earlier flat-range claim was not empirically verified; corrected. (2) Writes land in SYS_DRV's address space (confused deputy): 0x410500 accepted + RNG ran (status 0) but GCP's own 0x410500 stayed 0xAA -> the write does NOT reach GCP physical. 0x5FF000/0x7FF000 accepted + written + CRASHED the PSP (ring dead, grace 0xbb, FE07 garbage). So the client pointer is validated against GCP's page map but dereferenced in the SYS_DRV handler's context -> write lands in SYS_DRV's view of the VA; benign where SYS_DRV has innocuous RAM (0x410500), fatal at 0x5FF000/0x7FF000. Primitive shape: VA-selectable write into SYS_DRV addr space (VA in ~[0x4103BC,0x800000)), data = crypto op output (random for RNG; attacker-CONTROLLED for a cipher op). SYS_DRV = type-1 fuse-owner, so this is a strong escalation candidate. Not yet established: what is mapped at those SYS_DRV pages, SYS_DRV- vs-GCP crash attribution, and a controlled-data (cipher) end-to-end write. Wrote fw/LEAD-GCP-CRYPTO-BUFFER-WRITE-INTO-SYSDRV.md; hw/ptr_sweep.sh added.
* push further: full crypto op (TEE_GenerateRandom 0x35) EXECUTES but ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | pointer-buffer output does not marshal back to GCP Identified op 0x35 = TEE_GenerateRandom (sub_E04F38) -- self-contained, writes len random bytes to msg[2] output buffer. Hardware: svc(0x35) returned 0x0 (RNG executed) BUT the output buffer, pre-filled with 0xAA at 0x410500, came back UNCHANGED -> the random bytes did not land in our GCP-window buffer. Root cause (confirmed): the confused-deputy svc-0xf2 path bounces only the inline 124-byte message (sub_E087E4 works on a local copy v8, copies back on return) -- which is why INLINE results round-trip (AllocateOperation handle at msg[14]). A pointer-buffer op has the SYS_DRV handler dereference the client VA in its OWN context; GCP's SRAM window isn't mapped there, so the RNG wrote into SYS_DRV-side memory we can't read. The crypto service's buffer model assumes shared-DRAM client buffers (host ring RPC), but the type-5 gate confines our buffers to GCP's SRAM window -> incompatible. Net boundary: from GCP ROP we CAN execute any crypto op (RNG runs, status 0) and read INLINE results (create handle, state persistence); we CANNOT read pointer-buffer outputs (digest/cipher/RNG data) without defeating the cross-domain buffer bounce. Documented in fw/HARNESS-GCP-CRYPTO-ROP.md.
* crypto ROP harness: multi-op (two-svc) validated on silicon + usage/status docuser2026-10-08
| | | | | | | | | | | | | | | | | | | | gcp_rop_crypto2.py: issues TWO svc 0xf2 service-1 calls inside ONE FE0A-hijacked chain (the FE0A->ROP hijack is one-shot per boot, so stateful sequences must be in one chain). Hardware-validated: - create SHA256 -> handle 0x10 (status 0, dumped back) - create -> create -> handles 0x10 then 0x11: multi-op sequencing + crypto CONTEXT STATE PERSISTENCE proven on silicon - create -> digest(0x08): input 32B placed in the GCP-window scratch and dumped back correctly; the transform returned 0xffff0006 because op 0x08 reads ctx[5]>>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.
* add clean GCP->SYS_DRV crypto ROP harness (hw/gcp_rop_crypto.py)user2026-10-08
| | | | | | | | | | | | | | | | Parameterized crypto-op harness built on the proven stack-migration svc 0xf2 service-1 chain. Takes OPCODE + MSGSPEC (JSON dword_index->value) to drive the SYS_DRV GP-TEE crypto dispatcher (sub_E1DC30, opcodes <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.
* CORRECTION: crypto buffer confinement is the type-5 svc-0xf2 window ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | [0x400000,0x800000), not ptr<=0x40000000 A reviewer correctly flagged that validate_client_range's ptr<=0x40000000 bound does NOT mean GCP-owned -- SYS_DRV (0xE00000), sOS, and other TAs are all below 0x40000000; that check only excludes the high MMIO aperture. My 'self-corruption' verdict was right for the wrong reason. Verified in sOS: the real confinement is the type-5 caller gate sub_22B4 (sos_proc_switch). For a type-5 caller (GCP) it requires msg + arg ptr in [0x400000,0x800000) and RECORDS that window into tcb[24]=msg_base, tcb[25]=TA_end (type-1 uses tcb[26]/[27], 0x200000 window). SVC 0xB5 (validate_client_range's backing validator) checks each RPC (ptr,len) against that recorded window; teardown sub_24A4 reads tcb[25]/[27] back. So the window -- not the spoofed cur_tcb[0x4c] identity -- governs buffer validation. Consequence: even with the confused deputy setting cur_tcb[0x4c]=SYS_DRV for the ACL, the RPC arg window stays GCP's own [0x400000,0x800000). Every crypto buffer (in/out) must lie inside it, so crypto ops cannot read/write SYS_DRV/sOS/other domains via buffer descriptors. Output overrun = self- corruption within GCP's window. Conclusion holds, now correctly grounded. Caveat: verified the gate sub_22B4 + teardown sub_24A4; did not fully disasm the SVC 0xB5 body, but gate + prior service-1 overflow finding corroborate window-enforced validation. Updated the crypto overview.
* memory-safety spot-audit of the GCP-reachable crypto surface: bounded, no ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | overflow found How buffers are specified: up to 4 (ptr,len) pairs at request a1[2..9]; dispatcher validates each via sub_E0DEF4 (ptr<=0x40000000, len<=0x10000000) + SVC 0xB5 (caller-accessible + R/W permission per direction token from sub_E0AB44). So output buffers are caller(GCP)-owned -> output overrun = self-corruption, not cross-domain. Output lengths: consistent bounds-check-before-copy. cipher 0x06/07 rejects out-len v10>v14(caller buf); property 0x43 checks *a7>=required; set-op-key 0x03 checks key len > v5[18](op max-key-size) + algo caps (0xD0/0x20); object-create 0x57 hard-caps inputs (0x2000/0x40) and allocates size=base+len to match; derive 0x39 caps attr count <=1 and passes 256 as the work-buffer cap. Verdict: in the copy-heavy handlers audited (0x03,0x06/07,0x39,0x43,0x57 + hash/HMAC gates) the surface is memory-safe -- internal copies bounded against op/object max sizes and hard caps, allocs sized to match; no overflow found. Consistent with fuzz_sysdrv coverage. Honest caveat: spot-audit not exhaustive over all ~50 opcodes; a bug would be an outlier handler breaking the uniform check-before-copy pattern, and even a missed caller-buffer check only corrupts GCP-owned memory -- the only high-value class is host-length copy into a SYS_DRV-internal alloc, and every traced such copy sizes its allocation to the capped input. Updated the crypto overview.
* AUDIT: no device-bound key reachable via the GCP svc-0xf2 SYS_DRV crypto surfaceuser2026-10-08
| | | | | | | | | | | | | | | | | | Audited the key-object/derive path. Key-load ops (0x33/0x34/0x39/0x05) are GP TEE key-object ops dispatching by object type + key size (0x34 validates RSA 256-4096 bits, AES 128/192/256; 0x39 copies HOST attributes then derives). All key material is attacker-side: host attributes (PopulateTransientObject), TRNG (GenerateKey), or host-derived (agreement). No HUK/fuse/device-root/ keystore-by-identity reference. Decisive SVC evidence: the complete set reachable (depth<=2) from ALL crypto handlers is 0x54,0x56,0x57,0x5a,0x5c,0x5e,0x5f,0x66,0x68(CCP submit),0x72,0x76(cap),0xb5(range-validate) -- NO SVC 0x81 (property path is separate) and NO dedicated keyladder/device-secret SVC. A device key would need a distinct syscall absent from this surface. Conclusion: the surface is GP TEE crypto over TA-scoped objects with host/generated/host-derived keys -- AES/hash/MAC/RSA with attacker-controlled keys, not a device-key oracle, not an escalation. The device-key angle is CLOSED for this surface. Device keys (HDCP/keyladder) live in GCP's own command handlers / provisioning path, a different codebase. Updated the crypto overview.
* CORRECTION: crypto ops 0x43-0x48 are the TEE Property API, NOT a ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | keyladder/device-key path Caught my own overclaim. Decompiling the handlers: sub_E1E5CC (0x43) reads a type byte and formats the value as int (0x%08x) / bool (true/false) / UUID / identity / raw bytes = exactly GP TEE_GetPropertyAs* return types; sub_E09D70 is the property name->value TLV walk (TEE_GetProperty). The sub_E0B380 selectors are GP propset pseudo-handles: -1 = TEE_PROPSET_CURRENT_TA (SVC 0x81), -3 = TEE_PROPSET_TEE_IMPLEMENTATION. So SVC 0x81 is TEE_GetProperty, not a device-key fetch, and the 'GUID' is a property VALUE of UUID type, not a key id. Retracted the device/platform-key + keyladder-oracle framing. The surface is the GlobalPlatform TEE internal API (crypto ops + object mgmt + property API), scoped to GCP and mostly its legitimate surface. Keys are operation/object-bound: cipher requires ctx key-set flag; key comes from TEE_SetOperationKey off an object populated from host attributes (attacker's own key -> uninteresting) or TEE_DeriveKey / persistent secure-storage. The device-bound question lives in those object/derive paths, which are NOT audited -- so the device-key angle is UNESTABLISHED, not confirmed. Corrected next-step: audit TEE_SetOperationKey/PopulateTransientObject, TEE_DeriveKey, and persistent secure-storage opcodes for a device-bound (HUK/fuse-derived) key reachable by the GCP identity -- the only genuine oracle candidate. Updated fw/OVERVIEW-GCP-REACHABLE-CRYPTO-OPS.md.
* overview: GCP-reachable crypto surface (svc 0xf2 svc1 crypto dispatcher, ~50 ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | ACL-free opcodes) Corrected the scope: the crypto dispatcher sub_E1DC30 handles opcodes <0x1000 with NO ACL, and it is far larger than the 0x1-0x9 first enumerated -- ~50 opcodes across context mgmt (0x01-0x05, 0x30-0x3B, 0x50-0x5E), symmetric crypto (0x06/07 block cipher AES-aligned, 0x08/09 hash, 0x0A/0B HMAC/AEAD, 0x0C-0x11), KEY MANAGEMENT/keyladder (0x43-0x48), and misc (0x53 log src, 0x54 chip id). Entry gate range-checks buffer pointers via sub_E0DEF4. Key finding: the key-ops (0x43-0x48) route through the resolver sub_E0B380 which selects key source by a selector arg -- selector -1 => SVC 0x81/0xA4 (platform/DEVICE key fetch), -3 => internal source, else GUID keystore lookup (op 0x43 parses a full GUID). Resolved key feeds sub_E09D70 (unwrap/derive). So a ROP'd GCP can drive device/platform-key and GUID-keystore crypto ops, subject to per-key access control enforced inside SVC 0x81/sub_E09D70 (sOS/fuse-owner), not in the dispatcher. Flagged candidate angles (device-key oracle, key-type confusion type1 vs type3, GUID keystore confusion) and the honest caveats (TA-scoped, buffer range-checked, mostly GCP's legitimate surface). Next step = audit SVC 0x81/sub_E09D70 key access control to see which GUIDs/slots GCP's identity may use. Wrote fw/OVERVIEW-GCP-REACHABLE-CRYPTO-OPS.md.
* CORRECTION: retract the 0x105d-bypass thesis -- it is an internal helper, ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | not a host-facing path Two reader questions (is the descriptor even read? / aren't setup opcodes needed first?) exposed the error. Resolving sub_E105A8's dispatch: nested switch v14 = opcode-0x1001, case 5 => opcode 0x1006, handler sub_E142A0 = SETUP_TMR orchestrator -- the exact path the VULN doc tested and .54 fixed. sub_E142A0 (0x1006) is the real host-facing op: takes host base from the request, runs engine setup (sub_E2136C slot-init, sub_E212DC engine-cfg, read_reg_pair), then DMAs firmware regions via spirom_region_read -> spirom_xfer. 0x105d/spirom_xfer is the low-level per-region descriptor programmer called INSIDE that flow, not a standalone path. Standalone 0x105d returns 0xffff000f because sub_E17EF4 can't reserve a slot: the setup funcs sub_E2136C/sub_E212DC are called ONLY from sub_E142A0 and are NOT dispatchable by any opcode. So you cannot assemble the setup from adjacent opcodes to feed 0x105d, and the window check (dword_E264B0, referenced by sub_E0B19C/sub_E0DA10) is confirmed absent from the 0x105d path and present on the 0x1006 path. Our inline descriptor was never an input path. Net (corrected): the host-facing controlled-base firmware DMA is opcode 0x1006, which .54 DID fix (hardware-confirmed in the VULN doc) and which is one-shot-latched post-boot. spirom_xfer byte- identity .51<->.54 is true but irrelevant. There is NO GCP-reachable spirom bypass of the .54 TMR fix. Prepended a retraction banner and appended the full correction.
* HW gap #2: spirom_xfer executes from GCP but slot reservation fails ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | (0xffff000f), descriptor-independent Nailed the calling convention: sub_E087E4 copies the request into a local and dispatches with a1=v8; 0x105d -> sub_E1447C(a1+14,...), so spirom_xfer reads descriptor at request+0x38. Hardware (oracle-validated): 0x105d returns 0xffff000f for all-zero, valid, AND unaligned-base descriptors at both P+0x38 and M+0x38. 0xffff000f = sub_E17EF4 error (runtime base 0xffff0007 + 8). Airtight reasoning: (1) an unaligned base did NOT trigger spirom_xfer's own validation reject (0xffff0006) -> validation passed on an aligned/zero base, so the handler isn't seeing my descriptor; (2) with a zero base/len the overlap test cannot fire, yet sub_E17EF4 still returns base+8 -> the cause is SVC 0x7B engine-reg read failure or no-free-slot, both descriptor-independent and not fixed by a valid base. Conclusion: spirom_xfer IS reached/executed from GCP, but sub_E17EF4 slot reservation against the transfer-engine regs (SMN 0x49007a00) does not complete in the post-boot runtime state. So the chosen-destination staging via 0x105d is NOT achievable from the running-TA context -- blocked by an operational engine/slot gate, not a security check. The weak guard (no protected-window check on this path) is real but LATENT from this position; it only bites during the early-boot window when the engine is live. Updated the diff doc. Harness: hw/gcp_rop_spirom.py, hw/spirom_shot.sh.
* mechanism: overlap check reads engine slot regs (SMN 0x49007a00), not the ↵user2026-10-08
| | | | | | | | | | | protected-DRAM table; 0x105d stages only (no SVC 0xA1) Confirmed sub_E15EF0 (SVC 0x7B read) and sub_E218FC (SVC 0x7C write) use the SAME register block 0x49007a00 = the transfer engine's own 16 slot descriptors. The overlap check compares the chosen base against other in-flight transfer slots (owner-scoped), NOT the dword_E264B0 protected-DRAM table. So gap #3 answered statically: the window is NOT fenced on the 0x105d path (only on 0x1006). Also: spirom_xfer path has no SVC 0xA1 commit -> 0x105d stages a descriptor but moves no data, so a single shot is inert and safe to probe. Updated the diff doc.
* HW CONFIRM: 0x105d (spirom_xfer) is reachable from GCP on stock .54 (gap #1 ↵user2026-10-08
| | | | | | | | | | | | | | | | | closed) One-shot gcp_rop_sweep.py on the live .54 card, oracle-validated with controls: 0x1021 (pos control) status 0x0 REACHED <- correct 0x3103 (neg control) status 0xffff0001 ACL-REJECTED <- correct 0x105d (spirom_xfer) status 0xffff000f REACHED Both controls correct -> oracle trustworthy. 0x105d returned 0xffff000f (spirom_xfer's own validation reject of the all-zero descriptor), NOT the ACL 0xffff0001 -> svc 0x76 allowed it and the handler ran. Ring survived. So spirom_xfer IS reachable from a ROP'd GCP TA via svc 0xf2 service 1 on stock .54 -- the path the .54 TMR fix never hardened. Remaining: gap #2 (descriptor base/len/region_id that passes validation and triggers a real DMA; the all-zero case rejects with 0xffff000f) and gap #3 (does the 16-slot overlap check fence the targets). Updated fw/DIFF-SPIROM_XFER-51-VS-54-TMR-FIX-SCOPE.md. Harness: hw/drv_sweep3.sh / drv_sweep3.log.
* DIFF: .54 TMR fix is scoped to 0x1006 SETUP_TMR; spirom_xfer/0x105d path is ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | byte-identical to .51 Diffed the spirom_xfer path across builds: validator (.51 0xE14230 vs .54 0xE1447C) and transfer core (.51 0xE17CA8 vs .54 0xE17EF4). Both structurally byte-identical -- only data-global addresses relocated. The .54 TMR-rollback fix (0xffff0006 reject against the dword_E264B0 protected-DRAM table) was added ONLY to 0x1006/sysdrv_tee_map_shared_mem, NOT to spirom_xfer. The 0x105d dispatch (sub_E1447C(a1+14,1,v12)) calls the validator directly with a host descriptor and never touches the 0x1006 range check. So a caller reaching spirom_xfer via 0x105d gets the SAME guard on .54 as .51: 64KB alignment + 48-bit cap + per-proc 16-slot region-overlap check (sub_E17EF4), but NOT the protected-DRAM-window check. 0x105d is ACL-free -> GCP-reachable, so GCP-ROP -> svc 0xf2 -> 0x105d is a candidate for reproducing the controlled-destination firmware DMA on a STOCK .54 card (no downgrade), bounded only by the 16-slot overlap check. Verification gaps flagged honestly: (1) one-shot hardware confirm 0x105d reaches the handler from GCP (only 0x1021 confirmed so far); (2) exact msg[14..17] descriptor semantics + whether it triggers a real DMA; (3) whether the 16-slot overlap table fences the interesting physical targets (runtime state). Wrote fw/DIFF-SPIROM_XFER-51-VS-54-TMR-FIX-SCOPE.md.
* name the flagged handlers functionally; 0x105d = spirom_xfer is a CONFIRMED ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | primitive, not inert The repo already RE'd these as SYS_DRV RPC commands. Key: 0x105d (0xE1447C) = spirom_xfer -- a SPI-ROM/firmware-region -> DRAM DMA to a HOST-CHOSEN 64-bit physical base (64K-aligned, <48-bit), which is exactly the VULN-CONFIRMED-TMR-RANGE-ROLLBACK primitive confirmed on .51 hardware (DMA'd firmware regions to a chosen base; guard did not re-check base vs protected-DRAM window). It is a FREE opcode -> reachable from GCP. This corrects the earlier 'all bounded, no arbitrary primitive' verdict: no arbitrary 32-bit WORD poke is reachable, but 0x105d gives a host-steered firmware-DMA with a controlled DESTINATION, which is a real (confirmed) primitive. Open: is it still exploitable on the card's .54 build and does .54 re-check the destination base. Other handlers named: 0x105f sysdrv_tee_session_query (6 SMN writes, bounded-index addrs, host values only); 0x105e region-protect (bounded index <0x10); 0x1032 ASIC/engine init-sequence (fixed reg 0x10140340, steps 6-10, magic 0xF00D); 0x1037 region map/reserve (internal params). Plus the broader 0x1000-band RPC names from HUNT-HOST-REACHABLE-HEAP-CORRUPTION. Updated the overview.
* audit the reachable register/map/protect handlers: all bounded, no arbitrary ↵user2026-10-08
| | | | | | | | | | | | | | | | | | poke Drilled into the six flagged register/map/protect handlers to check host-controllability of the privileged op. All gate it: 0x105d validates the address (64KB-aligned, <48-bit, bounded) before a region/DMA setup; 0x105e uses a bounded region index (<0x10), protecting an index-selected region not an address; 0x105f validates struct index fields and writes FIXED registers in a fixed sequence; 0x1032/0x1049 read FIXED registers with no host address; 0x1037 maps from internal globals with a1/a2 as metadata. None is an arbitrary register/memory poke. So no reachable SYS_DRV opcode from GCP gives a host-controlled register/memory primitive -- consistent with SYS_DRV's uniform allowlist/index gating and with the one real arbitrary MMIO-write (0x6002) being in the ACL-gated 0x60xx range GCP can't reach. Net: of 164 reachable opcodes, ~84 inert, the reg/map/protect set bounded, ~14 memcpy handlers remain (largely fuzz_sysdrv-covered). No arbitrary primitive reachable from GCP; the dangerous ones need a POWERPLAY/RAS caller. Updated the overview.
* compile the named overview of all 164 GCP-reachable SYS_DRV opcodesuser2026-10-08
| | | | | | | | | | | | | | | | Mapped every reachable opcode (ACL-free) to its handler and classified by behaviour (SVCs issued, memcpy, size) from sys_drv2.elf (.54, card version). Categories: ~46 inert stubs, ~38 tiny getters, ~40 logic, ~14 memcpy (audit targets), ~5 crypto, ~6 register/map/protect. Notable: several FREE opcodes drive SYS_DRV's own privileged register/mem SVCs -- 0x105d (svc 0x7c reg-WRITE), 0x105f (svc 0x7b+0x7c reg r/w), 0x1032/0x1049 (svc 0x7b reg-read), 0x105e (svc 0x8a page-protect), 0x1037/0x1058 (svc 0x7d/0x7e map/reserve). These are distinct from the ACL-gated 0x70xx TA-reg family and ARE reachable from GCP -- the only reachable opcodes with real primitive potential. Plus ~14 memcpy handlers (0x1056/0x1016/0x1071/sub_E01FF8/0x1023/crypto 0x3) as memory-safety targets. The rest is the already-audited fuzz_sysdrv surface. Wrote fw/OVERVIEW-GCP-REACHABLE-SYSDRV-OPCODES.md with the full named table and flagged the register/protect + memcpy handlers as the next audit step.
* count the free opcodes: ~164 reachable from GCP (9 crypto 0x1-0x9 + 155 high ↵user2026-10-08
| | | | | | | | | | | | | | | 0x100a-0x10a4/0x4013-5) Enumerated handled opcodes in both SYS_DRV .54 dispatchers and classified vs the 10 ACL ranges. CRYPTO sub_E1DC30 (<0x1000, no ACL): 0x1-0x9 = 9 free. HIGH sub_E105A8: 0x100a-0x10a4 (minus gated 0x103d/0x103f) + 0x4013/0x4014/0x4015 = 155 free. Total ~164 free. 122 high opcodes gated (unreachable from GCP) -- the 0x1100/0x2000/0x3000/0x3100/0x4000-400f/0x6000/0x7000/0x8000 ranges, holding every high-value primitive (overflow, OTP read, MMIO-write, reg access). The ~164-opcode free surface (0x1000-0x10A4 high band + 0x1-0x9 crypto) is exactly what fuzz_sysdrv already exercised via ASD's emitted opcodes and found bounded. So a fresh hunt from GCP re-covers that ground; the unaudited high-value surface needs a different caller (POWERPLAY/RAS). Updated the audit doc.
* [HW] round-2 sweep: OTP-read (0x6001), MMIO-write (0x6002), reg-access ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | (0x70xx) all ACL-rejected from GCP 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 (<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.
* [HW] measured svc 0xf2 SYS_DRV reachability: ALL ACL ranges rejected, only ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | free opcodes pass 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 <0x1000 (sub_E1DC30, no ACL) and free high opcodes >=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.
* accurate picture: the only useful triggerable SVC is 0xf2; audit of ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | 0xf0-0xf5 + the 0xf2 surface Corrected '16 SVCs reachable' (gate allowance != our capability). Two limits: (1) we can only issue svc immediates present as even-aligned instructions in GCP code (SVC# is instruction-encoded, .bss not executable so we can't forge). Comprehensive scan: only svc 0xf2 (26 sites), 0xf3, 0xf4, 0xf5 are even-aligned real instructions; 0xE0-0xE9 are NOT issuable despite the gate allowing them; tlApi adds only svc 0x66 (<=0xDF, type-5 gated, unusable). (2) the dispatch gate -- moot since 0xf2-0xf5 are >=0xf0. Audited 0xf0-0xf5 via sos sub_6450: 0xf2 = sos_proc_switch (RPC, the whole escalation surface); 0xf3 = boot constant; 0xf4/0xf5 = reads of the caller's OWN PCB fields +28/+32. So only 0xf2 is useful; f3/f4/f5 are inert reads. svc 0xf2 reach: service 1 (SYS_DRV, exempt) + service 2 (ASD, cap 0x80); service 3 needs type 1. SYS_DRV opcode gating: sub_E05A24 issues svc 0x76 = (reqmask & ~off_6428[2*PCB[75]])==0, and off_6428 is BOOT-FILLED BSS -- so reachable ranges CANNOT be read from the image (why the emulator misled us). Measured: 0x3100 range (overflow opcodes) REJECTED. Free opcodes (outside the 10 ACL ranges) are allowed. Audit plan: measure reachable opcodes on silicon (vary opcode, read exfil status), then audit allowed handlers; the overflow is gated out so the hunt moves to allowed opcodes. Wrote fw/AUDIT-GCP-ROP-TRIGGERABLE-SVCS.md.
* [HW] CORRECTION: version is right (.54 confirmed), but the opcode ACL ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | | | | | rejects 0x3103 on silicon 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->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).
* [HW] svc 0xf2 issued from GCP ROP via stack migration; ASD survive-check passesuser2026-10-08
| | | | | | | | | | | | | | | | 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 -> 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.
* step 5b: stack-migration solves the svc 0xf2 SP/msg-window gate in one ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | memcpy (operator's idea) The clean fix for the gate constraint (SP and msg must be in [0x400000,0x800000)): have the ROP copy itself from the host buffer into the in-window GCP stack and pivot SP there, instead of pre-staging with many sel-4 writes. One memcpy satisfies both window constraints and the migrated bytes come from the fully-controlled host command buffer. Validated with real gadgets: 0x40410a 'blx 0x40111c ; movs r0,#0 ; add sp,#0x28 ; pop {r4,r5,r7,pc}' is a call-memcpy-and-continue gadget, and 0x40111c is confirmed memcpy(r0=dst,r1=src,r2=len) from the FE0A parent's own memcpy(params+0x418+sl, slot+8, len) usage. Recorded the full migration chain: pivot into host buffer -> pop r0=STACK_DST/r1=src/r2=len -> memcpy gadget copies the svc sub-chain+msg to STACK_DST (0x410000, GCP RW data, in-window, below the 0x41bfc0 frames the graceful return needs) -> repivot mov sp,STACK_DST (SP now in-window) -> migrated chain: pop r0=1/r1=STACK_DST+msgoff, svc 0xf2 (gate passes), str r0,[r5] exfil status to host buf, Addendum H graceful return. All in-window refs baked as STACK_DST+off at build time. This is the chain to build/fire; first silicon attempt stays the service-2 ASD survive-check, then service-1 0x3103 with exfil. Gadgets verified, memcpy confirmed.
* hardware prep: analysis finds the svc 0xf2 gate rejects a command-buffer SP; ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | revise the chain (in-window staging) Carefully re-checked the svc 0xf2 entry gate before firing at silicon. sos_proc_switch's type-5 range check (AUDIT-TA-KERNEL-BOUNDARY step 6) requires BOTH SP in [0x400000,0x800000) and msg in [0x400000,0x800000). Our FE0A sel-4 ROP runs with SP pivoted into the command buffer at 0x04026xxx, which fails SP<0x800000, so a svc 0xf2 from the current chain would be rejected by the gate before reaching SYS_DRV -- it would not test the confused deputy. (The emulator demos called sub_E087E4 directly and never exercised sos_proc_switch, so this did not surface there.) Recorded the revised hardware chain: pre-stage the request message M and the svc-issuing gadget sub-chain in the GCP window [0x400000,0x800000) via FE0A sel-4 writes (which reach in-window addrs, demonstrated on the stack), pivot SP to the in-window sub-chain so the svc sees an in-window SP, issue svc 0xf2 r0=1/r1=M, exfil r0 via str r0,[r5] to a host-readable command-buffer offset, then graceful-return. Listed the full verified gadget inventory. Not fired on hardware: the pre-check shows the current-shape chain would bounce off the gate, so building the in-window-staged variant is the remaining assembly. First silicon attempt should be the service-2 ASD survive-check, then service-1 0x3103 with status exfil. This is the careful outcome -- a gate rejection found by analysis, not by wedging the box.
* step 4: PC CONTROL in SYS_DRV demonstrated in emulator (chosen fault PC ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | | 0x00bada54) Composed the two primitives into an arbitrary SYS_DRV code patch, exploiting that the whole SYS_DRV region 0xe00000..0xeb7000 is RWX (measured). On real booted SYS_DRV with both real gates respected: (1) WRITE 0x3103 two-call overflow crafts a state-2 table entry with fully attacker-controlled content: a copy length spanning into entry1 (base+1100) lets the caller buffer set entry1 state(+4)=2, len(+40), and body -- measured entry1 state=2, [10]=0x40, [0:4]=01480047. (2) READ 0x3102 copies that state-2 entry to an arbitrary dst (a1, only permissively validated by sub_E0DEF4, ptr_ok(0x3102,0)=True). Pointed at SYS_DRV code 0xE0A430, it overwrote it: 1fb51549... -> 01480047 02000000 54daba00 (LDR R0,[PC,#4]; BX R0; <state word>; chosen PC). (3) Invoking opcode 0x3108 (calls sub_E0A430) executed the patched stub -> FETCH at pc=0x00bada54, our chosen address. Clean chosen-PC control in SYS_DRV (type 1). Handled the one constraint (READ forces 0x00000002 at dst+4, the entry state word) with a 4-byte branch stub at dst+0 reading the chosen PC from dst+8. A real payload patches a call to fuse_read_*/ fuse_program_bits (same SYS_DRV image) instead of a marker. Full chain now proven on both ends: GCP TA code exec on silicon, SYS_DRV PC control in emulator vs real firmware with real ACLs. Remaining silicon step is the transport glue (emit the 3 SVC 0xF2 dest-1 calls from the GCP ROP chain, same mechanism GCP uses for ASD). Artifact: psp_emu/harness/demo_sysdrv_codepatch.py. Updated the finding.
* step 3: the SYS_DRV overflow is a CONTROLLED overwrite (bounded, ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | non-crashing) -- tamable, demonstrated Answered 'can we tame it into control'. The first demo's ~1GB length faulted running off mapped memory, but the length is attacker-chosen (a1[10]+76), so demo_sysdrv_bounded_write.py plants v3[10]=0x1104 and the call-2 copy of 0x1150 bytes reaches the adjacent SYS_DRV global 0xea60ec and COMPLETES WITH NO FAULT, overwriting it with our chosen marker bytes. So the primitive is a fixed-address (no ASLR), controlled-length, controlled-content linear overwrite of SYS_DRV globals that returns cleanly -- a write, not a crash. Assessment that it is tamable: arbitrary content + arbitrary bounded length + fixed dest 0xea4fbc + clean completion; the reach contains multiple fixed SYS_DRV global structures actively used (0xea60ec/64ec/650c/658c/65a0); SYS_DRV has 349 indirect calls incl vtable dispatch (the 0xe01684 formatter blx [r4+0xC]); and opcode 0x3102 gives a companion arbitrary-length READ disclosure to leak layout/fn-ptrs/guard and aim the write. Fixed-address arbitrary overwrite + companion read + no ASLR in a type-1 process is a textbook controllable primitive. Honest status: tamable as a controlled overwrite = demonstrated; full PC control = not yet built, needs the specific adjacent fn-ptr/pointer-global and its trigger identified (focused RE, no ASLR so standard development). Artifacts: demo_sysdrv_3103_overflow.py, demo_sysdrv_bounded_write.py.
* step 2 DEMONSTRATED in emulator: opcode 0x3103 reaches sub_E0F364 and ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | overflows, real ACLs respected Wrote psp_emu/harness/demo_sysdrv_3103_overflow.py (reuses fuzz_sysdrv.SysDrvFuzzer: boots sOS+SYS_DRV, runs init so the RPC gate flag=1, respects the real opcode ACL sub_E05A24/svc 0x76 and the real pointer allowlist sub_E0AB44). Invokes the real sub_E087E4 with opcode 0x3103 twice. Measured: ptr_ok(0x3103,0)=True (the real allowlist permits a pointer in arg0, not bypassed); WRITE_E0F364 reached both calls; call 1 memcpy(global, src, 0x4c) bounded and planted global[+40]=0x40000000; call 2 memcpy(global, src, 0x4000004c) -> WRITE_UNMAPPED fault at 0xeb7000 as the ~1GB copy ran off the 1100-byte global. Verdict: handler reached, overflow length >0x44C, fault on call 2 -- all true. This confirms in real booted SYS_DRV every SYS_DRV-side step finding #9 left unestablished: 0x3103 passes the opcode ACL running as SYS_DRV, the pointer allowlist permits it, sub_E0F364 is reached, and the two-call overflow fires exactly as predicted. The only unexercised part is the SVC 0xF2 entry gate (demo calls sub_E087E4 directly), which is established statically (dest 1 exempt). Chain now proven on both ends: GCP ROP on hardware, SYS_DRV overflow in emulator. Remaining glue: emit the two SVC 0xF2 dest-1 calls from the GCP ROP chain on silicon and develop the overflow into SYS_DRV control. Updated fw/FINDING-GCP-ROP-TO-SYSDRV-VIA-SERVICE1.md.
* step 1 done: resolve the SYS_DRV overflow opcodes (0x3102 read / 0x3103 ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | write) via IDA + exploit recipe Decompiled sub_E105A8: *a1==0x3102 -> sub_E0B090 (read/disclosure), *a1==0x3103 -> sub_E0F364 (write/overflow, gated v14>249 with v14=*a1-0x3009=250, passes). a1 is the 0x7c-byte stack copy of the request, so req[0]=opcode, req[8]=ptr, req[12]=len, req[0x40]=ctx -- all inside the TA-window message. Both opcodes are in ACL range 0x3100-0x31FF (mask 0xffff0) which SYS_DRV's caps own, so the confused-deputy svc 0x76 passes. Confirmed the overflow from the decompile: sub_E0F364 memcpy(v3, a1, v3[10]+76) with v3[10] at offset 40 inside the copied 76 bytes and the only length check being a2>=0x44C (not vs the copy length). Two calls: call 1 (fresh, v3[10]=0) copies 76 bytes and plants v3[10]=a1[10]=huge; call 2 copies huge+76 from the caller buffer, overflowing the 1100-byte SYS_DRV global with attacker bytes. The guard a1[3]==v3[3] is self-defeating (v3[3] is offset 12, inside the copied region, initially 0). Wrote the full exploit recipe (msg[0]=0x3103, msg[8]=&buf, msg[12]=0x44C, buf[0]=0, buf[3]=0, buf[10]=huge, buf[11..]=payload; send twice). Remaining: confirm cur_tcb[0x70]==0, demonstrate in the emulator (full_system boots real SYS_DRV), then the GCP-ROP chain on silicon. Updated fw/FINDING-GCP-ROP-TO-SYSDRV-VIA-SERVICE1.md.
* crypto service (service 1) == SYS_DRV: the known overflow #9 is reachable ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | from GCP ROP (confused deputy) Looked into the crypto service as the alternative SVC 0xF2 target. Service 1 is SYS_DRV (GUARD-INIT-CHAIN): sub_E087E4 routes req[0]>=0x1000 to the HIGH dispatcher sub_E105A8, which holds finding #9's stored-length overflow (sub_E0F364 write / sub_E0B090 disclosure). #9 was filed 'REACHABILITY NOT established, would require TA code execution' -- which this session's GCP ROP supplies. The three gates all pass for a ROP'd type-5 TA: (1) dest 1 is permission-exempt in sos_proc_switch and the msg-range check pins msg to the TA window, satisfied; (2) the HIGH path 0xE08818 has no caller-type gate, just memcpy(stack,req,0x7c) then bl sub_E105A8; (3) the opcode-range ACL sub_E05A24 issues svc 0x76, which checks the CURRENT proc id's caps -- and sos_proc_switch set cur_tcb[0x4c]=service_desc_table[1]=SYS_DRV's proc id (0x22C8/0x23AA), which the dispatch gate (0x588E) uses for the caps lookup. So the ACL validates SYS_DRV's (type 1) caps, not the TA's -- confused deputy. SYS_DRV legitimately owns these ranges so it passes. Payload is #9 verbatim: two-call unbounded memcpy over a 1100-byte global -> SYS_DRV overflow + an arbitrary-length disclosure of SYS_DRV memory into the TA-window buffer (host-readable). SYS_DRV is type 1 and owns the fuse routines, so this is the route to fuses the earlier analysis was looking for. Established statically from firmware bytes; not yet demonstrated. Open: resolve the exact opcode into sub_E0F364/B090, confirm the cur_tcb[0x70]==0 precondition, and demonstrate (emulator then GCP-ROP on silicon). Supersedes the ASD route (uniformly validated, no bug); this reuses a documented overflow and lands in the more privileged, fuse-owning component. Wrote fw/FINDING-GCP-ROP-TO-SYSDRV-VIA-SERVICE1.md.
* ASD audit pass 2: 0x800/forwarded group + tail workers uniformly validated ↵user2026-10-08
| | | | | | | | | | | | | | | | | | | | | -- negative Read the forwarded workers 0xd0d134/26c/28c/114 and tail targets 0xd11238/112e4/11348/111c8 plus 0xd0d0bc/170. Uniform: zero-init a validated 4-byte user output ptr, validate arg2 as a 4-byte pointer via 0xd12e90, gate 0xd116ac, bound the channel selector <=0x3f (table+sel*0x60, state check), then write a single dword to the user ptr or dispatch through an ASD-internal fn-ptr. Get/ set-one-dword-on-a-bounded-channel ops: no message-controlled length, no copy into ASD, no attacker-controlled index. Negative. Pass 1+2 conclusion: across ~15 service-2 handlers in the 0x180 and 0x800/forwarded groups plus their channel workers, ASD's TA-reachable surface is uniformly validated, matching the documented result that its 37 host-reachable opcodes are all bounded. No memory-safety defect found. Flagged honestly that this is a manual pass, not an exhaustive validated-oracle sweep, so it does not CLOSE the surface; the 0x200/0x300 groups, the exact 0xd12e90 region semantics, and the per-channel fn-ptr handlers remain unaudited. Net: the escalation route is structurally open and proven up to the ASD message boundary, but ASD is well defended -- capability demonstrated to the ASD door, no hole found in the door yet.