# DF-3368 — nm_bdg_preflush() unbounded NS_MOREFRAG chains: heap OOB write, +4 GiB wild write, uint8 livelock

**Target:** `sys/net/netmap/netmap_vale.c:984-1016` (`nm_bdg_preflush`, reached from
`NIOCTXSYNC` → `bdg_netmap_txsync` → `netmap_vp_txsync:1448` on every VALE port),
with sinks at `netmap_vale.c:1012-1013` (wild/underflow write) and
`netmap_vale.c:1161` (`nm_bdg_flush` pass-1 walk livelock).

**Not** a re-report of DF-0401 (that is the *destination copy* size check at
:1330-1345); this is the *forwarding-table index* overrun in the fill loop.

## The bug

The `bridge_batch` flush check is only evaluated when a packet **completes**
(a slot without `NS_MOREFRAG`); fragment slots `continue` past it
(netmap_vale.c:998-1001).  `slot->flags` lives in user-writable shared memory
and is never validated (`netmap_ring_reinit`, netmap.c:1106, checks only
`cur`/`buf_idx`/`len`).  Marking **all** slots `NS_MOREFRAG` lets `ft_i`
grow once per slot up to the full ring size — up to `NM_BDG_MAXSLOTS` = 4096
(bound set at port creation, netmap_vale.c:595-600) — while `nkr_ft` is sized
for `NM_BDG_BATCH_MAX` = 1088 entries (`nm_alloc_bdgfwd`, netmap_vale.c:401-403,
52104-byte `kmalloc`):

* `ft[ft_i]` stores at indices ≥ 1088 → offsets ≥ 17408 (into `dst_ents`/`dsts`),
  and beyond index 3256 → offsets ≥ 52104, i.e. **past the end of the heap
  object** (up to 13416 bytes past at 4096 slots).  Every 16-byte entry carries
  two attacker-chosen u16s (`ft_len`=`slot->len`, `ft_flags`=`slot->flags`) and,
  with `NS_INDIRECT`, an **arbitrary attacker-chosen 64-bit pointer**
  (`ft_buf`=`slot->ptr`, netmap_vale.c:994-995).
* post-loop `ft[ft_i - frags].ft_frags = frags - 1;` (netmap_vale.c:1013) with
  `frags == ft_i + 1` evaluates index −1 in 32-bit unsigned → **wild write at
  ft + 0xFFFFFFF8** (~4 GiB above the object), byte value `(ring_size-1) & 0xff`.
* a completed 256-fragment chain stores `frags` into the **uint8_t** `ft_frags`
  → 256 & 0xff == 0 → `nm_bdg_flush` pass-1 walk `for (i=0; i<n; i+=ft[i].ft_frags)`
  never advances → **non-terminating loop** in netisr context (fully in-bounds;
  needs only 257 slots).  (`NM_MULTISEG`=64, "max size of a chain of bufs",
  netmap_vale.c:124, is documented but enforced nowhere.)

## Platform note (established, netmap.c pass-2)

The netmap subsystem **does not compile** on this DragonFly tree
(`netmap_kern.h:747` references `ifnet.if_unused7`, removed from if_var.h; no
`sys/conf/files` entry; no netmap.ko in /boot).  The code ships in-tree and
builds on FreeBSD-lineage systems where netmap is enabled.  Runtime proof here
is therefore done the established way: `df3368_kmod.c` copies
`nm_bdg_preflush` **verbatim** (stubbed lock/prefetch/BDG_NMB plumbing that
cannot influence `ft_i`) into a KLD and drives it with exactly the ring state
an mmap()ed user thread produces.

## Build / run (guest, root)

```
cd /root/df3368 && make && make fixed    # or ./build.sh pattern
./run.sh vuln 0    # control: 8 packets, canary intact, walk terminates
./run.sh vuln 1    # ft[0].ft_frags == 0 -> pass-1 walk STUCK at i=0
./run.sh vuln 2    # 13418 canary bytes corrupted past ft[] contract @ft[1088]..
./run.sh vuln 3    # real kmalloc(52104): writes 680 B PAST the object (survives)
# all-MOREFRAG variant: Fatal trap 12, fault VA = ft+0xFFFFFFF8, at the
# netmap_vale.c:1013 store  (see run.crash.log)
./run.sh fixed 1|2|3   # max index 1023 <= 1088, canary intact, walk terminates
```

Console output goes to the serial console; capture with `dmesg | grep df3368`
or `dfbsd-qemu/vm.sh log`.

## Expected results

See `run.log` (verbatim captured lines) and `run.crash.log` (the wild-write
panic).  `fix.diff` caps `frags` at `NM_MULTISEG` — validated A/B with the
fixed variant in the same module.
