aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/FINDING-RING-LOADTA-WORKS-RAS-SPECIFIC-FAILURE.md
blob: 8e9793d220cf7c2b1751cfaf2655557fb475c7ee (plain) (blame)
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# Ring LOAD_TA works in `full_system`; the failure is RAS-specific

Enabled by fixing the response readback (`Ring.response()` — the firmware writes
the status into the ALIAS copy of the command buffer). With real statuses
visible, `LOAD_TA` over the host ring can finally be evaluated.

## Measured

| image submitted | status | session |
| --- | --- | --- |
| RAS (`ras_full.bin`) | `0xffff000b` | 0 |
| **HDCP (`hdcp_full.bin`)** | **`0x00000000`** | 0 |
| RAS with 0x40 bytes of body corrupted | `0x34` (52) | 0 |
| all-zero image | `0x2d` (45) | 0 |

Four distinct outcomes, so the path discriminates properly:

* **`LOAD_TA` over the ring works.** HDCP loads with status 0. The earlier
  reading that "LOAD_TA registers no session" was an artefact of the stale
  readback, and the follow-on "LOAD_TA fails" was true only for RAS.
* `0x34` is signature-verification failure — the same code `load_asd.py` reports
  for a bit-flipped image, so the verification path is live and correct.
* `0x2d` for an all-zero image is a distinct, earlier rejection.
* **RAS alone returns `0xffff000b`**, and it is not "already loaded": HDCP is
  equally loaded at boot and succeeds.

## Where it fails

`tee_cmd_dispatch` reaches cmd 1 through `tbb [pc, r6]` at 0x202922 (table at
0x202926; cmd 1 -> 0x2029AC). The handler calls **`tee_load_ta`** at 0x202C24
and takes its return as the status — so `0xffff000b` originates inside
`tee_load_ta`, not in the dispatcher.

The sOS trace during the failing load runs through TA manifest parsing —
`ta_prop_get`, `ta_prop_get_heap_pages`, `ta_prop_get_appid`, `bl_rpc_probe`,
`sysdrv_rpc_3104/3105/1005` — which points at a **resource or property
condition specific to RAS's manifest** (heap pages being the obvious candidate)
rather than at image validity, since the image passes signature checks that a
corrupted one fails.

## What this unblocks

HDCP loads and returns a session over the ring, so the **`INVOKE_CMD` path can
now be exercised end to end in `full_system`** against a TA that loads. That was
the thing blocking command-coverage work, and it no longer depends on solving
RAS first.

The RAS-specific `0xffff000b` remains open, with a concrete next step: instrument
`tee_load_ta` and find which property check rejects RAS but not HDCP.

---

# Narrowing RAS's `0xffff000b`: it is not the image loader

Differentially traced HDCP (loads, status 0) against RAS (`0xffff000b`), each in
a **fresh build**, filtering the idle spin loop that otherwise swamps the trace
(343k blocks of `bl_main`/`bl_finalize_boot` phase drift).

## Two hypotheses, both refuted by measurement

**"RAS is already loaded at boot."** Refuted in the same run that raised it:
HDCP is equally loaded at boot and returns status 0.

**"RAS trips the size guard at `0x2041F2`."** `bl_load_ta_image` has exactly one
site setting the error code 0xb — `movs r4, #0xb` at `0x2041F2`, reached from a
page-count check (`> 0x400`) and an available-memory check. Hooking both guards
shows **RAS never reaches it**, and the two TAs pass identically:

    HDCP: combined field 0x6 (limit 0x400), need 0xf000, avail 0x3e0000 -> FITS
    RAS:  combined field 0x6 (limit 0x400), need 0xf000, avail 0x3e0000 -> FITS

So the failure is not a memory-size rejection, which was the natural reading of
the `ta_prop_get_heap_pages` trace.

## Where the paths actually part

