Files

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 memcpys with announcement-derived lengths and no confirmed upper bound. announce_replay.py tests exactly that.

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():

// 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):
    sudo LATD_FUZZ=255 ../local/build/latd -d -v -ienp197s0f3u1 -s<service> -lestderr
    
    or ./run_latd_fuzz.sh -d -v -ienp197s0f3u1 -s<service> -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:

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.