# Hardware probes — what each one tests and what it found All probes run on the Navi 31 test part: `AM_RESET=1 PYTHONPATH=/root/psp/tinygrad python3 hw/.py` Recovery after a wedge: `PYTHONPATH=/root/psp/tinygrad python3 work/atx_power.py cycle 25`. Most probes carry an in-run **control**; a run whose control fails proves nothing and several results in the audit log were discarded on exactly that basis. ## Infrastructure | probe | purpose | |---|---| | `ring_wrap.py` | wrapping PSP ring submit. tinygrad's `_ring_submit` never wraps the write pointer, so every session was capped at ~1008 invokes and then wedged. Import `install(psp, adev)`. **Required by the deep fuzzers.** | ## Denial-of-service triggers (all [HW] reproduced, all hangs) | probe | finding | |---|---| | `auc_cmd6_wedge.py` | AUC cmd 6 wedges the PSP; cmds 4/5 as in-run controls | | `ta_cmdfuzz.py` | the 12-TA command sweep that found AUC cmd 6 and FP cmd 7 | | `len0_test.py` | `LOAD_TA` with `cmd_buf_len = 0` is accepted, then the first INVOKE hangs | | `fp_lenthresh.py` | FP cmd 2 completes at `decl=0x4000`, hangs at `0x2000` — parameter-driven | | `aperture_test.py` | `cmd_buf` = MC 0 hangs, with a VMC fault flood: no aperture validation | | `auc_wedge_state.py` | register evidence that the class is a **hang, not a fault** (sOS alive, heartbeat frozen) | | `auc_sid_test.py`, `auc_len_test.py`, `fp_cross.py` | hypotheses tested and **rejected** (session index, underflow, cross-TA state) | ## Boundary checks found CORRECT | probe | result | |---|---| | `cmdbuf_tmr.py`, `tmr_edge.py` | TMR range-checked on both descriptor halves, end-exclusive, no off-by-one | | `tmr_resetup.py` | `SETUP_TMR` cannot be re-issued — the region is not relocatable | | `app_range.py` | the TA image source is range-checked too; `app_len = 0` rejected cleanly | | `align_test.py` | misaligned `cmd_buf`/image rejected; no rounding, no stray writes | | `oversized_decl.py` | over-declaring `cmd_buf_len` does **not** cause out-of-bounds writes | | `image_parser_fuzz.py`, `hdr_field_test.py` | pre-auth image parser robust; the apparent signature gap is optional-field semantics | | `session_uaf.py` | stale, double-freed and forged session handles all rejected | | `session_churn.py` | 500 load/unload cycles: no slot leak, ids recycled | | `session_interleave.py` | 8 concurrent sessions, 480 invokes: no cross-session leakage | ## Ring frame (the structure itself, not the command) | probe | result | |---|---| | `fence_write.py` | the PSP writes a host-chosen value to a host-chosen address | | `fence_tmr2.py ` | `fence_addr` in the TMR is **accepted** (`cmd_buf` is rejected); `fence_addr=0` **wedges** | | `fence_land.py` | host reads of the TMR return `0xffffffff`, so read-back cannot settle it | | `fence_spray.py N STRIDE` | dense functional test: 16384 dwords / 64 B over the first TMR megabyte, TA behaviour unchanged | | `frame_sweep.py [off]` | all 16 frame dwords, re-running the control after every mutation so a dead PSP cannot fake hits | | `vmid_test.py ` | any non-zero `vmid` wedges the PSP (7th trigger); `frame_type` ignored | | `vmid_proof.py [conf]` | configuring MM VM context 1 does not make `vmid=1` work — mechanism only partly characterised | ## Ring creation and control space | probe | result | |---|---| | `ring_create_fuzz.py ` | creation is well validated: bogus sizes `0x4`/`0x5`, base inside the trusted region `0xa` | | `ring_unalign.py` | but a **misaligned base is accepted** then wedges on first submit (8th trigger) | | `ring_wptr_oob.py` | the ring index is bounded, and out-of-range is ignored rather than fatal | | `gfx_ctrl_sweep.py ` | all 14 control ids inert, including five undocumented | | `gbr_ih_set.py ` | **VACUOUS** — every address including the valid one returns `0x124`, so the path is non-functional here and proves nothing about its validation | ## Bootloader interface and rollback | probe | result | |---|---| | `bl_runtime_probe.py ` | the bootloader mailbox is **live at runtime**: answers a bogus component id with status `0xa` while sOS runs | | `bl_runtime_load.py ` | but **refuses to act** post-boot — valid loads get the same `0xa` as a nonsense id (uniform refusal) | | `rollback_inspect.py` | extracts SYS_DRV from historical `psp_13_0_0_sos.bin` blobs and version-compares with the running build | | `rollback_boot.py ` | substitutes a SYS_DRV during init; `identity` is the positive control | | `rollback_verify.py ` | **no anti-rollback**: after a cold cycle, 49.0.37 boots on a 49.0.54 part and services TAs | ## Memory-safety sweeps (all negative) | probe | coverage | |---|---| | `ta_leak_probe.py` | disclosure: 10 TAs, PSP-internal-pointer shapes in returned data | | `ta_oob_write.py` | writes past the declared window, 9 TAs with valid controls | | `corpus_oob.py` | the same over **63 distinct corpus TA builds**, commands 0-11 | | `ta_deepfuzz.py` | randomized multi-field mutation; full window after pass 111 | | `deep_param_sweep.py` | **exhaustive** single-dword sweep of the entire 16 KB window | | `invoke_struct_fuzz.py`, `field24.py` | the 784-byte INVOKE ring structure; one validated field, characterised | | `loadta_sweep.py` | full-resolution sweep of the LOAD_TA structure | | `ta_seqfuzz.py` | stateful sequences: output carried into the next invoke | | `ta_multisess_seq.py` | multi-session stateful fuzz with slots recycling underneath | | `ta_handlefuzz.py` | handle replay at offset +4 only — **superseded** by `handle_offsweep.py` | | `handle_offsweep.py` | handle replay at **every** dword offset, vs a random-value control (closes pass 94's weak spot) | | `gfx_undoc.py`, `gfx13_probe.py` | the last two unread GFX commands | | `asd_load_sweep.py` | all 17 corpus ASD builds through `LOAD_ASD`, with a PSP-health control after each | ## Earlier rounds (GCP lifetime work) `gcp_fe0d_refcount_probe.py`, `gcp_fe0f_stale_slot_probe.py`, `gcp_acquire_gate_bypass_poc.py`, `gcp_asd_differential_sweep.py`, `gcp_asd202_leak_probe.py`, `invoke_newtas_probe.py`, `loadta_windows_sweep.py`.