β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0616

Heap buffer overflow in generic_netmap_rxsync: unbounded m_copydata into fixed-size netmap buffer

Field Value
ID DF-0616
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE CWE-787 Out-of-bounds Write
File sys/net/netmap/netmap_generic.c
Lines 669-674 (bug); 500 (TX path that correctly validates)
Area net/netmap (generic netmap RX ring sync)
Confidence certain
Discovered 2026-07-02
Reported pending

Summary

generic_netmap_rxsync() copies a dequeued RX mbuf into a netmap buffer with m_copydata(m, 0, len, addr) where len = m->m_pkthdr.len and addr is a fixed 2048-byte netmap buffer. len is never bounded against NETMAP_BUF_SIZE. The mbuf arrived from a real NIC via generic_rx_handler and may be a jumbo frame (up to 9 KB) or an LRO-aggregated chain (up to ~64 KB), so the copy writes thousands of bytes past the end of the netmap buffer, corrupting adjacent buffers in the shared netmap memory pool and potentially the kernel heap beyond the pool allocation. The TX path at netmap_generic.c:500 correctly validates len > NETMAP_BUF_SIZE, proving the RX path's omission is a defect, not intended behavior.

Root cause

At sys/net/netmap/netmap_generic.c:669-674 the RX loop does:

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 = NMB(&ring->slot[j]) -> 2048-byte buffer */
674:    ring->slot[j].len = len;

addr comes from NMB() (netmap_kern.h:838-843) and points into the netmap BUF_POOL whose objects are exactly netmap_buf_size = 2048 bytes (netmap_mem2.c:765,855). m_copydata (sys/kern/uipc_mbuf.c:1671-1696) is a straight bcopy over the mbuf chain with KASSERTs only on the source side β€” it does not know the destination size and will happily write len bytes regardless. m_pkthdr.len is whatever the NIC driver produced: drivers routinely deliver chained mbufs (jumbo frames via m_getjcl, MJUM9BYTES=9216) or LRO-coalesced frames up to ~64 KB.

There is no clamp and no NETMAP_BUF_SIZE check anywhere on this path. By contrast, the TX equivalent at netmap_generic.c:500 explicitly rejects len > NETMAP_BUF_SIZE:

500:    if (unlikely(addr == netmap_buffer_base || len > NETMAP_BUF_SIZE)) {
501:        return netmap_ring_reinit(kring);
502:    }

Threat model & preconditions

Two reachable attack profiles:

  1. Remote (worst case): any host that has a NIC in netmap mode (typical for netmap/VALE-based firewalls, IDS, traffic generators) will trigger the overflow whenever an unauthenticated remote peer sends an Ethernet frame larger than 2048 bytes β€” a single 9000-byte jumbo or an LRO-aggregated burst suffices. No authentication required for the frame.

  2. Local: any member of the wheel group (device is 0660 root:wheel per netmap.c:2237) can open /dev/netmap and register a NIC in generic netmap mode via NIOCREGIF (which performs no priv_check/suser β€” netmap.c:1350-1418), then cause the kernel to receive a frame larger than 2048 bytes (e.g. ping -s 8000 over a jumbo-capable loopback/vlan, or a local sender on an NIC with MTU raised).

Precondition for both: the target NIC must be in netmap mode. This is not the default β€” it requires explicit registration by a privileged/wheel user. Once active, the remote trigger is unauthenticated.

Impact

  • Memory corruption: the overflow writes past the end of the 2048-byte netmap buffer into adjacent netmap buffers in the shared pool, and when the affected buffer is the last in its cluster, past the contigfree() pool allocation into neighboring kernel heap β€” classically exploitable for local privilege escalation to uid 0 / ring 0 or for reliable remote kernel panic (A:H).
  • Info leak: the corrupted slot->len (set to the oversized len at line 674) and the overflow-written adjacent buffers expose kernel pool memory to the netmap userspace client via mmap of the shared region (C:H).
  • Integrity: the overflow corrupts adjacent netmap ring slots and kernel heap (I:H).

Severity rationale: High. Kernel memory corruption via remote unauthenticated frame on a non-default-but-common config (netmap-mode NIC). AC:L (just send a jumbo frame). The PR:L in the CVSS reflects the local wheel-membership vector; the remote vector is PR:N and arguably worse.

Proof of concept

