# DF-0616 — VERDICT

## Verdict: REPRODUCED (code-level proof); FIX VALIDATED (logic-level before/after)

The heap buffer overflow in `generic_netmap_rxsync()` is **real and confirmed**.
The proposed one-line clamp **closes it**. Both conclusions are demonstrated by a
faithful code-level harness (the accepted proof for this finding, since the
runtime netmap path is unavailable on this master-DEV guest — see
"Reachability / why not runtime" below).

---

## 1. The bug (root cause, confirmed line-by-line)

`generic_netmap_rxsync()` (`sys/net/netmap/netmap_generic.c:634-717`) drains the
RX mbuf queue filled by `generic_rx_handler()` and copies each mbuf into a fixed
netmap buffer. The vulnerable block at `netmap_generic.c:669-674`:

```c
669:  m = mbq_safe_dequeue(&kring->rx_queue);
670:  if (!m)
671:      break;
672:  len = MBUF_LEN(m);                 /* == m->m_pkthdr.len — NETWORK CONTROLLED */
673:  m_copydata(m, 0, len, addr);        /* addr -> 2048-byte netmap buffer, UNBOUNDED */
674:  ring->slot[j].len = len;
```

Confirmed facts in the audited tree:

- `MBUF_LEN(m)` is `((m)->m_pkthdr.len)` (`sys/net/netmap/netmap_kern.h:52`) —
  set by the NIC driver to the received frame length; for jumbo frames or
  LRO-aggregated chains this can be up to 9216 (`MJUM9BYTES`) or ~64 KB.
- `addr = NMB(&ring->slot[j])` points into the netmap `BUF_POOL`, whose objects
  are exactly **2048** bytes (`NETMAP_BUF_POOL.size = 2048`,
  `sys/net/netmap/netmap_mem2.c:765`; `NETMAP_BUF_SIZE = netmap_buf_size`,
  `netmap_kern.h:721`).
- `m_copydata()` (`sys/kern/uipc_mbuf.c:1671-1696`) is a straight `bcopy` over
  the mbuf chain. Its only `KASSERT`s are on the **source** side (off/len vs the
  mbuf chain); it has **no knowledge of the destination size** and copies exactly
  `len` bytes.
- **Asymmetry proof:** the TX equivalent, `generic_netmap_txsync()`, explicitly
  validates the length at `netmap_generic.c:500`:
  ```c
  500:  if (unlikely(addr == netmap_buffer_base || len > NETMAP_BUF_SIZE)) {
  501:      return netmap_ring_reinit(kring);
  502:  }
  ```
  The RX path has **no such check** — a clear omission, not intended behavior.

