# DF-0591 — PoC: legacy ng_bridge mbuf leak when numLinks == 1

Privileged-capability local memory-leak PoC. When the legacy `ng_bridge` node
has exactly one connected link, the fan-out loop at `ng_bridge.c:663` never
executes (`i < numLinks - 1 == i < 0` is false), so the original mbuf is
never consumed and never freed. Each broadcast / multicast / unknown-unicast
frame into the single link leaks one mbuf permanently.

## Files

- `leak.c` — reproducer that builds the topology entirely from userland
  via the `ng_socket` data API (no ng_eiface/ng_ether/BPF needed).
  - Opens an `AF_NETGRAPH` control socket, names the node `df591`, opens a
    data socket, `connect()`s it to `df591:`, then `NGM_MKPEER`s a `bridge`
    peer so `df591:out ↔ bridge:link0` (numLinks == 1).
  - `sendto()`s broadcast Ethernet frames addressed to local hook `out`;
    each one is delivered to `ng_bridge_rcvdata` and (because numLinks == 1)
    leaks.
- `build.sh`, `run.sh` — exact, runnable build/run wrappers.
- `build.log`, `run.log`, `fix_build.log`, `fix_run.log`, `env.txt`,
  `fix.diff`, `VERDICT.md`, `manifest.json` — full evidence pack.

## Build & run

```
./build.sh                       # cc -O2 -o leak leak.c
sudo kldload ng_socket
sudo kldload ng_bridge
sudo ./run.sh                    # runs ./leak 500 by default
```

(Requires root: opening an `AF_NETGRAPH` socket and `kldload` are both
root-restricted on this guest. Consistent with the finding's Low severity.)

## Expected output (unpatched `#0` kernel)

```
mbufs in use BEFORE: 7
mbufs in use AFTER:  507
DELTA: 500 mbufs leaked
DF-0591 REPRODUCED: mbuf pool grew by 500
```

After the fix, the same invocation prints `DELTA: 0 mbufs leaked`.

## Notes for the per-PoC verifier

- The leak is straight-line, no race; reproduces 100% of the time on a
  single-link bridge. Three independent runs at 500/1000/250 frames
  produced exactly that many leaks (deterministic, no variance).
- The fix (`fix.diff`) adds `if (m != NULL) NG_FREE_DATA(m, meta);` after
  the fan-out loop and clears `m`/`meta` in the "last link" branch so the
  multi-link case doesn't double-free. Validated on a built-and-booted
  kernel: 0 leaks over 6000 frames.
- Coordinate with DF-0590's fix: the legacy code lacks a per-node lock
  around the fan-out, so `numLinks` can change concurrently in theory; in
  practice the leak is straight-line and doesn't need a race to trigger.
