aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/test_tmr_range_guard.py
blob: de0e26f8fe2076ca9d163e8a43f93401516461dc (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
"""Differential test of the SYS_DRV shared-memory range guard.

sysdrv_tee_map_shared_mem (RPC 0x1006, reached by GFX ring cmd 5 = SETUP_TMR)
gained range_overlap_check in >= .53, which rejects requests overlapping
    [0x0000FFFD_FB000000, 0x0000FFFD_FC000000)   (two adjacent 8 MB regions)
That check is absent in 0.49.0.37 .. 0.49.0.51.

SETUP_TMR is state-gated: once the TMR is established, re-issuing returns
0xffff0007 regardless of parameters. So the probe must be issued INSTEAD of the
legitimate TMR setup -- we monkeypatch _tmr_load_cmd, fire the probes, then let
the real one run so the device still finishes booting.

Expectation:
  stock .54  -> probes inside the window REJECTED (guard present)
  rolled .51 -> probes inside the window ACCEPTED (guard absent) == vulnerable

usage: python3 psp_out/test_tmr_range_guard.py [--fw51]
Run psp_out/psp_recover.py first (PSP must be cold for the fw substitution).
"""
import ctypes, sys, hashlib
import tinygrad.runtime.support.am.amdev as amdev
from tinygrad.runtime.autogen.am import am
from tinygrad.helpers import data64

FW51 = '--fw51' in sys.argv
BLOB_51 = '/tmp/lfw/36934f24c008.bin'

if FW51:
    data51 = open(BLOB_51, 'rb').read()
    _orig_fetch = amdev.fetch_fw
    def patched(path, name, sha256):
        if name == 'psp_13_0_0_sos.bin': return data51
        return _orig_fetch(path, name, sha256)
    amdev.fetch_fw = patched
    print("FW: 0.49.0.51 (rolled back)")
else:
    print("FW: stock 0.49.0.54")

import tinygrad.runtime.support.am.ip as ip

RESULTS = []

def probe(psp, name, addr64, size, sysaddr=None):
    # guard reads *(u64*)v7 == buf_phy_addr only; keep system_phy_addr legitimate
    cmd = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_SETUP_TMR)
    hi, lo = data64(addr64)
    cmd.cmd.cmd_setup_tmr.buf_phy_addr_hi, cmd.cmd.cmd_setup_tmr.buf_phy_addr_lo = hi, lo
    shi, slo = data64(sysaddr if sysaddr is not None else addr64)
    cmd.cmd.cmd_setup_tmr.system_phy_addr_hi, cmd.cmd.cmd_setup_tmr.system_phy_addr_lo = shi, slo
    cmd.cmd.cmd_setup_tmr.bitfield.virt_phy_addr = 1
    cmd.cmd.cmd_setup_tmr.buf_size = size
    SZ = ctypes.sizeof(am.struct_psp_gfx_cmd_resp)
    try:
        psp._ring_submit(cmd); st = 0
    except RuntimeError:
        r = bytes(psp.adev.vram.view(psp.cmd_paddr, SZ)[:])
        st = int.from_bytes(r[864:868], "little")
    except TimeoutError:
        st = -1
    RESULTS.append((name, addr64, size, st))
    print("  %-34s addr=0x%016x size=0x%-8x -> %s"
          % (name, addr64, size, "TIMEOUT" if st < 0 else hex(st)))
    return st

ADDR = None
for a in sys.argv:
    if a.startswith('--addr='): ADDR = int(a.split('=',1)[1], 0)

_orig_tmr = ip.AM_PSP._tmr_load_cmd
def hooked(self):
    # SETUP_TMR is one-shot per boot: the first ACCEPTED call establishes the TMR
    # and every later one returns 0xffff0007. So issue exactly ONE probe.
    SZ = self.tmr_size
    tgt = ADDR if ADDR is not None else self.adev.paddr2mc(self.tmr_paddr)
    v = self.adev.reg(f"{self.reg_pref}_58").read()
    print("  [running FW at probe time] C2PMSG_58=0x%08x -> %d.%d.%d.%d"
          % (v, v>>24&0xff, v>>16&0xff, v>>8&0xff, v&0xff))
    print("--- single SETUP_TMR probe (size=0x%x, the legit TMR size) ---" % SZ)
    sysa = self.adev.paddr2xgmi(self.tmr_paddr) if self.tmr_paddr else 0
    probe(self, "probe", tgt, SZ, sysaddr=sysa)
    print("--- restoring legitimate TMR setup ---")
    return _orig_tmr(self)
ip.AM_PSP._tmr_load_cmd = hooked

try:
    from tinygrad import Device
    d = Device["AMD"]
    adev = d.iface.dev_impl
    v = adev.reg(f"{adev.psp.reg_pref}_58").read()
    print("running FW C2PMSG_58 = 0x%08x -> %d.%d.%d.%d"
          % (v, v >> 24 & 0xff, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff))
    print("device init: OK")
except Exception as e:
    print("device init raised: %r" % (e,))

print("\nSUMMARY")
for n, a, s, st in RESULTS:
    print("  %-34s -> %s" % (n, "TIMEOUT" if st < 0 else hex(st)))