1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# ASD service-2 message handler: attack surface for a GCP-ROP -> ASD escalation
Target of `fw/LEAD-GCP-ROP-TA-KERNEL-SURFACE.md`'s revised plan: a ROP'd GCP TA can send an
arbitrary crafted `SVC 0xF2` message to **service 2 = ASD** (reachable: TA cap bit `0x80` is set;
only service 3/SYS_DRV needs type 1). Code exec in ASD is a type-5 -> type-2 escalation that escapes
the `<=0xDF` SVC jail (types 1/2 may issue the full surface incl. the `0x7C`/`0xA0` poke and MMIO),
from which fuses become reachable. This note maps ASD's dispatcher so the bug hunt is resumable.
Source: `asd_code.bin`, base `0xD00100` (two-point verified).
## Transport (already proven to work)
GCP RPCs ASD over service 2 today for cmds `0x109/0x201/0x204/0x305` (see GCP `0x4061d0`:
`stm sp,{cmd,arg..} ; movs r0,#2 ; svc 0xf2`). The message is a small struct in the TA's address
space (the `0xF2` range check pins a type-5 caller's `msg` to `[0x400000,0x800000)`):
msg[0x00] = cmd msg[0x04] = arg1 msg[0x08] = arg2 msg[0x0c] = arg3 ...
From ROP we reuse that exact `svc 0xf2` path with any `cmd` and any args.
## Dispatcher `sub_D013B8` (0xD013B8): command groups
cmd = *msg (r6). Range dispatch:
0x180..0x1C0 -> 0xd01464 tbh table, 28 distinct handlers <- biggest, GCP-unused
0x100..0x10D -> 0xd014f2 (GCP uses 0x108/0x109)
0x200..0x20E -> 0xd0151c (GCP uses 0x201/0x204)
0x300..0x309 -> 0xd01546 (GCP uses 0x305)
0xFFFF0000..7 -> 0xd01568 tbh
0x402..0x405 -> 0xd01588 tbh
0x160,0x161,0x800,0x801,0x802 -> individual
else -> [msg+4] -> bl 0xd15bcc
## The 0x180 group (0xd01464): 28 handlers, each forwards message fields to a worker
Examples (all read `msg+4..`):
0x182-0x185 ldm {r0,r1,r2} from msg+4 -> worker(arg1,arg2,arg3) (ptr/val/len shape)
0x186 r0=msg+4, r1=u16 msg+8, r2=u16 msg+0xa -> 0xd0f2ac (ptr + two u16 lengths)
0x18c ldm {r0,r1,r2,r3} from msg+4 -> 0xd0f960 (4 controlled args)
0x18e/0x1a7 ldrd msg+4, byte msg+0xc, word msg+0x10 -> worker (ptr64 + len/flags)
... (full list enumerated in the audit script output)
Each worker begins with a capability gate `bl 0xd116ac` (r0 = a per-cmd capability id, e.g. 0xf).
## The gate `0xd116ac` is permissive in the unprovisioned state
ldrb r2,[global] ; cbz r2 -> return 1 (ALLOW) ; if the ASD state byte is 0, EVERY cap passes
else: switch(capid) -> load [global+off] ; return (slot != 0)
So in ASD's default/unprovisioned state (the state GCP drives it in over the ring), the byte is
plausibly 0 and the whole 0x180 group is **open**. Once ASD is provisioned the per-capability slots
gate it. The runtime value of `[global]` in our scenario is the first thing to pin (emulator boots
real ASD; `psp_emu` full_system loads ASD).
## The bug to find (not yet found)
A 0x180-group (or 0x200/0x300/0x800-group) handler that, past the gate, copies a
**message-controlled length** of bytes into a **fixed-size ASD buffer** (stack or global), or
indexes an ASD table/function-pointer array with a **message-controlled index** beyond its bounds.
The dispatcher already shows one indirect-call-through-table site (`0x186` -> `blx [table+0x1828]`),
so an index/table-base bug there yields ASD PC control directly. Candidate workers to read next, for
the (dst,src,len)/(ptr,len) copy shape: `0xd0f648, 0xd0f960, 0xd0f114, 0xd0fa1c` (the multi-arg
ones). Pointer args are constrained to the TA window, but ASD-internal destination buffers are not,
so a controlled length overflows ASD memory.
## Why this is the right target
Code exec in ASD (type 2) is the pivot the whole session's GCP ROP work was building toward for
kernel/fuse reach: type 2 is not in the `<=0xDF` SVC jail, so it can MMIO-read the FUSE/CCEFUSE
blocks directly (and reach SYS_DRV's surface). The GCP->ASD transport is proven; the gate is
plausibly open; the only missing piece is one memory-safety defect among ~40 reachable ASD handlers.
Audit scripts: this map was produced by disassembling `asd_code.bin` at base 0xD00100; the 0x180
tbh table and worker heads are reproducible from the dispatcher at 0xD013B8.
## Audit pass 1: first-level 0x180 handlers validate (ptr,len), but only against a 256 MB cap
Read the multi-arg workers `0xd0f648 (0x182)`, `0xd0f960 (0x18c)`, `0xd0f114 (0x18e)`, `0xd0f2ac
(0x186)`. All share a shape and come back **bounded at this level**:
1. capability gate `bl 0xd116ac` (permissive while ASD unprovisioned, see above);
2. channel index arg1 (`msg+4`) bounded `<= 0x3e/0x3f`, then `table + arg1*0x60` -- no OOB index;
3. pointer/length args validated via `bl 0xd12e90` before use;
4. dispatch `blx [asd_table + 0x183c / 0x1828 / 0x1c84]` to a per-channel handler, or a fixed-size
copy (`0x18e` copies a constant 5 bytes per element via `0xd01058`).
So the first dispatch level is not trivially overflowable. **But the validator does not close the
overflow class.** `0xd12e90(ptr,len)`:
cmp ptr,#0x40000000 ; it ls ; cmpls len,#0x10000000 ; bls ok ; else return 0
(then 0xd12ea4+ : region-overlap checks on [ptr, ptr+len))
It bounds `len <= 0x10000000` (256 MB) and checks the source range -- it does **not** check `len`
against any destination buffer size. So a downstream per-channel handler that copies a validated
`len` into a fixed-size ASD buffer is still an overflow, and the first-level validation would not
catch it.
## Audit pass 1 verdict and where pass 2 goes
Negative for the first dispatch level (validated/bounded), but the bug class is intact one level
down. Pass 2 targets:
* **The per-channel function pointers** `[asd_table + 0x1828/0x183c/0x1c84]`. Resolve the table base
(the `ldr r0,[pc,#...]` literal in each worker) and the installed handlers, then look for one that
copies the validated `len` into a fixed buffer. These are the real workers; the dispatcher just
validates and forwards.
* **The unaudited groups** `0x100/0x200/0x300/0x800/0x402/0xFFFF000x`. The `0x800` group
(`0xd015a6`) forwards `ldrd r0,r1,[msg+4]` straight to `0xd0d0bc/0xd0d134/0xd0d26c/...` with no
visible gate in the dispatcher -- read those for an unvalidated (ptr,len).
* **`0xd12e90`'s region checks** (`0xd12ea4+`): confirm whether a TA-window pointer (`0x400000-
0x800000`) with a large `len` passes, which would let a downstream fixed-buffer copy overflow from
attacker-readable source.
Net: the escalation route (GCP ROP -> ASD service 2 -> type-2 code exec -> fuses/MMIO) is
structurally open and the transport+gate are favourable; audit pass 1 cleared the first dispatch
level; the bug, if present, is in the per-channel handlers or the unaudited groups.
## Audit pass 2: the 0x800/forwarded group and tail workers are uniformly validated -- negative
Read the forwarded workers `0xd0d134/0xd0d26c/0xd0d28c/0xd0d114` and their tail targets
`0xd11238/0xd112e4/0xd11348/0xd111c8`, plus `0xd0d0bc/0xd0d170`. Uniform shape, all bounded:
* `str #0,[arg2]` zero-inits the caller's 4-byte output pointer;
* arg2 validated as a **4-byte** pointer via `0xd12e90` (len fixed at 4, so no overflow of the
caller buffer, and nothing copied *into* ASD);
* capability gate `0xd116ac`;
* channel selector arg1 bounded `<= 0x3f`, index `table + sel*0x60`, state check `[entry+4]==1`;
* result is a **single dword written to the validated 4-byte user pointer**, or dispatch through an
ASD-internal function pointer `[asd_table + 0x1888/...]`.
These are get/set-one-dword-on-a-bounded-channel operations. No message-controlled length, no copy
into an ASD buffer, no attacker-controlled index. Negative.
## Pass 1+2 conclusion: ASD's service-2 surface is uniformly validated across what was audited
~15 handlers across the `0x180` and `0x800`/forwarded groups, plus their tail/channel workers, all
share the same discipline: capability gate, bounded channel index (`<= 0x3e/0x3f`, stride `0x60`),
`0xd12e90` pointer validation, and either fixed-length copies or dispatch through ASD-internal
function-pointer tables. This matches the project's documented result that ASD's 37 **host**-reachable
opcodes are all bounded (`ASD-REACHABLE-AUDIT.md`); the **service-2 (TA-reachable)** handlers audited
here are the same style and equally defended. No memory-safety defect was found.
**This is a manual pass, not an exhaustive validated-oracle sweep**, so it does not *close* the
surface to the project's standard. What remains genuinely unaudited:
* the `0x200`/`0x300` groups (GCP uses `0x201/0x204/0x305`; likely same discipline but unread here);
* the exact region semantics of `0xd12e90` at `0xd12ea4+` -- whether a TA-window pointer
(`0x400000-0x800000`) with a large `len` passes, which is the only way the "copy len into a fixed
ASD buffer" class stays alive, and no audited handler actually performs such a copy;
* the per-channel function-pointer handlers (`[asd_table + 0x1828/183c/1888/1c84]`) in full -- they
are ASD-internal and not attacker-controlled without a prior write, but a bug inside one reached
with a validated pointer could still exist.
## Net for the escalation
The route GCP-ROP -> ASD service 2 -> type-2 code exec -> fuses is **structurally open and proven up
to the ASD message boundary** (transport works, gate is favourable). But ASD itself is well
defended: two audit passes over the most promising groups found uniform validation and no bug. An
escalation through ASD would need either an exhaustive validated sweep of all groups + channel
handlers, or a different ASD defect class (e.g. a logic/state bug rather than memory safety). The
honest status is: capability demonstrated to the ASD door; no hole found in the door yet.
|