The two runs are block-identical for 186 blocks and diverge at **`0x2047F0`**,
`bl_load_ta_image`'s continuation test:

    002047e0  cbz  r4, #0x2047f0        ; r4 == 0 -> SUCCESS so far, BOTH take this
    002047f0  ldr  r0, [pc, #0x9c]      ; lit = 0x002060ec
    002047f4  ldr.w r0, [r0, #0x824]
    002047f8  cbz  r0, #0x20480e
    002047fa  ldr  r0, [sp, #0x178]
    002047fc  cbz  r0, #0x20480e

`r4 == 0` for **both** TAs, so `bl_load_ta_image` succeeds for RAS too. HDCP then
loops back to `bl_load_ta_image+0x9c` to process a further segment; RAS falls out
to `0x20480E` and returns.

Back in `tee_load_ta`, `movs r7, r0` / `bne` at `0x202CCE` is **not** taken for
RAS — confirming a zero return — and RAS proceeds through the `sl == 2` branch to
`0x202CF2`.

## Conclusion and next step

`0xffff000b` is produced **after** the image loader, on `tee_load_ta`'s path
through `0x202CF2`, with `sl == 2` selecting a different arm than HDCP's
`sl == 1`. The image loads fine; something downstream rejects RAS.

Next step is narrow and concrete: instrument `tee_load_ta` from `0x202CF2`
onward and find the site that sets 0xb, with `sl` as the discriminator.

---

# Chain complete: the rejection is the TA-side init handshake

Continuing past `0x202CF2`, `tee_load_ta`'s `sl == 2` arm reaches:

    00202d14  sub.w r1, r0, #0x4100      ; typeword must be 0x4154
    00202d18  subs  r1, #0x54
    00202d1a  bne   #0x202d7e
    ...
    00202d46  movs  r2, #3               ; command 3 = init/create
    00202d4c  bl    #0x201848            -> ta_send_command
    00202d50  movs  r7, r0               ; <- THIS becomes the ring status
    00202d52  beq   #0x202d62

Measured for both TAs:

| | typeword @0x202D14 | `ta_send_command` | ring status |
| --- | --- | --- | --- |
| HDCP | `0x4154` (ok) | called, returns **`0x0`** | `0x00000000` |
| RAS | `0x4154` (ok) | called, returns **`0xffff000b`** | `0xffff000b` |

Both pass the typeword gate, both reach the send. The status the host sees is
`ta_send_command`'s return, verbatim.

## What this means

The full chain is now traced end to end:

    ring LOAD_TA
      -> tee_cmd_dispatch, tbb [pc,r6] @0x202922, cmd 1 -> 0x2029AC
      -> tee_load_ta @0x202C24
      -> bl_load_ta_image            SUCCEEDS for both (r4 == 0)
      -> ta_send_command(cmd 3)      HDCP 0x0,  RAS 0xffff000b   <== origin

So **RAS's image loads and verifies fine**; what fails is the init/create
handshake with the loaded TA. That is consistent with the independent
observation in this same harness that RAS's init gate at VA `0x4060E0` reads
`0x00` (closed) while HDCP's reads `0x90` — the RAS TA is not completing its
init in this environment.

This reframes the remaining question. It is not "why does LOAD_TA reject RAS"
but "why does the RAS TA fail its init handshake", which is a TA-side question
about RAS's environment (its RPC dependencies, `sysdrv_rpc_3104/3105/1005`
appear in the load trace) rather than a loader question.

Note also that `0xffff000b` may be a timeout rather than a TA-authored code:
`ta_send_command` returning it does not prove the TA replied with it. Deciding
between "RAS replied with an error" and "RAS never replied" is the next
discriminator, and it is cheap — check whether RAS's command handler executes
during the send.

---

# RAS authors the error itself, and the single fix-point is located

## It is not a timeout

`ta_send_command` returning `0xffff000b` did not prove the TA replied. Checked:
**both TAs execute during the send** — HDCP 9 blocks, RAS 13 — so RAS is running
its handler and answering, not timing out.

RAS's 13 blocks, with the error appearing on return:

    0x4045f0 0x404608 0x404618 0x404620 0x40466c
    0x402c64 0x402ed0 0x402f0c 0x402c70 0x402c94   <- r0 becomes 0xffff000b
    0x404670 0x404674 0x4045e8

## The mechanism

    00402c64  push {r4,r5,r6,lr}       ; RAS init
    00402c6c  bl   #0x402ed0
    00402c70  cbz  r0, #0x402c94       ; callee returned 0 -> error
    00402c94  ldr  r0, [pc, #0x30]     ; lit = 0xffff000b
    00402c96  pop  {r4,r5,r6,pc}

`0x402ed0` scans its argument nibble-by-nibble for the first nonzero nibble,
stores that index, and requires the nibble to be 5, 6 or 7. RAS receives
**r0 = 0** as its first argument — passed in, not read from memory (measured:
`r0=0x0, r1=0x412010, r2=0x412008`) — so the callee returns 0 immediately.

## Controlled experiment, with the control that mattered

Forcing r0 at the function entry changed nothing, which would have refuted the
whole story. But **the experiment had to be validated before being believed**,
and it failed validation:

| intervention | ring status |
| --- | --- |
| baseline | `0xffff000b` |
| force r0 **after** the call, at `0x402C70` | **`0x00000000`** (success) |
| force r0 at entry, `0x402C64` | `0xffff000b` (no effect) |

The middle row is the positive control: `reg_write` from a code hook *does*
affect this TA, and supplying a nonzero value at the decision point makes the
whole `LOAD_TA` return **success**. So the null result at the entry site is an
artefact of writing a register at a translation-block boundary, not evidence
about the firmware. Without that control the natural reading would have been
"hypothesis refuted", and it would have been wrong.

## Conclusion

RAS's image loads and verifies; RAS's init then declines because a single input
word reaches it as 0, and `0xffff000b` is RAS's own literal for that. Forcing
the decision point to nonzero yields a clean `LOAD_TA` success, so this is the
only thing standing between `full_system` and a working RAS session.

**This is an emulator environment gap, not a firmware defect, and it is
deliberately NOT patched.** A forced register would be exactly the "stub
returning 0 is a silent lie" failure this project avoids — the value is a real
input that hardware supplies and our environment does not. The remaining work is
to find what legitimately produces it; the card runs RAS 27.0.2.5 successfully,
so the value is observable there.

## Attempt to find the legitimate source: inconclusive (recorded so it is not repeated)

The obvious next move is to find what *should* produce that word. sOS builds the
init parameter block just before the send:

    00202d24  ldrb r0, [r4, #1]
    00202d26  cmp  r0, #0xff
    00202d28  beq  #0x202d36        ; 0xff -> skip filling the params
    00202d2a  ldr  r0, [r4, #4]     ; -> sp+0x20
    00202d2e  ldr  r0, [r5, #0x30]  ; -> sp+0x24
    00202d32  movs r0, #7           ; -> sp+0x1c

Tried to read `[r4+1]`, `[r4+4]`, `[r5+0x30]` and the assembled parameter block
from inside a code hook, for both HDCP and RAS. **All of the reads fail** with a
Unicorn error: those are sOS virtual addresses, and `uc.mem_read` in that hook
context does not resolve them.

So this says nothing about the firmware — no conclusion should be drawn from it
in either direction. The next attempt needs to resolve the sOS stack/descriptor
VA to its physical address first (the harnesses already do this elsewhere for TA
spaces, e.g. gating attribute PAs by trampoline VA), and read through that.