diff options
| author | user <user@clank> | 2026-10-09 16:11:31 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-10-09 16:11:31 +0200 |
| commit | bc43149d914325e15dc0490dee9d982e21287623 (patch) | |
| tree | a632cf70de742d5a60d01c9c0d806bf5b9257538 /hw/gcp_fe0a_params_va_nonrop.py | |
| parent | GCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers) (diff) | |
| download | amd-psp-re-main.tar.gz amd-psp-re-main.tar.xz amd-psp-re-main.zip | |
Diffstat (limited to 'hw/gcp_fe0a_params_va_nonrop.py')
| -rw-r--r-- | hw/gcp_fe0a_params_va_nonrop.py | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/hw/gcp_fe0a_params_va_nonrop.py b/hw/gcp_fe0a_params_va_nonrop.py new file mode 100644 index 0000000..151857b --- /dev/null +++ b/hw/gcp_fe0a_params_va_nonrop.py @@ -0,0 +1,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) |
