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)))
|