# Simulating the GCP->SYS_DRV crypto ROP message — our setup is correct; the reject is the SVC 0xB5 window Operator question: *are we confident our ROP sets everything up correctly? can we simulate the ROP chain and inspect the input buffers/arguments at call time?* Answer: **yes, and yes.** Built `psp_emu/harness/gcp_crypto_argcheck.py`, which reconstructs the EXACT 124-byte request our hardware harness (`gcp_rop_crypto.py`) builds for op 0x35 (`TEE_GenerateRandom`) and runs it through the firmware's own `sub_E087E4 -> sub_E1DC30` crypto dispatcher in the emulator, with the real `sub_E0DEF4` / `sub_E0AB44` validators unpatched, hooking the dispatcher, the validator, and the RNG leaf to inspect the arguments at call time. ## Our message is constructed correctly (proven, not inferred) With the emulator's default (permissive) SVC 0xB5, the real dispatcher sees EXACTLY what we intended: sub_E1DC30 entry: opcode=0x35 buf0(ptr=,len=0x4/0x10/0x20) buf1(0,0) sub_E0DEF4(ptr=, len=..., dir=0x2, client=0x2) # validated as an OUTPUT buffer >> sub_E04F38 REACHED: out= len=... # RNG leaf runs with OUR buffer out[:N] = # buffer WRITTEN * opcode routed from `msg[0]`; output ptr/len taken straight from `msg[2]`/`msg[3]`; direction classified as output (dir 2); RNG leaf reached with our exact `(out,len)`; buffer written. * **len 4, 16 and 32 all succeed** (rv=0). So the hardware len-sensitivity is NOT a message-format bug. ## The hardware reject (0xFFFF000F) is the SVC 0xB5 window check — reproduced in sim Re-modelled SVC 0xB5 to return a real `[base,limit]` region with the limit just above the buffer base (`--window lo,base+8`): [RNG len=4 ] -> rv=0x0 RNG runs, buffer written (end <= limit) [RNG len=16] -> rv=0xffff000f sub_E04F38 NOT reached (end > limit) [RNG len=32] -> rv=0xffff000f sub_E04F38 NOT reached (end > limit) Exact reproduction of the hardware len-sensitivity (`len4` accepts, `len16/32` reject `0xffff000f`), confirming the reject is the window bound check `(ptr+len)` vs `[base,limit]`, not our setup. ## The real validation chain (ground truth from sys_drv2 + sos2) `sub_E0DEF4(ptr, len, dir, client)` (sys_drv2 @ 0xE0DEF4), disassembled: if dir==1 (input) || client==0 -> return 1 (PASS, no pointer check at all) if len>0x10000000 || ptr>0x40000000 -> return 0 (FAIL) SVC 0xB5 (R0=client, R1=SP descriptor buf, R2=token) -> fills [b1,l1,b2,l2], returns R0 if R0!=0 -> return 0 (FAIL) PASS iff [ptr,ptr+len] ⊆ [b1,l1] OR ⊆ [b2,l2] OR ⊆ [dword_E25710, dword_E25714] SVC 0xB5 = sos2 svc-body case 100 = `sub_178C` (sos_check_user_ptr, generic `[0x200000,0x10000000]` + type-4 window exclusions) then `sub_27F8`, which FILLS the `[b1,l1,b2,l2]` descriptor from the CLIENT process entry: type 5 : l1 = 0x800000 ; b1 = (mode==1 ? 0x400000 : proc[6]) ; b2/l2 from proc[v1] type 2 : [0xE00000, 0xF00000] type 4/6: [0xD00000, 0xE00000] So for a **type-5** client with mode==1 the window is the wide `[0x400000, 0x800000]`, which covers our buffer at any length — but when `b1 = proc[6]` (mode!=1 path) the base is the client's actual image base, a narrow window. The accept/reject therefore turns on the **client process type and the mode token** presented at validation time, plus the SYS_DRV global fallback region `[dword_E25710, dword_E25714]` (runtime values, set at init). That selection is the knob that flips the verdict per boot — not anything our ROP controls. Window recorder for the type-5 path, `sub_22B4` (sos2): on the gated svc it writes `tcb[24] = *(tcb+0x34) - 0x44` (base) and `tcb[25] = tcb[16]` (limit) when `base>=0x400000 && *(tcb+0x34)<0x800000`. ## Conclusions 1. **Our ROP/message construction is correct** — proven by executing it through the real firmware dispatcher and watching every argument land where intended and the RNG write happen. 2. The crypto-output `0xFFFF000F` reject is **the SVC 0xB5 client-window validation**, reproduced in sim, governed by the client process type/mode and the SYS_DRV global region — state we do not set. 3. The emulator cannot reproduce the hardware accept/reject directly because its SVC 0xB5 is deliberately permissive (`[0,0xFFFFFFFF]`); `--window` lets us model it and reproduce the reject. Tool: `psp_emu/harness/gcp_crypto_argcheck.py` (`--window lo,hi` to model the SVC 0xB5 region). ================================================================================ ## Hardware verdict: no deterministic output placement — SVC 0xB5 client check fails Pinned the per-boot knob and tested on silicon. SVC 0xB5's return (which gates all of `sub_E0DEF4` via `if R0!=0 -> FAIL`) is `sub_27F8` -> `sub_1978(client_id)` (sos2): sub_1978(client): return 0 (OK) iff client < 0x20 AND proc_table[client].flag < 0 (active bit); else return 37 (FAIL) So if the client id SYS_DRV presents for our confused-deputy call does not resolve to an ACTIVE process entry, the validation fails for ANY output buffer/length -- a client/session reject, not a buffer-placement one. That is buffer-independent: no placement can fix it. Hardware pass-rate test (op 0x35 len4 @ 0x410380, dumped, fresh ATX per boot): rnglen / rngdet / rngdet4 / passrate : ~25 boots, ALL reject 0xffff000f, buffer never written acceptsweep (the one apparent pass, 11/11 status 0) : did NOT reproduce -- its exact config (0x410380, DUMP_OFF 0x380) now rejects 10/10. Conclusion: **output-buffer crypto ops reliably reject (0xffff000f) from the GCP confused-deputy path; there is no deterministic buffer placement that makes them land.** The reject is the SVC 0xB5 client validation (`sub_1978` active-check + the region check), governed by client/session state the GCP side does not set. The single non-reproducible "pass" window is treated as noise, not a result. Security-model note (the clean asymmetry): `sub_E0DEF4` returns PASS immediately for `dir==1` (INPUT) buffers -- SYS_DRV does NOT validate pointers it only READS -- but gates `dir==2` (OUTPUT) buffers through the client check. So the confused deputy can FEED arbitrary pointers (unvalidated reads) but cannot reliably RECEIVE into a chosen buffer (validated writes). The crypto-output read-back oracle is therefore closed; the earlier inline read-back (create handle) remains the only reliable channel.