aboutsummaryrefslogtreecommitdiffstats

Audit: are any sOS->SYS_DRV forwards a genuine TOCTOU?

Result: NO. Closed negative. Every live host pointer sOS passes to SYS_DRV is a response-write destination, never a parsed input. All request parsing is done against sOS's private snapshot. This closes the double-fetch hunt and supersedes two retracted findings.

Method: for each sysdrv_rpc_* call in tee_cmd_dispatch (0x147C4) that mentions src, determine (a) whether the stub actually sends the pointer to SYS_DRV, and (b) if sent, whether the handler dereferences it for reading. This is the check that was missing from the earlier, wrong analyses.

The structural fact

tee_cmd_dispatch does bl_memcpy(dst, src, 0x400) and then passes slot as the request. dst is at [sp+18h], slot at [sp+34h], so slot == dst + 28 — it points into the snapshot, at the offset of the cmd union in struct psp_gfx_cmd_resp (cmd@28). Handlers dereference the first rpc parameter, which is slot.

The src+N pointers exist so results land directly in the host's buffer without a second copy.

Per-call results

RPC src arg sent to SYS_DRV? role verdict
0x3124 src+32 (a1) no stub does STR R1,[R4] after SVC OUTPUT
0x1007 src+864 (a1) no (stub packs slot) handler derefs R6=slot OUTPUT/unused
0x310C src+864 (a2) yes, with len 0x400 handler only STRs to [R2,#0x38], [R2,#0x40] OUTPUT
0x109E src+928 (a2) yes (R9) R9 never appears in a load OUTPUT/unused
0x3127 src+932,src+928 no args sent at all STR R2,[R5]; STR R1,[R4] OUTPUT
0x3138 src+924 (a0) no result stored to *R4 OUTPUT
0x1024 src+864 (a1) yes unimplemented in this build n/a
0x106A src+864 (a1) yes unimplemented in this build n/a
0x1078 src+924 (a0) no STR R0,[R4] after SVC OUTPUT

Evidence for the "OUTPUT" calls is the post-SVC store in the stub, e.g.

sysdrv_rpc_1078 @0x12BC0:
  0x12BDA  STR  R5,[SP,#var_44]   ; only slot[0] is sent
  0x12BE6  SVC  0xF2
  0x12BEA  LDR  R0,[SP,#var_48]
  0x12BEC  STR  R0,[R4]           ; R4 = src+924  <- WRITE-BACK

sysdrv_rpc_3127 @0x12798:
  (no parameters stored into the frame at all)
  0x127BA  SVC  0xF2
  0x127BE  LDRD.W R1,R2,[SP,#var_48]
  0x127C2  STR  R2,[R5]           ; R5 = src+932
  0x127C4  STR  R1,[R4]           ; R4 = src+928  <- WRITE-BACK

0x310C — the last open item, resolved

sysdrv_tee_query_12 (0xE1BAB0) read [R1,#0x168] three times, which looked like a triple-fetch. It is not host memory:

0xE1BAC6  ADD.W R4, R4, R1,LSL#2   ; uses the INCOMING R1 (index, bounded <0xF)
0xE1BACA  LDR   R1, [R7]           ; R7 = off_E257A0 -- R1 RELOADED from a GLOBAL
0xE1BAF0  LDR.W R1, [R1,#0x168]    ; ...so this derefs the global, not the host

The incoming index is bounds-checked (CMP R1,#0xF; BCC). The host pointer is R2, which appears only as a store target (STR R0,[R2,#0x38], STR R4,[R2,#0x40]) — both inside the 1024-byte buffer (src+920, src+928).

Conclusion

sOS consistently applies snapshot-then-parse, and uses live host pointers only for writing results back. There is no double-fetch, no TOCTOU, and no attacker-controllable window in any of these forwarders.

The two earlier findings were wrong for the same reason: they observed a live pointer in an argument list and assumed it was the request. Always confirm which argument the handler dereferences, and in which direction.

What remains genuinely open elsewhere

  • 0x1024 / 0x106A are unimplemented here but are sent (src+864, 0x400) by their stubs. If a firmware version implements them, they would receive a live pointer with a length that overruns the 1024-byte buffer (864+1024 = 1888). Worth checking on other PSP builds.
  • The allow-list bypass (ring == 0xFFFF, see ATTACK-SURFACE.md addendum) stands and is unrelated to this audit.