| Commit message (Collapse) | Author | Age |
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
(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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
+ SYS_DRV DoS only
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
[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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
-- 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.
|