# DF-0401 — VERDICT

**Status:** NOT REPRODUCED (latent bug — vulnerable code path is real in source but the containing netmap subsystem is dead/unloadable on DragonFlyBSD master DEV `6cc80ee9`).

**Impact (live):** none — the bug path cannot be entered on this kernel.
**Impact (source-level primitive, if netmap were loadable):** heap overflow of up to 63488 bytes (65536 − 2048) with 100% attacker-controlled content and length into a `NETMAP_BDG_BUF_SIZE` (default 2048) kernel buffer — a critical memory-corruption primitive.

**Confidence:** certain (the source-level proof is ironclad; the unreachability is also ironclad).

---

## 1. The bug is real in the source

The vulnerable code path and the missing check are confirmed by direct source inspection.

**Trigger — `sys/net/netmap/netmap_vale.c:988` (`nm_bdg_preflush`):**
```c
984: 	for (; likely(j != end); j = nm_next(j, lim)) {
985: 		struct netmap_slot *slot = &ring->slot[j];
986: 		char *buf;
987:
988: 		ft[ft_i].ft_len = slot->len;          /* <-- NO bounds check */
989: 		ft[ft_i].ft_flags = slot->flags;
```
`slot` is `&ring->slot[j]`, where `ring` is the user-mmap'd netmap TX ring
(`kring->ring`). `slot->len` is a `uint16_t` (confirmed in
`sys/net/netmap/netmap.h:131`: `uint16_t len; /* packet length */`), writable
directly by userspace via the shared ring memory. It can be any value 0–65535.
`nm_bdg_preflush` copies it verbatim into `ft[ft_i].ft_len` with **no**
validation against the bridge buffer size.

**Sink — `sys/net/netmap/netmap_vale.c:1330-1347` (`nm_bdg_flush`):**
```c
1329: 			    void *dst, *src = ft_p->ft_buf;
1330: 			    size_t len = (ft_p->ft_len + 63) & ~63;   /* round up; 65535 -> 65536 */
...
1333: 			    dst = BDG_NMB(&dst_na->up, slot);          /* 2048-byte netmap buf */
...
1338: 			    if (ft_p->ft_flags & NS_INDIRECT) {
1339: 				if (copyin(src, dst, len)) { ... }      /* writes `len` bytes */
1342: 			    } else {
1344: 				//memcpy(dst, src, len);
1345: 				pkt_copy(src, dst, (int)len);           /* writes `len` bytes */
1346: 			    }
```
- `len = (65535 + 63) & ~63 = 65536`.
- `dst = BDG_NMB(&dst_na->up, slot)` returns a pointer into the destination
  adapter's `NETMAP_BUF_POOL`. Per `sys/net/netmap/netmap_mem2.h:210`,
  `NETMAP_BDG_BUF_SIZE(n) = (n)->pools[NETMAP_BUF_POOL]._objsize`, which
  defaults to **2048** (confirmed by the comment in the finding and the
  `netmap_obj_malloc(... NETMAP_BDG_BUF_SIZE(n) ...)` site at
  `netmap_mem2.c:367`).
- `pkt_copy(src, dst, 65536)` writes 65536 bytes into the 2048-byte `dst` →
  **63488-byte heap overflow with attacker-controlled content** (the source
  buffer is the attacker's TX buffer) and attacker-controlled length (the
  `slot->len` value).

**The intended contract — proven by the sibling check.** The same
`slot->len` value IS validated in three other places in
`sys/net/netmap/netmap.c`:

- `netmap.c:748` — `if (slot->len < 14 || slot->len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) { D("bad pkt at %d len %d", n, slot->len); continue; }`
- `netmap.c:1124` — `} else if (len > NETMAP_BDG_BUF_SIZE(kring->na->nm_mem)) {`
- `netmap.c:2017` — `if (len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) { /* too long for us */`

The VALE forwarding fast path in `netmap_vale.c` omits this check entirely.
This is the canonical "check exists in three sibling code paths but was
forgotten in the fourth" pattern — a genuine defect, not a false positive.

