Route C's write window identified: PSP-internal physical space, deliberately unguarded
VULN-LOADIPFW-UNSIGNED-REGWRITE.md has carried "capability real, impact
unestablished" because nobody could say what [0x21500, 0x21B78] actually is.
A VCN reading was proposed and refuted on hardware
(RERATE-ROUTEC-TARGET-MAY-BE-VCN.md). This settles it from the firmware.
The mechanism
Route C's writes go through SVC 0x7C, which resolves addresses via sOS's
vawin_map_fixed (FA 0x3D8C). That function splits its address argument:
r1 = (addr >> 16) & 0xFFF0 ; programmed into a VA-window register
r5 = addr & 0xFFFFF ; 20-bit offset within a 1 MB granule
So the address is a PSP-internal physical address, mapped a megabyte at a
time into the caller's VA — not a host SMN address, which is why every attempt
to read 0x21xxx over the host SMN aperture mirrored.
The access control, and what it does not cover
vawin_map_fixed gates every request through sos_check_va_window_request
(FA 0x6D20), which rejects exactly two ranges plus an overflow case. Measured by
calling it directly in the emulator:
| request | result |
|---|---|
Route C window 0x21500 |
0 — allowed |
Route C window 0x21B78 |
0 — allowed |
protected range 1, 0x3700000 |
0xa rejected |
protected range 1, 0x3780000 |
0xa rejected |
protected range 2, 0x3900000 |
0xa rejected |
just below range 1, 0x36FF000 |
0 allowed |
just above range 1, 0x3800000 |
0 allowed |
addr + size overflow |
0xa rejected |
The controls matter: the protection is demonstrably real and its boundaries are
exactly [0x3700000, 0x3800000) and [0x3900000, 0x3A00000), and the overflow
guard fires. It simply does not cover 0x21xxx.
What the window contains
At least the bootloader's own boot-stage registers: bl_set_boot_stage writes
0x215C0 and 0x2161C through the same mechanism (recorded in
VULN-LOADIPFW-UNSIGNED-REGWRITE.md). So the window is PSP-internal state that
the bootloader itself uses, sitting outside the VA-window protection.
Status
Upgraded from unidentified to PSP-internal physical space, unprotected by the VA-window access control, containing bootloader boot-stage state.
That is a better answer than the tree has had, and it removes the VCN
distraction for good. It does not by itself establish impact: what the other
registers in 0x21500..0x21B78 do is still unknown, and the "nothing reads
them" scan that originally deflated this finding covered only the images we
hold. The right next question is narrow and concrete — enumerate which addresses
in that 1656-byte window the firmware itself touches, now that we know the
window is PSP-internal rather than host-visible.
What the firmware actually touches in that window: 3 of 415 dwords
With the window identified as PSP-internal physical space, the follow-up is narrow: which of its 415 dwords does firmware we hold reference at all?
Scanned sOS and SYS_DRV for 32-bit literals and MOVW/MOVT immediates landing
in [0x21500, 0x21B78]:
| address | referenced from | what it is |
|---|---|---|
0x215C0 |
bl_main+0x12 and +0x1a (sOS bootloader) |
the boot-stage register |
0x21B40 |
literal at 0xE100AC in SYS_DRV |
Route C handler's own fixup constant |
0x21B50 |
literal at 0xE100A8 in SYS_DRV |
Route C handler's own fixup constant |
3 of 415. And two of the three are the handler's own special cases —
VULN-LOADIPFW-UNSIGNED-REGWRITE.md already records 0x21B40/44/50/54 as the
addresses that "get firmware-chosen values" rather than the host's. So the only
address in the window that firmware genuinely uses is 0x215C0, and its user
is the bootloader's bl_main, which runs before SYS_DRV exists.
A control correction
The first version of this scan used 0x2161C as a positive control, because
VULN-LOADIPFW-UNSIGNED-REGWRITE.md records bl_set_boot_stage writing it. The
scan did not find it — and the reason is not a broken scan: in our build
bl_set_boot_stage is 28 bytes at FA 0x159E0 and references neither address.
That description came from a different firmware version, the same
version-mismatch trap that made the v53 map unusable for the TSU subsystem.
The working cross-check is 0x21B40/0x21B50: independently documented as the
handler's special cases, and found by the scan.
Impact, now bounded
Route C writes up to 64 attacker-chosen 32-bit values into PSP-internal register space that the VA-window access control does not protect. But of the 1656 bytes reachable, firmware we possess references exactly one address meaningfully, and its consumer is the bootloader at boot time — before the code that would be attacked is running.
So the original "capability real, impact unestablished" becomes capability real, impact bounded-low on the evidence available — with the standing caveat that consumers in hardware, or in code we do not hold, cannot be excluded by a scan over the images we have. That caveat is why this is "bounded-low" and not "none".
CORRECTION: "3 of 415" is NOT established — the detector has a demonstrated blind spot
Prompted by the question "how did you establish there's only one consumer?". Re-examining the method rather than the result, the answer is: I did not establish it.
What the scan could see
Only addresses appearing whole: 32-bit literals in constant pools, and
MOVW/MOVT immediate pairs. Over two images (sOS, SYS_DRV).
What it structurally cannot see
Register blocks are normally accessed as base + immediate offset. A
sequence holding 0x21000 in a register and doing ldr r0, [r1, #0x500] never
materialises 0x21500 anywhere in the image, so the scan is blind to it — and
that is the expected access pattern for the very thing this window looks like.
Tested for it. Scanning the band [window_lo - 0x1000, window_hi] for values
usable as a base with a 12-bit offset finds six candidates:
sOS base 0x021000 (covers the ENTIRE window) at FA 0x168ca
SYS_DRV base 0x020d04 at 0xE0EC5C
SYS_DRV base 0x020d1c at 0xE104B4, 0xE1657C
SYS_DRV base 0x020d5c at 0xE180F8
SYS_DRV base 0x020e30 at 0xE18288
SYS_DRV base 0x020f49 at 0xE01DEC
An attempt to resolve the sOS one did not decode as code at the offset the scan assumed, which additionally means the scan's address model for sOS is itself unverified.
Further weaknesses, stated plainly
- The control set was effectively N=1. Of two positive controls,
0x2161Cfailed; the build-mismatch explanation for it is evidence-backed but post-hoc. A detector validated by one surviving control is thin — and this project's own standing lesson is that a scan is worthless until it can find a known positive. - Coverage was 2 images, not the boot ROM, earlier bootloader stages, or any TA.
Status of the impact rating
"Firmware references 3 of 415 dwords" should be read as "3 whole-address references in 2 images, with 6 unresolved indexable bases and an unverified address model" — not as a census of consumers.
The bounded-low rating rested on that count, so it is downgraded to provisional until either the six candidates are resolved or the scan is rebuilt to follow base+offset addressing. The capability itself is unaffected; it remains confirmed on hardware.
Dynamic attempt: the right instrument, and why its first answer is void
The static scan's blind spot (base+offset addressing) disappears if the question
is asked at run time, so psp_emu/harness/routec_window_watch.py traps every
read and write in [0x21500, 0x21B78] across a full boot, three TA loads and six
ring commands.
It reports zero reads and zero writes, and after fixing two broken controls that result now stands (for the paths exercised).
The harness carries two mandatory controls, and getting them right took two attempts — both failures worth recording, because they pointed in opposite directions.
- Liveness — the same hook observed 15,438,193 reads in a known-busy range (sOS globals) from 197 distinct PCs. The hook works.
- Spanning coverage — firmware traffic must be seen in the same mapped
region both below and above the window, which proves the watcher's
address-space assumption holds across it. Final result: 20 pages below,
1 above (highest
0x3d000).
Broken control #1: it retracted a correct result
The first version of control 2 injected an ldr r1,[r0] with r0 = 0x21500 and
checked the hook fired. Post-boot it did not, and I concluded the MMU had made
the watcher blind and declared the zero void.
That was wrong. Instrumenting the probe with an unfiltered hook showed no memory access at all post-boot, not a translated one — the injected instruction never executed, because once the MMU is configured there is no mapping for the scratch page. The control was measuring code injection, not observability.
A control that fails for a reason unrelated to the claim is worse than no control: it retracts a correct result and looks rigorous doing it.
Broken control #2: the hook range again
The replacement control looked for traffic above the window and found none —
because the hook was registered with end=0x22000, so addresses above the
window could never be delivered. The same hook-range truncation that had already
broken two traces earlier in the session.
What the corrected run shows
With the hook spanning the full region, firmware reads land on 21 pages from
0x0 to 0x3d000, including above the window — so a read at 0x21500 would
have been seen exactly as those are. It never happens.
This is strictly better evidence than the static scan, and it is immune to the base+offset blind spot, because at run time the address exists whether or not it ever appeared whole in the image. The six unresolved indexable bases are therefore answered on these paths: none of them is used to reach the window.
Residual limits, unchanged
- only exercised paths are covered (boot, 3 TA loads, 6 ring commands);
- the boot ROM is not emulated, and
0x215C0is a bootloader-era register; - this emulator backs the window with plain RAM, so a hardware consumer is invisible here by construction — which remains the main reason the rating is not "none".
