aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/FINDING-LOADTA-ADDRESS-CLASSIFICATION-ORACLE.md
blob: d4799bea66ec5e96306339a6fd87f2edaa33a338 (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
# A working, non-destructive address-classification oracle on silicon

Outcome of `STRATEGY-MEASUREMENT-FIRST.md` Prong 2, after the absent-address arm
wedged the card and was dropped.

## The oracle

`LOAD_TA` lets the host choose `app_phy_addr`; the PSP then reads and verifies
that buffer. The returned status classifies what the PSP found:

| source | status |
| --- | --- |
| a real, signed TA image | **`0x0`** (loads, session allocated) |
| readable VRAM, invalid contents | **`0x34`** (52, signature verification failure) |
| a range overlapping the **TMR** | **`0xA`** (10) |

Two different junk buffers both return `0x34`, so the status is not noise; a real
image returns `0x0`, so the path works. Both controls pass, and the PSP heartbeat
stays alive through every probe — this is **non-destructive**, unlike the absent-
address probing that cost a power cycle.

`0xA` is the same code sOS's own gate uses for a rejected pointer
(`GATE_BAD_PTR` in `pspemu/gate.py`), which suggests the refusal is a range
check rather than a protection fault raised by hardware. For the oracle's
purpose the distinction does not matter: the address is *classified*.

## Validated against independent ground truth

The TMR is at PA `[0x300000, 0x1600000)` from the SMU/PSP fields
(`tmr_paddr`/`tmr_size`), measured previously and by a completely different
method. Sweeping the oracle across that boundary:

    0x200000    0x34  outside    expected outside   agree
    0x2ff000    0xa   INSIDE     expected outside   MISMATCH
    0x300000    0xa   INSIDE     expected INSIDE    agree
    0x301000    0xa   INSIDE     expected INSIDE    agree
    0x900000    0xa   INSIDE     expected INSIDE    agree
    0x15f0000   0xa   INSIDE     expected INSIDE    agree
    0x1600000   0x34  outside    expected outside   agree
    0x1700000   0x34  outside    expected outside   agree

    7/8 agree.  PSP alive after sweep.

**The one mismatch is the most useful row.** `0x2ff000` is a single page *below*
the TMR base, but the probe's `app_len` is the whole TA blob, so the source
*range* extends into the TMR. The refusal therefore covers
`[start, start+len)`, not just the start address — and the upper edge confirms
it: `0x15f0000` (extends past the end) reads INSIDE, while `0x1600000` (fully
outside) reads outside.

So the oracle answers *"does this range overlap a region the PSP refuses to read
from?"*, and page-granular results are obtainable by shrinking `app_len`.

## Scope — what it does and does not reach

* **Does:** classify **host-visible VRAM (MC) addresses**. It can map the TMR
  page-exactly and, more interestingly, **discover protected ranges we do not
  already know about**, by sweeping VRAM for regions that answer `0xA`.
* **Does NOT:** reach PSP-internal physical space. `app_phy_addr` is an MC
  address, so this says nothing about Route C's window, which lives in
  PSP-internal space. That question remains open.

## Why this one survived when the others did not

Every previous attempt needed a channel returning **data**, and none exists.
This needs only a channel returning **status**, and status was always there —
`0x34` had already been seen in `load_asd.py` for a bit-flipped image without
anyone asking what *other* values the same field could take.

Harnesses: `hw/psp_addr_classify.py` (the three-way probe with controls),
`hw/psp_addr_classify_validate.py` (the ground-truth boundary sweep).

---

# Sweep result: in the first ~1 GB of VRAM, the TMR is the ONLY refused range

Swept PA `[0, 0x3ef00000)` at 1 MB granularity with `app_len = 0x1000`
(`hw/vram_protect_sweep.py`), using the TMR as a live per-boot control — if
`0x900000` does not come back refused, the oracle is not working on that boot
and the sweep's negatives mean nothing. The control passed.

    1006 addresses probed, 4 seconds, 19 refused
    refused range: 0x00300000 .. 0x01600000   <- exactly the known TMR

**No protected range other than the TMR exists in the first ~1 GB**, at 1 MB
resolution. That is a real negative with a validated instrument, and it also
re-derives the TMR bounds independently, agreeing with the SMU/PSP fields.

## RETRACTED: there is no wall at PA 0x3ef00000 — it was ring exhaustion

The first version of this section reported a "hard wall" at PA `0x3ef00000`
above which the oracle stopped working, and concluded that its validated domain
was `PA < 0x3ef00000`. **That was wrong, and the reasoning was a textbook
confound.**

The failures began at PA `0x3ef00000`, which was *also* probe **#1006**. The
failing value was `(PA >> 16) + 0x101` — but since `PA = n * 0x100000`, that is
identically `16*n + 257`. Address-derived and count-derived fit the same data
exactly, and I picked the address explanation without testing the other.

Two measurements settle it:

* A **fresh process** probes `0x3ff00000` and `0x40000000` in 0.00 s with
  status `0x34` — no wall at those addresses at all.
* Probing **one fixed address** (`0x20000000`) repeatedly deviates at probe
  **#998**, returning `0x3ff1` — the *same* value the sweep produced — while
  `(PA>>16)+0x101` for that address would be `0x2101`. The value has nothing to
  do with the address.

`0x3ff1 = (1023 << 4) | 1`, and the latency jumps to exactly 10.00 s (a
timeout). So the number is a **ring write pointer at 1023** on a 1024-entry
ring: after ~1000 submits the ring is full and submissions stop being serviced.

## What this actually means

* **The oracle has no known address limit.** Its real limit is **~998 probes per
  process**, after which the ring must be drained or the process restarted.
* **The sweep's coverage was bounded by ring capacity, not by hardware.** It
  stopped at 1006 MB because that is where it ran out of ring, so
  "only the TMR is protected below 1 GB" stands, but the *reason* it stopped
  there was an artefact.
* A full 24 GB sweep is feasible by restarting every ~900 probes (~28 restarts),
  or by finding the ring drain.

The negative result above — TMR is the only refused range in the swept region —
is unaffected, because every probe in it returned a fast, valid verdict with the
live control passing.