PoC source: findings/poc/DF-0616/poc_rxsync_overflow.c.

Build & run

cc -O2 -o poc_rxsync_overflow poc_rxsync_overflow.c
# On a host where em0 (or any NIC) is in netmap mode:
./poc_rxsync_overflow em0
# In another shell or from a remote peer, send a jumbo frame:
#   ifconfig em0 mtu 9000; ping -s 8972 <host>
# OR rely on LRO aggregation from remote TCP traffic.

Expected output

With jumbo/LRO traffic flowing, the system either: - (a) panics with a page-fault or uma/zone corruption signature in the netmap_buf_pool region (see panic.txt), or - (b) adjacent-slot netmap buffers contain attacker-controlled bytes from the oversized frame (visible in the mmap'd shared region), confirming the overflow.

Bound the copy length to the destination buffer size, exactly as the TX path already does. Truncating (rather than dropping) preserves the packet's leading bytes and avoids leaving a stale slot.

--- a/sys/net/netmap/netmap_generic.c
+++ b/sys/net/netmap/netmap_generic.c
@@ -669,6 +669,12 @@ generic_netmap_rxsync(struct netmap_adapter *na, u_int ring_nr, int flags)
             m = mbq_safe_dequeue(&kring->rx_queue);
             if (!m)
                 break;
        len = MBUF_LEN(m);
+            if (unlikely(len > NETMAP_BUF_SIZE)) {
+                /* RX mbufs (jumbo frames, LRO aggregation) can exceed the
+                 * fixed-size netmap buffer; truncate the copy to avoid an
+                 * out-of-bounds write into the shared netmap buffer pool. */
+                RD(5, "rx packet too large (%d), truncating to %d",
+                    len, NETMAP_BUF_SIZE);
+                len = NETMAP_BUF_SIZE;
+            }
             m_copydata(m, 0, len, addr);
             ring->slot[j].len = len;

An alternative, stricter fix would be to drop the oversized mbuf entirely (m_freem(m); continue; without advancing j or incrementing n) plus increment a dropped-packet counter, mirroring how bridge code handles oversize frames. Either approach closes the overflow; truncation is the minimal-impact choice consistent with the TX-side NETMAP_BUF_SIZE validation already present at netmap_generic.c:500.

References

Timeline

  • 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
  • 2026-07-02 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0616 Β· 16 files
FileTypeDescriptionSize
df0616_harness.c trigger-source code-level harness replicating verbatim MBUF_LEN/m_copydata/RX-block; -DFIX applies the clamp 7.7 KB view raw
poc_rxsync_overflow.c trigger-source original runtime-trigger scaffold (needs live netmap NIC + remote jumbo sender) 2.3 KB view raw
fix.diff suggested-fix git-apply-able clamp mirroring netmap_generic.c:500 TX-side check 778 B view raw
build.sh build-script builds vulnerable + fixed harness binaries 565 B view raw
run.sh run-script runs vulnerable then fixed logic on a given frame size 467 B view raw
build.log build-log cc output for the vulnerable harness (clean, CC_RC=0) 8 B view raw
run.log run-log decisive vulnerable run (9000-byte frame): 6952 bytes OOB 916 B view raw
run.2.log run-log LRO-max run (65535-byte frame): 8192 canary bytes corrupted (extent 63487) 920 B view raw
run.3.log run-log control run (1500-byte frame): no overflow 476 B view raw
fix_run.log run-log patched-logic run (9000-byte frame): 0 bytes OOB, clamped 560 B view raw
fix_build.log build-log full nativekernel build with fix applied (rc=0, 35688 lines, zero errors) 5.6 MB ↓ download
env.txt environment uname, cc version, netmap-not-in-config, if_unused slots, vtnet0 530 B view raw
VERDICT.md verdict full narrative: mechanism, reproduction, reachability, exploit ceiling, fix before/after 11.2 KB ↓ raw
README.md readme build/run/expected + why code-level harness 2.2 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme build/run/expected + why code-level harness
↓ download raw

DF-0616 β€” PoC: netmap generic rxsync heap buffer overflow

Heap buffer overflow (CWE-787) in generic_netmap_rxsync(): m_copydata(m, 0, len, addr) copies an RX mbuf of attacker-controlled length len = m->m_pkthdr.len (jumbo/LRO frame, up to ~64 KB) into a fixed 2048-byte netmap buffer with no bounds check, writing thousands of attacker-controlled bytes into the adjacent netmap pool / kernel heap.

The TX path at netmap_generic.c:500 does validate len > NETMAP_BUF_SIZE; the RX path at :672-674 does not β€” the omission this finding reports.

Files

  • df0616_harness.c β€” code-level proof (the accepted form for this audit). Replicates verbatim MBUF_LEN (netmap_kern.h:52), m_copydata (uipc_mbuf.c:1671-1696), and the vulnerable RX block (netmap_generic.c:672-674). -DFIX inserts the proposed clamp.
  • poc_rxsync_overflow.c β€” original runtime-trigger scaffold (needs a live netmap-mode NIC + remote jumbo sender; infeasible on this guest β€” kept for reference).
  • fix.diff β€” git-apply-able clamp mirroring the TX-side check at :500.
  • build.sh / run.sh β€” exact repro.
  • VERDICT.md β€” full narrative incl. fix before/after.
  • logs: build.log, run.log, run.2.log, run.3.log, fix_run.log, fix_build.log, env.txt.

Build & run

./build.sh                       # builds df0616_harness + df0616_harness_fixed
./run.sh 9000                    # 9000-byte jumbo frame (default); try 65535 for LRO-max

Expected outcome

  • Vulnerable logic (df0616_harness 9000): OOB WRITE CONFIRMED: 6952 bytes corrupted past the 2048-byte buffer β€” the corrupted adjacent-pool bytes are attacker-controlled (0x41+ pattern).
  • Patched logic (df0616_harness_fixed 9000): No OOB write ... FIX HOLDS: overflow prevented (clamped).

Why a code-level harness (not runtime)

The runtime netmap path is unavailable on this master-DEV guest: netmap is not in X86_64_GENERIC, the KLD module no longer compiles (struct ifnet dropped if_unused7, which netmap_kern.h:747 WNA() needs), and QEMU SLIRP caps the path MTU at 1500. The harness reproduces the verbatim audited logic and is the accepted proof per the DF-0265/DF-0594 precedent. See VERDICT.md Β§3.

VERDICT.md verdict full narrative: mechanism, reproduction, reachability, exploit ceiling, fix before/after
↓ download raw

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:

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 KASSERTs 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.)

