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
|
# TA -> sOS kernel surface under ROP (GCP sel-4 chain), with corrections to SVC-OVERVIEW
Now that `FINDING-GCP-FE0A-SEL4-CHOSEN-CONTENT-WRITE.md` gives repeatable ROP inside GCP, the TA can
issue **any SVC with any registers**, not just the `0xF0`/`0xF2` its own code uses. This re-opens
the "compromised TA" surface that `ANGLE-TA-KERNEL-SURFACE-CAPABILITY-BOUNDARY.md` and
`AUDIT-TA-KERNEL-BOUNDARY.md` analysed. This note resolves a contradiction between those two docs
against the gate bytes, corrects two SVC-OVERVIEW errors, and ranks the escalation leads. Source:
`sos2.elf`, gate at `0x587C`.
## Resolved: a type-5 TA can issue 0xE0-0xE9 (the boundary audit was wrong)
The two docs disagreed: the ANGLE doc said `0xE0-0xE9` reachable (cap bit `0x20` set in TA byte
`0xF5`); the boundary audit said only `0x75` and `0xF0+`. The gate at `0x58BA` settles it:
0x58ba cmp r1,#0xef ; bhi 0x58d4 ; 0xF0+ -> no check
0x58be ldrb caps[pid] ; mvns ; lsls #0x1a ; bpl allowed ; 0xE0-0xEF: allowed iff caps bit 0x20 set
0x58c8 movs r0,#0xe (deny)
TA byte `0xF5` has bit `0x20` set, so **`0xE0-0xEF` are allowed**; `sub_58F0`'s `cmp r1,#0xa` caps it
at `0xE0-0xE9`. Reachable TA kernel surface = **`0x75`, `0xE0-0xE9`, `0xF0-0xF5`** (16 SVCs). The
boundary audit's "exactly two options" is corrected.
## Corrected: SVC-OVERVIEW's E-alias mapping
Decoded the `0x58F0` tbb (table at `0x590C`, entries `0xE0..0xE9`) and each worker:
| SVC | worker | what it is |
|---|---|---|
| 0xE0 | 0x5916 -> big validate chain | register/get object (0x59-ish) |
| 0xE1 | 0x662c | wait/yield (0x56) |
| 0xE2 | 0x553c | object/session op (0x57) |
| 0xE3 | 0x1908 | arg validate / common |
| 0xE4 | 0x5998 | object op (0x5E-ish) |
| 0xE5 | 0x4860 | find object by (id,type) (0x5F) |
| 0xE6 | 0x6e40 | **sub_6E40 = 0x5A "map/reserve range"** |
| 0xE7 | 0x6e40 | same, different arg shape |
| 0xE8 | 0x6f38 = `bx lr` | **NO-OP stub** |
| 0xE9 | 0x6f38 = `bx lr` | **NO-OP stub** |
**SVC-OVERVIEW was wrong that `0xE9 -> 0x7C` poke.** `0xE8`/`0xE9` call `bl 0x6f38`, which is a
standalone `bx lr` (verified: `70 47` at a real 2-byte boundary, manual BL decode confirms target
`0x6f38`). They do nothing.
## The most-direct escalation (arbitrary kernel poke) is NOT reachable
The `0x7C`/`0xA0` "write value to an address" worker (`sub_6F3A`, `0x6f3a`) is real and its gate
`sub_6D20` is permissive -- it returns OK for any `addr+8 <= 0xCB00000` outside the
`~0x3700000-0x3a00000` MMIO apertures (limit literal `0xCB10000`, tables `0x80d8`/`0x8114`). That
window plausibly covers kernel BSS/structures. **But `0x7C` and `0xA0` are both `<= 0xDF`, and the
gate denies type-5 TAs everything `<= 0xDF` except `0x75`.** So the poke is unreachable from a TA,
even a fully ROP'd one. This is the main negative: ROP in GCP does not hand you a kernel write SVC.
## Ranked leads that ARE reachable
1. **`SVC 0xF2` -> services, with a now-arbitrary message.** Service 1 (crypto) is
permission-exempt (`AUDIT-TA-KERNEL-BOUNDARY.md` step 3), and ROP lets us send any service id +
fully crafted message, not GCP's fixed crypto-shaped RPCs. The message-pointer range check is
enforced, but message *content* is unconstrained -- a parsing bug in a service handler pivots us
into that (more privileged) process. This is the richest surface and the natural next target.
2. **`SVC 0xE6/0xE7` -> `sub_6E40`.** Not a raw physical map as SVC-OVERVIEW implied; it walks a
per-object permission bitmap (index `r8>>5`, bit `1<<(r8&0x1f)`, args `r1..r3` bounded `<0x80`)
and clears/sets capability bits over a kernel table. Whether a TA can flip a bit that grants
itself a broader capability (e.g. turning on the `0x20`/other bits, or affecting another object)
is unaudited and is the second target.
3. **`SVC 0xF2` third window headroom bug (`VULN-SOS-PROCSWITCH-MSG-HEADROOM`).** 84-byte overrun
into SYS_DRV's base, but the vulnerable window requires caller not type 5/1 or a nested
cross-process call (`[tcb+0x4c] != 0xFF`). From a bare TA it is not reached; worth re-checking
whether a ROP chain can set up the nested-call condition before issuing `0xF2`.
## Net
ROP in GCP widens the TA kernel surface from 2 SVCs to 16, but does not directly expose a kernel
write/map: the poke is type-gated out, and the reachable map-alias is a permission-bitmap op. The
real escalation path is unchanged in target but newly unlocked in capability: **arbitrary crafted
`SVC 0xF2` messages to the permission-exempt crypto service (and ASD).** That, not a magic SVC, is
where TA->kernel/other-process escalation should be pursued next.
## Addendum: fuses are not TA-reachable, but ASD (service 2) is -- and that escapes the type-5 jail
Question raised: can `SVC 0xF2` read efuses? No, not from the TA. The fuse functions
(`fuse_read_flag/config/pair`, `fuse_program_bits`, `debug_unlock_apply`) live in **SYS_DRV =
service 3** (`LEAD-SYSDRV-FUSE-DEBUG-DMA-SURFACE.md`). The `0xF2` gate `sos_proc_switch` (0x22B4),
decoded:
0x22bc cmp r0,#0x10 ; bhs reject (target>=16)
0x22c8 ldrb r5,[svc_table, r0] ; cbz -> 0x2f (unregistered)
0x22d4 cmp r0,#1 ; beq ALLOW ; service 1 (crypto) EXEMPT
0x22dc ldrb caps[caller] ; lsls #0x18 ; bpl reject(0xe) ; else need cap bit 0x80
0x22e6 cmp r0,#3 ; beq type-check ; service 3 (SYS_DRV):
0x22f2 proctype[caller]==1 ? ALLOW : reject(0xe) ; requires caller TYPE==1
A TA is type 5, so service 3 (and its fuse reads) is rejected. No TA-reachable SVC does an MMIO/fuse
read, and the boot-cached fuse globals are returned only by `<=0xDF` query SVCs a TA cannot issue. So
there is no one-call fuse read from the TA. Confirmed, not assumed.
**However** the TA byte `0xF5` has bit `0x80` set, so a ROP'd TA passes the gate for **service 2
(ASD)** too (only service 3 needs type 1; services 1 and 2 need only the `0x80` bit). This is the
escalation route, and it is better than "lead 1 = craft 0xF2 to crypto":
* The SVC gate (0x587C) restricts the full `<=0xDF` surface to **types 5/6 only**. **Type 2 (ASD)
and type 1 can issue every `<=0xDF` SVC** -- including the `0x7C`/`0xA0` poke, `0x7A`/`0x96` MMIO,
and the register reads.
* So code execution in ASD (reached by a crafted `SVC 0xF2` message that triggers a bug in ASD's
message handler) escapes the type-5 jail entirely: as type 2 it can MMIO-read the FUSE/CCEFUSE
registers directly, and reach much more besides.
* If a crafted message to the **service-1 (crypto) owner** (slot 0, type 1 in the live table dump)
yields code exec there, that is type 1 -> SYS_DRV service 3 -> `fuse_read_*` directly.
**Revised next target: ASD's `SVC 0xF2` message handler**, reached from GCP ROP with an arbitrary
crafted message (GCP already RPCs ASD for cmds 0x109/0x201/0x204/0x305, so the transport works). A
memory-safety bug there is a type-5 -> type-2 escalation, from which fuses (and the `<=0xDF` kernel
surface) become reachable. Audit ASD's handler (`asd` dispatcher, e.g. sub_D013B8) for the opcodes
reachable over service 2 and their message parsing.
|