# LAT crash probe Targets the VT1200's LAT receive path, specifically **Finding A** from `../notes/lat-receive-audit.md`: the demultiplexer at `0x16C44` walks `nbr_slots` slots with **no length bound**. ## Components | File | Purpose | |---|---| | `lat_probe.py` | crafts a malformed LAT RUN on the wire (standalone; `--dry-run`) | | `test_lat_probe.py` | unit tests; reconstructs a captured real frame byte-for-byte | | `run_latd_fuzz.sh` | runs the **patched latd** with the probe armed via `LATD_FUZZ` | | `extract_stream.py` | pulls the exact host→VT1200 byte stream out of a pcap (self-contained libpcap + LAT parser) | | `replay_stream.py` | replays that stream to stdout so it can be used as a latd service command; `--limit N` bisects | | `announce_replay.py` | re-sends the captured LAT **service announcements** (type `0xA`) verbatim, at a chosen interval | | `heartbeat.sh` | output-producing soak service — so "alive" is distinguishable from a blank idle window | ## New lead (2026-10-15): service announcements Replaying the data stream from the crash capture does **not** crash the terminal (see `../../NEXT_SESSION.md`). The one protocol-level difference is that the crash session also carried **91 LAT service announcements** (message type `0xA`, multicast to `09:00:2b:00:00:0f`), one every ~10 s — and the audit's **Finding 3** says that path (LAD update, `0x10660`/`0x103CA`) does `memcpy`s with announcement-derived lengths and no confirmed upper bound. `announce_replay.py` tests exactly that. ```sh sudo python3 announce_replay.py ../local/filter-test2.pcap --dry-run sudo python3 announce_replay.py ../local/filter-test2.pcap --interval 10 --forever ``` Note the 2-byte field at payload offsets 14–15 that changes on every announcement (`server.cc:221`: `announce->incarnation = --multicast_incarnation;`) — prime suspect. ## The patch to latd `code/latd/connection.cc` gains `LATConnection::send_fuzz_run()` and a one-shot hook in `circuit_timer()`: ```cpp // fires once, on the first circuit-timer tick of a live circuit if (fuzz_claimed > 0 && !fuzz_done) { fuzz_done = true; send_fuzz_run(n); } ``` `send_fuzz_run()` builds the LAT header by hand and sets `num_slots = n` while appending exactly one 4-byte Data_a slot — i.e. it lies about the slot count, which is precisely what the firmware fails to check. It uses the connection's own `local_connid`/`remote_connid`, sequence number and socket, so the frame is valid on the live circuit (no need to guess negotiated circuit IDs). ## Procedure 1. Power-cycle the VT1200 (a crash leaves it faulted). 2. Build: `make -C ../local/build -j8` 3. Arm and run (needs root for AF_PACKET): ```sh sudo LATD_FUZZ=255 ../local/build/latd -d -v -ienp197s0f3u1 -s -lestderr ``` or `./run_latd_fuzz.sh -d -v -ienp197s0f3u1 -s -lestderr` 4. Have the terminal connect (log in) — the probe fires on the first circuit timer after the circuit is up. 5. Watch the terminal for a Level-0 fault, and `tcpdump` for the frame. Standalone (no latd) alternative, once you know the live circuit IDs: ```sh python3 lat_probe.py --dst-cir 0x0001 --src-cir 0x5460 --nbr-slots 255 --dry-run sudo python3 lat_probe.py --nbr-slots 255 --count 3 ``` ## Status The Finding-A fuzz probe (`lat_probe.py` / `LATD_FUZZ`) is still **not fired at hardware** — deliberately, until the demux walk is understood, so a crash can be *interpreted* rather than merely observed. The **stream replay** route was fired, and is negative: the exact byte stream that accompanied the crash does not crash the terminal. The **announcement soak** (`announce_replay.py`, or latd's own announcements with `latcp -m 10`) is the current test — see `../../NEXT_SESSION.md` for the armed procedure and the bisect plan.