Net effect: a >2048-byte RX mbuf causes `m_copydata` to write
`len - 2048` attacker-controlled bytes past the netmap buffer into the adjacent
`BUF_POOL` objects (which are `mmap`'d to userspace) and, when the affected
buffer is the last in its cluster, into the kernel heap beyond the pool
allocation. The oversized `len` is also stored into `slot[j].len` (line 674),
leaking the overflow extent to the netmap client. **CWE-787 OOB write.**

---

## 2. Reproduction (code-level harness)

File: `df0616_harness.c`. It replicates **verbatim** the logic of the audited
path:

- `MBUF_LEN()` verbatim from `netmap_kern.h:52`;
- `m_copydata()` verbatim from `uipc_mbuf.c:1671-1696` (`bcopy`→`memcpy`,
  KASSERTs elided — identical byte semantics);
- the RX copy block verbatim from `netmap_generic.c:672-674`.

The destination `addr` models a `BUF_POOL` object (2048 bytes); the bytes after
it are a `0xAA` canary region modelling the neighbouring pool objects / heap.
A >2048-byte "mbuf" (jumbo/LRO frame) filled with a recognisable attacker
pattern is fed in. Compile-time `-DFIX` inserts the proposed clamp.

### Before (vulnerable logic), 9000-byte jumbo frame — `run.log`

```
[*] m_copydata(m, 0, len=9000, addr) into 2048-byte netmap buffer
[!] OOB WRITE CONFIRMED: 6952 bytes corrupted past the 2048-byte buffer
[!] First corrupted byte: pool[2048] (= +0 past buffer end)
[!] Overflow extent (frame_len - NETMAP_BUF_SIZE) = 6952 bytes
[!] Corrupted adjacent-pool bytes are ATTACKER-CONTROLLED (0x41+ pattern, canary was 0xAA):
    41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 41 42 43 44 45 46
[!] ring->slot[j].len = 9000 (oversized -> also leaks len to userspace)
```

LRO-max case (`run.2.log`, 65535-byte frame): 8192 canary bytes corrupted
(extent capped only by the harness's 8192-byte canary; the real overflow extent
is `65535 - 2048 = 63487` bytes). Control (`run.3.log`, 1500-byte frame): no
overflow (as expected — frame ≤ `NETMAP_BUF_SIZE`).

**Primitive characterised:** a single received jumbo/LRO frame yields a
contiguous, **fully attacker-controlled** OOB write of up to `~63 KB` past a
2048-byte buffer, into adjacent mmap'd pool objects and kernel heap — a strong
memory-corruption primitive (write size and content both attacker-controlled).

---

## 3. Reachability / why a runtime trigger is infeasible on this guest

A live netmap runtime trigger is **not possible** on this master-DEV guest, for
three independent reasons (all verified):

1. **netmap is not compiled into the kernel.** `grep -ci netmap
   /usr/src/sys/config/X86_64_GENERIC` → `0`. The generic-RX path exists in the
   source but is not present in the running kernel.
2. **The netmap KLD module no longer builds against master.**
   `make -C /usr/src/sys/net/netmap` fails with 15+ errors of the form
   `'struct ifnet' has no member named 'if_unused7'`. `sys/net/if_var.h` now
   defines only `if_unused2` (line 370) and `if_unused4` (line 412); the
   `WNA(_ifp) = (_ifp)->if_unused7` macro at `netmap_kern.h:747` is stale. So
   netmap cannot be `kldload`'d — no NIC can be placed in netmap mode on this
   guest. (This is a **separate, pre-existing build-break** in netmap on master
   DEV, independent of DF-0616, and worth its own note upstream.)
3. **QEMU user-mode (SLIRP) networking caps the path MTU at 1500**, so even with
   a functional netmap, jumbo/LRO frames >2048 bytes cannot be delivered to
   `vtnet0` on this guest.

Because of (1)–(3), the finding's threat model ("a NIC already in netmap mode
receives a >2048-byte frame") cannot be instantiated here. Per the DF-0265 /
DF-0594 precedent, a code-level harness that links the **verbatim** audited
logic is the accepted, honest proof for this audit, and is what `df0616_harness.c`
provides. The bug is real in the source and would fire on any kernel where
netmap is functional (e.g. an older DragonFly release, or once the `if_unused7`
drift is reconciled).

---

## 4. Exploit chain (ceiling)

The confirmed primitive is a **large, contiguous, fully-attacker-controlled
kernel heap OOB write** (up to ~63 KB) past a 2048-byte `BUF_POOL` object. In a
live netmap deployment the realistic exploitation ceiling is:

- **Info leak / integrity (trivial):** adjacent `BUF_POOL` objects are `mmap`'d
  to the netmap client, so the overflow-written attacker bytes are directly
  visible in userspace, and adjacent slots' metadata (`len`, `buf_idx`, flags)
  can be corrupted — corrupting other clients' packet streams.
- **Kernel heap corruption (high):** when the overflowing buffer is the last in
  its `contigfree()` cluster, the write continues into neighbouring kernel heap
  → corrupt adjacent `kmalloc` objects (function-pointer vectors like `fo_*` /
  `cdev_*`, `struct ucred *`, refcounts) → classical path to local privilege
  escalation or reliable kernel panic.

A live kernel exploit (slab grooming of the `BUF_POOL`/heap neighbour,
function-pointer hijack → pivot → uid0) could not be developed because netmap
cannot run on this guest (see §3). The harness confirms the **write primitive**
itself (size, contiguity, full attacker control of content); the conversion to
uid0 would require a running netmap instance and is left as the documented
next step on a netmap-capable kernel.

---

## 5. Fix — `fix.diff` (validated)

The fix mirrors the existing TX-side check at `netmap_generic.c:500`: bound `len`
to `NETMAP_BUF_SIZE` before the `m_copydata`, so an oversized RX mbuf is
truncated rather than overflowing. (Truncation is the minimal-impact choice
consistent with the TX path; an alternative drop-and-count would also close it.)

```diff
--- a/sys/net/netmap/netmap_generic.c
+++ b/sys/net/netmap/netmap_generic.c
@@ -670,6 +670,13 @@
             if (!m)
                 break;
 	    len = MBUF_LEN(m);
+            /* RX mbufs (jumbo frames, LRO-aggregated chains) can exceed
+             * the fixed-size netmap buffer; bound the copy to
+             * NETMAP_BUF_SIZE, exactly as the TX path does at line 500,
+             * to avoid an out-of-bounds write into the shared netmap
+             * buffer pool. */
+            if (unlikely(len > NETMAP_BUF_SIZE))
+                len = NETMAP_BUF_SIZE;
             m_copydata(m, 0, len, addr);
```

`git apply --check` passes on the read-only host `sys/` tree; `patch -p1`
applies cleanly to in-guest `/usr/src` (hunk #1 succeeded at line 670; patched
guard present at `/usr/src/sys/net/netmap/netmap_generic.c:678`). The fix is
**compile-safe**: the only errors the netmap module emits after the patch are
the pre-existing `if_unused7` drift errors — **zero new errors** are introduced
by the clamp.

### Before/after (harness, deterministic) — `fix_run.log`

| variant                | 9000-byte frame              | result                      |
|------------------------|------------------------------|-----------------------------|
| vulnerable logic       | `m_copydata(..., len=9000)`  | **6952 bytes OOB write**    |
| patched logic (`-DFIX`) | `m_copydata(..., len=2048)`  | **0 bytes OOB** (clamped)   |

Identical clean before/after for the 65535-byte LRO-max frame.

### Phase 8 — kernel build/boot (honest status)

A single-fix kernel was built (`make -j6 nativekernel KERNCONF=X86_64_GENERIC`,
rc=0, 35688-line `fix_build.log`, zero compile errors) with `fix.diff` applied
to `/usr/src`. **However, a running-kernel before/after for the netmap code is
structurally impossible on this guest**, because:

- netmap is **not** in `X86_64_GENERIC`, so the rebuilt kernel is byte-identical
  to the baseline for netmap purposes (`sha256` of the obj `kernel.stripped` ==
  the running `/boot/kernel/kernel`); the netmap RX path is simply not present
  in any bootable kernel here; and
- the guest's `cp /usr/obj/.../kernel.stripped /boot/kernel/kernel` + reboot
  sequence leaves the boot block unreadable (loader error
  *"Unable to load /kernel/kernel; don't know how to load module 'kernel'"*)
  even when the installed kernel is **byte-identical** to the known-good
  baseline — i.e. the boot failure is a guest fs-flush/hard-kill infrastructure
  artifact, **not** a property of the fix or the kernel build.

The fix is therefore validated at the **logic level** (the harness reproduces
the verbatim audited code path), which is the accepted proof for this finding
given the runtime netmap path is unavailable. The fix is a trivial, obviously-
correct mirror of the already-validated TX-side check at `:500`.

`fix_status: fixed` (logic-level before/after: 6952 OOB bytes → 0 OOB bytes;
fix.diff applies cleanly and is compile-safe; kernel-boot path structurally
inapplicable because netmap is not compiled into the kernel and the KLD module
is pre-existing-broken on master DEV).

---

## 6. PoC changes from the seeded scaffold

- Added `df0616_harness.c` — code-level harness (the seeded `poc_rxsync_overflow.c`
  required a live netmap-mode NIC + remote jumbo sender, which is infeasible
  here; it is retained as the original runtime-trigger scaffold).
- Added `fix.diff` — git-apply-able clamp mirroring `netmap_generic.c:500`.
- Added `build.sh` / `run.sh` — exact repro.
- Captured full logs: `build.log`, `run.log`, `run.2.log`, `run.3.log`,
  `fix_run.log`, `fix_build.log`, `env.txt`.
