# DF-3381 VERDICT

## Reproduced: YES (first attempt)

Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 (X86_64_GENERIC, stock INVARIANTS
kernel), x86_64, QEMU/KVM.  `/dev/bpf*` are 0600 root:wheel; `bpfopen()`
also rejects jailed credentials (bpf.c:363).

Sequence actually executed (`run.log`):

1. root opens `/dev/bpf0`, `BIOCSBLEN(32)` — kernel clamps to/accepts 32
   (`BPF_MINBUFSIZE`, bpf.c:802-803).  Confirmed by the program's
   `BIOCSBLEN -> 32` printout.
2. fork; child `setgroups/setgid/setuid` to `nobody` (uid 65534), then
   `BIOCSETIF("lo0")` — `lo0` is DLT_NULL with `bif_hdrlen`
   = `roundup2(4 + 32, 8) - 4` = **36** (bpf.c:1543, bpf.h:61-62,147-149).
3. child sends one loopback UDP packet → `looutput` → `if_simloop`
   (if_loop.c:227) → `bpf_ptap` → `bpf_mtap` → `catchpacket` with
   `bd_bufsize=32 < hdrlen=36`.

Result (serial console, `panic.txt`):

```
panic: bpf_mcopy
cpuid = 0
bpf_mcopy() at bpf_mcopy+0x6c
catchpacket.isra.1() at catchpacket.isra.1+0xff
bpf_mtap() at bpf_mtap+0x71
bpf_ptap() at bpf_ptap+0x39
if_simloop() at if_simloop+0x8f
Debugger("panic")
```

This is the exact predicted path and proves the underflow primitive:
before the `m == NULL` KASSERT fires, `bpf_mcopy` has already written the
4-byte AF header at `bd_sbuf+36` and the full UDP packet (~44 bytes of
attacker-chosen content) after it — i.e. ~50 bytes of controlled data
written past the end of a `kmalloc(32)` allocation, with a nominal copy
length of `0xfffffffc` bytes.

## Primitive characterization (primary objective)

* **What:** linear heap overflow forward from `kmalloc(32)+hdrlen`.
* **Where:** the store buffer allocated in `bpf_allocbufs()` (bpf.c:1475).
* **Content control:** full — the copied bytes are the tapped packet's
  bytes (attacker generates them; here a UDP payload).
* **Length control:** none.  The length is forced to
  `bd_bufsize - hdrlen` interpreted as `u_int` (0xfffffffc for lo0, 0xffffffe
  variants per DLT).  The copy runs until the mbuf chain ends (then
  `panic("bpf_mcopy")`, bpf.c:1283-1284) or until an unmapped page faults,
  whichever comes first.  There is no way to stop the copy at a chosen
  offset, so the kernel *always* dies; this is a corrupt-then-panic
  primitive, not corrupt-and-continue.
* **uid0 route:** blocked by the privilege model, not by hardening:
  opening `/dev/bpf*` requires euid 0 (0600 cdev) and jails are refused
  (bpf.c:362-366), so an *unprivileged* local user cannot reach the bug on
  a default system.  What the PoC does demonstrate is the
  drop-privileges-after-open pattern (tcpdump/libpcap keep a bpf fd across
  a setuid drop): the process that issued the corrupting ioctls and packet
  had `uid=euid=65534`.  A sandboxed/capability-dropped capture daemon that
  ever calls `BIOCSBLEN(<= hdrlen)`/`BIOCSETIF` can smash kernel memory
  while holding no privileges at trigger time.  Reliable uid0 was not
  pursued further because the unbounded copy length makes
  survive-and-exploit implausible: any successful run also kills the
  kernel.

## Why not already fixed upstream-style
FreeBSD rewrote its BPF buffer handling (locked buffers, `bpf_buffer.c`)
and rejects buffer sizes that cannot hold `bif_hdrlen`; DragonFly retains
the 1990s two-buffer scheme where the only guard is the (too small)
`BPF_MINBUFSIZE = 32` constant, which lost meaning when `struct timeval`
grew to 16 bytes on LP64 (`SIZEOF_BPF_HDR` 18 -> 32, `bpf.h:147-149`).

## Fix validation (mandatory for memory corruption)
`fix.diff`: (1) `BIOCSETBLEN` also rejected once `bd_sbuf` is allocated,
(2) `bpf_setif` returns EINVAL when `bd_bufsize < bif_hdrlen`,
(3) `catchpacket` drops when `totlen < hdrlen` (and when `bd_sbuf == NULL`,
which also covers DF-3384's NULL write).

Baseline: panic above, 100% reproducible by construction (no race).
Patched: rebuilt `X86_64_GENERIC` in guest (`make nativekernel`), rebooted,
re-ran the identical PoC — see `fix_baseline_reproduced=1`,
`fix_patched_reproduced=0` in `verdict.json` and `run.patched.log`:
`BIOCSETIF` now fails with EINVAL ("BIOCSETIF(lo0): Invalid argument") and
the guest survives.  The defensive catchpacket guard was additionally
exercised by binding to Ethernet (`bif_hdrlen` 34) with a 40-byte buffer —
packets are dropped (`bd_dcount` grows), no panic, no corruption.

kernel refs: sys/net/bpf.c:794-806, 1128-1134, 1418-1421, 1462;
sys/net/bpf.h:61-62, 64-67, 134-149.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
