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:
-
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.
-
Local: any member of the
wheelgroup (device is0660 root:wheelpernetmap.c:2237) can open/dev/netmapand register a NIC in generic netmap mode viaNIOCREGIF(which performs nopriv_check/suserβnetmap.c:1350-1418), then cause the kernel to receive a frame larger than 2048 bytes (e.g.ping -s 8000over 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 touid 0/ ring 0 or for reliable remote kernel panic (A:H). - Info leak: the corrupted
slot->len(set to the oversizedlenat line 674) and the overflow-written adjacent buffers expose kernel pool memory to the netmap userspace client viammapof 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.
Recommended fix
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
sys/net/netmap/netmap_generic.c:669-674β the vulnerable RX copy with no bounds check.sys/net/netmap/netmap_generic.c:500β the TX path that correctly validateslen > NETMAP_BUF_SIZE.sys/net/netmap/netmap_mem2.c:765βNETMAP_BUF_POOL.size = 2048.sys/net/netmap/netmap_kern.h:52β#define MBUF_LEN(m) ((m)->m_pkthdr.len).sys/net/netmap/netmap_kern.h:721β#define NETMAP_BUF_SIZE netmap_buf_size.sys/kern/uipc_mbuf.c:1671-1696βm_copydata, no destination bounds check.sys/net/netmap/netmap.c:2237βmake_dev(..., UID_ROOT, GID_WHEEL, 0660, ...).sys/net/netmap/netmap.c:1350-1418βNIOCREGIFwith nopriv_check.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0616 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 verbatimMBUF_LEN(netmap_kern.h:52),m_copydata(uipc_mbuf.c:1671-1696), and the vulnerable RX block (netmap_generic.c:672-674).-DFIXinserts 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.
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 netmapBUF_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 straightbcopyover the mbuf chain. Its onlyKASSERTs are on the source side (off/len vs the mbuf chain); it has no knowledge of the destination size and copies exactlylenbytes.- Asymmetry proof: the TX equivalent,
generic_netmap_txsync(), explicitly validates the length atnetmap_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 fromnetmap_kern.h:52;m_copydata()verbatim fromuipc_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):
- 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. - The netmap KLD module no longer builds against master.
make -C /usr/src/sys/net/netmapfails with 15+ errors of the form'struct ifnet' has no member named 'if_unused7'.sys/net/if_var.hnow defines onlyif_unused2(line 370) andif_unused4(line 412); theWNA(_ifp) = (_ifp)->if_unused7macro atnetmap_kern.h:747is stale. So netmap cannot bekldload'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.) - 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
vtnet0on 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_POOLobjects aremmap'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 adjacentkmallocobjects (function-pointer vectors likefo_*/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 (sha256of the objkernel.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 seededpoc_rxsync_overflow.crequired 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 mirroringnetmap_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
fixedFIX 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.
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.
No comments yet.