**Primitive characterization (source-level, since the subsystem is dead on
this guest):**
- Write size: up to 65536 bytes (`(slot->len + 63) & ~63` with `slot->len` up to 65535).
- Overflow size: up to 63488 bytes past the 2048-byte buffer.
- Content control: 100% — the source buffer `src` is the attacker's TX buffer
  (`BDG_NMB(&na->up, slot)` from the attacker's own ring slot), or, with
  `NS_INDIRECT` set, an arbitrary user pointer (`copyin(src, dst, len)`).
- Length control: 100% — `slot->len` is the attacker's `uint16_t`.
- Allocation bucket: the netmap buffer pool (`NETMAP_BUF_POOL`), 2048-byte
  objects, slab-backed. On a kernel with INVARIANTS off this would be a
  high-confidence arbitrary-overwrite primitive into adjacent slab objects;
  on default GENERIC (INVARIANTS on) slab poisoning/magic checks would
  likely catch cross-type reuse and panic (DoS) before a clean uid=0.

---

## 2. Why it cannot be reproduced on this guest (Phase 4d: latent bug)

The entire netmap subsystem is **dead code on DragonFlyBSD master DEV
`6cc80ee9`**. It cannot be compiled, loaded, or instantiated. Evidence:

**(a) `struct ifnet` no longer has `if_unused7`.** Netmap attaches its
per-adapter state to an `ifnet` via a spare field, accessed through the
`WNA` macro:
```c
sys/net/netmap/netmap_kern.h:747:
#define	WNA(_ifp)	(_ifp)->if_unused7	/* XXX better name ;) */
```
But the current `struct ifnet` (`sys/net/if_var.h`) only has:
```
370:	int	if_unused2;
412:	int	if_unused4;
```
— `if_unused7` was removed in an `ifnet` refactor and netmap was never
updated. **Every netmap `.c` file includes `netmap_kern.h` and fails to
compile**, with 15 hard errors in `netmap.c` alone (30 `if_unused7`
expansions across the build). See `netmap_build_attempt.log`:
```
/usr/src/sys/net/netmap/netmap_kern.h:747:27: error: 'struct ifnet' has no member named 'if_unused7'; did you mean 'if_unused2'?
 #define WNA(_ifp) (_ifp)->if_unused7 /* XXX better name ;) */
```

**(b) Netmap is not in GENERIC.** `grep -ci netmap
/usr/src/sys/config/X86_64_GENERIC` → `0`. No `options NETMAP`.

**(c) No `netmap.ko` is shipped.** `ls /boot/kernel/netmap.ko` → no such
file. `kldload netmap` → "can't load netmap: No such file or directory".

**(d) No `/dev/netmap` device node.** Confirmed by the reachability probe
(`reachability_probe.c`):
```
[UNREACHABLE] /dev/netmap does not exist: No such file or directory
[UNREACHABLE] netmap subsystem is not loaded/available.
[UNREACHABLE] The VALE forwarding path (nm_bdg_preflush/nm_bdg_flush)
              cannot be entered live.
```

**(e) Netmap userland headers are not installed.** Neither
`/usr/include/net/netmap.h` nor `/usr/include/net/netmap/netmap.h` exist,
so even the userspace PoC cannot build against the standard install path.

**Conclusion:** the missing-check defect at `netmap_vale.c:988` is a
**genuine latent bug** in the source. It is not triggerable live on this
guest because the containing netmap subsystem does not build, load, or
expose any device node. This is Phase 4(d): "genuinely not reachable on
this kernel … the sink is dead code". The trigger conditions that would
make it live are: (1) a future commit restoring `if_unused7` (or migrating
netmap to a dedicated `ifnet` member / `NA(ifp)` softc) so the subsystem
compiles again, and (2) an admin loading `netmap.ko` and a local user
gaining access to `/dev/netmap`. At that point this defect becomes a
critical heap-overflow primitive.

Because the subsystem cannot be loaded, no heap-grooming / escalation
chain can be developed or tested on this guest. This is a valid hard
blocker (Phase 6: "dead/unreachable at runtime on this guest AND no
harness can exercise it" — the harness itself, the netmap module, cannot
be built).

---

## 3. The fix

`fix.diff` adds the missing bounds check at `netmap_vale.c:988`, mirroring
the existing check at `netmap.c:748`:

```diff
--- a/sys/net/netmap/netmap_vale.c
+++ b/sys/net/netmap/netmap_vale.c
@@ -985,6 +985,15 @@
 		struct netmap_slot *slot = &ring->slot[j];
 		char *buf;

+		/* Validate slot length against the bridge buffer size, mirroring
+		 * the check in netmap.c:netmap_bwrap_flush()/nm_bdg_flush_new().
+		 * Without this, a userspace-mapped VALE TX ring can set slot->len
+		 * up to 65535 and nm_bdg_flush() will pkt_copy/copyin that many
+		 * bytes into a NETMAP_BDG_BUF_SIZE (default 2048) buffer. */
+		if (slot->len > NETMAP_BDG_BUF_SIZE(na->up.nm_mem)) {
+			RD(5, "dropping oversize slot len %d", slot->len);
+			continue;
+		}
 		ft[ft_i].ft_len = slot->len;
 		ft[ft_i].ft_flags = slot->flags;
```

- `na` is `struct netmap_vp_adapter *` (function signature at line 962);
  `na->up` is the embedded `struct netmap_adapter` (netmap_kern.h:374);
  `na->up.nm_mem` is `struct netmap_mem_d *` (netmap_kern.h:349).
- `NETMAP_BDG_BUF_SIZE(n)` expects `struct netmap_mem_d *` and expands to
  `(n)->pools[NETMAP_BUF_POOL]._objsize` (netmap_mem2.h:210). The
  expression is type-correct by construction and identical in form to the
  sibling check at `netmap.c:748`.
- The `continue` drops the oversize slot (matching `netmap.c:748`'s
  `continue` semantics) without advancing `ft_i`, so it neither corrupts
  the `ft[]` work area nor enters `nm_bdg_flush` with the bad length.

This **matches the finding markdown's `## Recommended fix` proposal** in
substance (same check, same location) and improves it with a comment
explaining the defect and the use of `RD(5, ...)` (rate-limited debug,
matching netmap's logging conventions) instead of a louder `D(...)`.

**Fix validation:** the diff applies cleanly to both the host `sys/` tree
(`git apply --check` → OK) and the in-guest `/usr/src` tree
(`patch -p1 --dry-run` → "Hunk #1 succeeded at 985"). The fix inserts the
correct check at the correct location (verified by `sed` of the patched
region). However, a **full Phase 8 boot-test cannot be performed** because
the netmap subsystem itself does not compile on this master (the `if_unused7`
breakage upstream of our hunk), so no kernel/module can be built that
contains a loadable netmap, so the bug cannot be triggered before or after
the fix. `fix_status = "not_testable"` per the Phase 8f rubric: we
validated that the diff **applies** and traced that it **closes the code
path** (the check rejects any `slot->len > 2048` before it reaches
`ft[ft_i].ft_len` and thence `nm_bdg_flush`), but it could not be
exercised live.

---

## 4. PoC artifacts

| File | Purpose |
|------|---------|
| `poc.c` | The intended VALE-overflow PoC (as given by the finding). Needs live `/dev/netmap`; cannot run on this master. |
| `reachability_probe.c` | Self-contained probe that opens `/dev/netmap`. Proves the device node is absent. **Runs and returns `[UNREACHABLE]`.** |
| `net/netmap.h`, `net/netmap_user.h` | Vendored netmap UAPI headers (so `poc.c` could build if the subsystem existed). |
| `netmap_build_attempt.log` | Full `make` output of `cd /usr/src/sys/net/netmap && make` — 15 `if_unused7` errors, no `.ko` produced. Proves the subsystem is dead. |
| `build.log` | Output of `./build.sh` (probe builds; intended PoC fails on vendored headers). |
| `run.log` | Output of `./run.sh` — reachability probe shows `/dev/netmap` ENOENT. |
| `fix.diff` | The verified fix (applies cleanly; closes the path). |
| `env.txt` | Guest environment. |
