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
|
# LATENT: RAS TA — out-of-bounds parameter-array read (NOT host-reachable)
**Component:** RAS TA (`ras2.elf`), `ras_map_shared_buffer` @ `0x402ED0`
**Reached via:** host `LOAD_TA(RAS)` -> `OPEN_SESSION` -> `INVOKE_CMD`
**Class:** out-of-bounds read of the GP parameter array -> type-confused pointer,
subsequently dereferenced for **read and write**
**Status:** **NOT HOST-REACHABLE — see section 7. The reachability claim
originally made in this file is RETRACTED.** The latent defect is real and
confirmed at instruction level, but sOS's parameter marshalling makes the
triggering condition unsatisfiable.
--------------------------------------------------------------------------------
## 1. The defect
`ras_map_shared_buffer(paramTypes, params, &out_idx)` scans for the first
parameter slot whose type nibble is non-zero. **The loop has no failure path.**
0x402ed2 CBZ R0, fail ; paramTypes == 0 -> fail (the ONLY guard)
0x402ed4 MOVS R3, #0 ; bit offset
0x402ed6 MOVS R4, #0 ; i
loop:
0x402ed8 CMP R4, #4
0x402eda BEQ 0x402EEC ; <-- i == 4 FALLS THROUGH with R3 = 16
0x402edc LSR.W R5, R0, R3
0x402ee0 LSLS R5, R5, #0x1C ; nibble i != 0 ?
0x402ee2 BNE 0x402EEA
0x402ee4 ADDS R3, #4
0x402ee6 ADDS R4, #1
0x402ee8 B loop
0x402eea STR R4, [R2] ; record slot index (SKIPPED on fall-through)
0x402eec LSRS R0, R3 ; R3 == 16 -> nibble 4
0x402eee AND.W R0, R0, #0xF
0x402ef2 SUBS R0, #5
0x402ef4 CMP R0, #2
0x402ef6 BHI fail ; nibble must be 5, 6 or 7 (memref types)
0x402ef8 ADD.W R0, R1, R3,LSL#1 ; params + (16<<1) = params + 32
0x402f00 LDR R0, [R0,#4] ; *** params[4].size -- OUT OF BOUNDS ***
0x402f02 CMP R0, R2 ; >= 0x828 ?
0x402f04 BCC fail
0x402f06 LDR.W R0, [R1,R3,LSL#1]; *** params[4].buffer -- OUT OF BOUNDS ***
0x402f0a POP ; returned as the command buffer
The GP parameter array has **four** entries — the loop's own `CMP R4,#4` bound is
the author's statement of that. Indexing slot 4 reads **8 bytes past the array**.
### Trigger
paramTypes nibbles 0..3 all zero, nibble 4 in {5,6,7}
e.g. paramTypes = 0x50000
and the 8 bytes following the array must satisfy size >= 0x828
## 2. Why it is host-reachable
`TA_InvokeCommandEntryPoint` performs **no `paramTypes` validation** before the
call — unlike its siblings:
RAS : BL ras_map_shared_buffer <- paramTypes used raw
DTM : AND.W R1,R2,#0xF ; CMP R1,#7 ; BNE reject
HDCP : AND.W R0,R2,#0xF ; CMP R0,#7 ; BNE reject
RAS disassembly:
0x402b12 MOV R7, R2 ; paramTypes, straight from the host
0x402b18 MOV R1, R3 ; params
0x402b1c BL ras_map_shared_buffer
`paramTypes` and `params` are supplied by the host in `INVOKE_CMD`. RAS is a
host-loadable TA.
## 3. Impact — the OOB pointer is read AND written
`TA_InvokeCommandEntryPoint` uses the returned pointer `v8` directly:
* `switch ( *(_DWORD *)v8 )` — **dereferenced read** (command id)
* `STR R1, [R4,#8]` — **write** of the handler result to `v8 + 8`
* `ADD.W R5, R4, #0x10` — `v8 + 16` handed to the command handlers
## 4. A second, compounding defect: the stale slot index
On the OOB path the `STR R4,[R2]` at `0x402EEA` is **skipped**, so
`dword_4060EC` keeps a **stale** index. `ras_attach_response_buffer` then uses
that stale index against a *different* slot than the one validated:
0x403c52 LDR R1, [R5] ; stale index
0x403c58 ADD.W R1, R4, R1,LSL#3 ; params[stale]
0x403c5c LDR R1, [R1,#4] ; .size -- NOT the slot that passed >= 0x828
0x403c5e SUBW R1, R1, #0x828 ; len = size - 0x828 -> UNDERFLOW if size < 0x828
0x403c66 B ras_bind_response_area
and `ras_bind_response_area` performs `bzero(ptr, 0x14)` at
`oob_buffer + 0x810` — an **out-of-bounds-derived write**, with a response-area
length that can underflow to ~4 GB.
## 5. ~~Distinguishing this from the project's previous dead ends~~ RETRACTED
I claimed this differed from the previous six dead ends because `paramTypes`
comes straight from the host. **That was wrong, and I wrote it before completing
the analysis.** The host does not control `paramTypes` directly — sOS constructs
it.
## 6. Why it is NOT reachable: sOS re-packs paramTypes to 16 bits
`tee_unmarshal_params` (`0x17884`) unpacks the host's type nibbles into four
*bytes*, then re-packs them into nibbles before handing the value to the TA:
*a1 = (unsigned __int8)v27 // t0 -> bits 0..3
| (16 * BYTE1(v27)) // t1 -> bits 4..7
| (BYTE2(v27) << 8) // t2 -> bits 8..11
| (HIBYTE(v27) << 12); // t3 -> bits 12..15
Each `tN` is a nibble (`v9 & 0xF`), so the maximum value is `0xFFFF`:
**bits 16 and above are always zero.**
In `ras_map_shared_buffer` the nibble-4 test happens *before* the out-of-bounds
access:
0x402eec LSRS R0, R3 ; paramTypes >> 16 == 0 ALWAYS
0x402ef2 SUBS R0, #5 ; 0 - 5 = 0xFFFFFFFB
0x402ef4 CMP R0, #2
0x402ef6 BHI fail ; <-- ALWAYS TAKEN on the exhaust path
0x402ef8 ADD.W R0, R1, R3,LSL#1 ; params+32 (never reached)
0x402f00 LDR R0, [R0,#4] ; the OOB read (never reached)
So when the loop exhausts, the function returns 0 and
`TA_InvokeCommandEntryPoint` returns `-65525`. The OOB read is never performed,
and the stale-index path in section 4 is likewise unreachable.
## 7. Verification status
* the loop fall-through, the OOB indexing, the absent `paramTypes` validation in
RAS, and the read/write use of the pointer: **CONFIRMED at instruction level**
* **reachability: REFUTED.** sOS guarantees `paramTypes <= 0xFFFF`.
* No hardware test is warranted.
## 8. What would make it live
Any caller that reaches `ras_map_shared_buffer` with a `paramTypes` whose bits
16..19 are in {5,6,7}. sOS's marshalling forbids this today. A future sOS that
widens the type field, or any direct caller bypassing `tee_unmarshal_params`,
would expose it. **Worth a regression check on future firmware.**
|