aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/probe_routec_regwrite.py
blob: bb768f74b51b08021a8e0b2791990b2c895e5905 (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
"""Route C decisive probe: does LOAD_IP_FW type 0x55 actually perform the register write?

Prior hardware data (ATTACK-SURFACE.md addendum) recorded type 85 -> 0xffff0006 at
sizes 0x10/0x80/0xFF and was interpreted as "signature verification rejected it".
That reading was WRONG: 0xFFFF0006 is buffer_copy_bounded's own reject, and the old
probe's fill pattern put 0x6f68615a in the address slot -- outside the allow-list
window [0x21500,0x21B78]. So the path had already reached the register-write loop.

Model under test (buffer_copy_bounded @ 0xE0FFD0):
    a1 == 0                    -> 0xFFFF0006
    size > 0x200               -> 0xFFFF300F
    size % 8 != 0              -> 0xFFFF0006
    element addr out of window -> 0xFFFF0006   (loop aborts, no write)
    element addr in window     -> SVC 0x7C write, loop continues, returns 0x0

Stage 1 (ZERO WRITES) validates that model using only rejecting inputs.
Stage 2 performs exactly ONE write, and deliberately uses address 0x21B40, which
buffer_copy_bounded special-cases: on that path the FIRMWARE supplies the value
(qword_E264A8.lo + 0x700000), not us. So the bytes written are exactly what the
firmware writes during a legitimate type-0x55 load -- we control only whether it
happens, not what is written.

Stage 2 is skipped unless --write is passed.
"""
import ctypes, sys, time
from tinygrad import Device
from tinygrad.runtime.autogen.am import am
from tinygrad.helpers import lo32, hi32

DO_WRITE = "--write" in sys.argv

d = Device["AMD"]; adev = d.iface.dev_impl; psp = adev.psp
SZ = ctypes.sizeof(am.struct_psp_gfx_cmd_resp)
pref = psp.reg_pref

def alive(): return adev.reg(f"{pref}_81").read() != 0
def stat(): return int.from_bytes(bytes(adev.vram.view(psp.cmd_paddr, SZ)[:])[864:868], "little")

pa = adev.mm.palloc(0x10000, align=0x1000, zero=True)
mc = adev.paddr2mc(pa)

def set_elements(elems):
    """elems = list of (addr, value) 8-byte pairs."""
    buf = bytearray(0x1000)
    for i, (a, v) in enumerate(elems):
        buf[8*i:8*i+4] = (a & 0xFFFFFFFF).to_bytes(4, "little")
        buf[8*i+4:8*i+8] = (v & 0xFFFFFFFF).to_bytes(4, "little")
    adev.vram.view(pa, 0x1000)[:] = bytes(buf)
    adev.gmc.flush_hdp()

def load(fw_type, size):
    adev.vram.view(psp.cmd_paddr, SZ)[:] = b"\x00" * SZ
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_IP_FW)
    t = c.cmd.cmd_load_ip_fw
    t.fw_phy_addr_hi, t.fw_phy_addr_lo = hi32(mc), lo32(mc)
    t.fw_size = size; t.fw_type = fw_type
    t0 = time.time()
    try:
        psp._ring_submit(c); s = 0
    except RuntimeError:
        s = stat()
    except TimeoutError:
        return "TIMEOUT", time.time() - t0
    return s, time.time() - t0

def run(label, elems, size, expect):
    if not alive():
        print("  !! sOS DEAD -- aborting"); return None
    set_elements(elems)
    s, dt = load(0x55, size)
    ss = s if isinstance(s, str) else "0x%08x" % s
    ok = "MATCH" if (isinstance(s, int) and s == expect) else ("?" if expect is None else "DIFFERS")
    print("  %-46s -> %-12s %6.3fs alive=%-5s %s" % (label, ss, dt, alive(), ok))
    return s

OUT = 0x6F68615A          # the old probe's pattern: outside the window
IN_SPECIAL = 0x21B40      # in-window AND special-cased -> firmware supplies the value

print("sOS alive at start:", alive())
print("\n=== STAGE 1 - model validation, ZERO writes (every input is rejected) ===")
run("size=0 (zero-length map)",                 [],                 0x000, 0x5)
run("size=0x208 > 0x200",                       [(OUT, 0)]*2,       0x208, 0xFFFF300F)
run("size=0x0C misaligned",                     [(OUT, 0)],         0x00C, 0xFFFF0006)
run("size=8, addr OUT of window (old result)",  [(OUT, 0)],         0x008, 0xFFFF0006)
run("size=8, addr just BELOW window (0x214FC)", [(0x214FC, 0)],     0x008, 0xFFFF0006)
run("size=8, addr just ABOVE window (0x21B7C)", [(0x21B7C, 0)],     0x008, 0xFFFF0006)

print("\n  If all six MATCH, the error model is confirmed and 0x0 can only mean")
print("  the element was accepted and SVC 0x7C executed.")

if not DO_WRITE:
    print("\n=== STAGE 2 skipped (pass --write to run the single write) ===")
else:
    print("\n=== STAGE 2 - ONE write, firmware-chosen value, addr 0x21B40 ===")
    run("size=8, addr 0x21B40 (special-cased)",  [(IN_SPECIAL, 0)], 0x008, None)
    print("  0x0 => the PSP performed the register write, unsigned, host-commanded.")

print("\nsOS alive at end:", alive())