--- 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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

FIX VALIDATED at the logic level (clean deterministic before/after): the vulnerable logic writes 6952 attacker-controlled bytes past the 2048-byte buffer on a 9000-byte frame; the patched logic (-DFIX, identical clamp as fix.diff) clamps len to 2048 and writes 0 bytes OOB. fix.diff applies cleanly to /usr/src (patch hunk #1 succeeded at line 670; guard present at netmap_generic.c:678) and to the host sys/ tree (git apply --check passes); it is compile-safe β€” after applying, the netmap module emits ONLY the pre-existing if_unused7 drift errors (zero new errors from the clamp). A nativekernel build with the fix applied succeeded (rc=0, 35688-line fix_build.log, zero compile errors). A running-kernel before/after is structurally impossible for this finding: netmap is NOT in X86_64_GENERIC (grep count 0) so the rebuilt kernel is byte-identical to baseline for netmap (sha256 obj kernel.stripped == /boot/kernel/kernel) and contains no netmap RX path; additionally the guest's cp-kernel+reboot sequence leaves the boot block unreadable (loader error even for a byte-identical kernel) β€” a guest fs-flush/hard-kill artifact unrelated to the fix. The harness (verbatim audited logic) is the accepted proof for this infeasible-runtime finding per DF-0265/DF-0594, and it decisively confirms the fix closes the OOB write.

BASELINE (vulnerable logic, 9000-byte frame, run.log): [!] OOB WRITE CONFIRMED: 6952 bytes corrupted past the 2048-byte buffer | ring->slot[j].len = 9000. PATCHED logic (9000-byte frame, fix_run.log): [*] FIX: len 9000 > NETMAP_BUF_SIZE 2048 -> clamping to 2048 | [+] No OOB write: all 2048 bytes stayed within the 2048-byte buffer | [+] FIX HOLDS: overflow prevented (clamped). Identical clean before/after for the 65535-byte LRO-max frame (8192 canary bytes corrupted -> 0). Compile-safety: netmap module after fix emits only 'struct ifnet has no member if_unused7' (pre-existing drift), zero new errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (nativekernel rebuild with fix.diff applied produced a byte-identical kernel β€” netmap is not compiled into X86_64_GENERIC, so no bootable kernel contains the netmap RX path on this guest; see fix_verdict)

Confirmed kernel references

Detail

Exploit chain

Primitive characterised (not pushed to uid0 β€” netmap cannot run on this guest). The overflow yields a single contiguous, fully-attacker-controlled OOB write of up to ~63KB past a 2048-byte BUF_POOL object into adjacent mmap'd pool objects (info leak + slot-metadata corruption visible to the netmap client) and, when the buffer is last in its contigfree() cluster, into neighbouring kernel heap -> corrupt adjacent kmalloc objects (function-pointer vectors fo_/cdev_, ucred pointers, refcounts) -> classical LPE/panic path. Conversion to uid0 (slab grooming of BUF_POOL neighbour + function-pointer hijack) requires a running netmap instance and is the documented next step on a netmap-capable kernel; it could not be developed here because netmap is non-functional on master DEV. Harness confirms the write primitive itself (size, contiguity, full content control). Chain logic lives in findings/poc/DF-0616/df0616_harness.c.

Evidence (decisive lines)

VULNERABLE (9000-byte frame, run.log): m_copydata(m, 0, len=9000, addr) into 2048-byte buffer -> [!] OOB WRITE CONFIRMED: 6952 bytes corrupted past the 2048-byte buffer; first corrupted byte pool[2048]; adjacent-pool bytes ATTACKER-CONTROLLED: 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 ...; ring->slot[j].len = 9000. LRO-max (run.2.log, 65535): extent = 63487 bytes. Control (run.3.log, 1500): no overflow. PATCHED logic (fix_run.log): FIX: len 9000 > NETMAP_BUF_SIZE 2048 -> clamping to 2048; m_copydata(..., len=2048) -> [+] No OOB write: all 2048 bytes stayed within the 2048-byte buffer. [+] FIX HOLDS.

PoC changes

Added df0616_harness.c (code-level proof replicating verbatim MBUF_LEN/m_copydata/RX-block with -DFIX clamp variant); the seeded poc_rxsync_overflow.c required a live netmap NIC + remote jumbo sender which is infeasible here (retained as the runtime scaffold). Added fix.diff (clamp mirroring netmap_generic.c:500), build.sh/run.sh repro scripts, VERDICT.md, manifest.json, and full logs (build/run/run.2/run.3/fix_run/fix_build/env).

Verified recommended fix

In sys/net/netmap/netmap_generic.c generic_netmap_rxsync(), clamp len to NETMAP_BUF_SIZE before the m_copydata (mirroring the TX-side check at :500): 'if (unlikely(len > NETMAP_BUF_SIZE)) len = NETMAP_BUF_SIZE;'. Full git-apply-able diff in findings/poc/DF-0616/fix.diff. Matches the finding markdown's Recommended fix proposal (truncation variant).

Verdict

REPRODUCED (code-level proof). The bug is real: generic_netmap_rxsync() at sys/net/netmap/netmap_generic.c:672-673 does len = MBUF_LEN(m) (= m->m_pkthdr.len, network-controlled) then m_copydata(m, 0, len, addr) into a fixed 2048-byte netmap buffer (NETMAP_BUF_POOL.size, netmap_mem2.c:765) with NO bound on len. m_copydata (uipc_mbuf.c:1671-1696) is a straight bcopy with only source-side KASSERTs; it copies len bytes regardless of destination size. The TX path at netmap_generic.c:500 DOES validate len > NETMAP_BUF_SIZE, proving the RX omission is a defect (CWE-787). A faithful harness replicating verbatim MBUF_LEN, m_copydata, and the RX block shows a 9000-byte jumbo frame writes 6952 attacker-controlled bytes past the 2048-byte buffer into adjacent pool objects; a 65535-byte LRO-max frame has a 63487-byte overflow extent. Runtime netmap trigger is infeasible on this master-DEV guest (netmap is NOT in X86_64_GENERIC; the KLD module no longer compiles because struct ifnet dropped if_unused7 which netmap_kern.h:747 WNA() needs; QEMU SLIRP caps path MTU at 1500), so the code-level harness is the accepted proof per DF-0265/DF-0594 precedent. The bug is certain in the source and would fire on any kernel where netmap is functional.