Unbounded kernel loop in netmap_grab_packets via unvalidated ring->reserved: unkillable thread + console flood + mbuf-exhaustion DoS (DF-0358 sign error escalated)
Summary
netmap_txsync_to_host() validates the user-writable shared-memory field ring->cur but never ring->reserved; netmap_grab_packets() computes its loop terminator from both with the DF-0358 sign error (k += n instead of k -= n). With cur < reserved (e.g. cur=0, reserved=1) the terminator k = num_slots+1 lies outside nm_next()'s wrap range, so the loop NEVER exits: a non-preemptible kernel-mode spin with an unconditional kprintf per iteration (console/log flood), and with slot lens in [14..bufsize] an unbounded m_devget/mbq allocation loop until mbuf exhaustion. The thread never returns to user mode - unkillable; repeatable per CPU. VERIFIED in a real 6.5-DEVELOPMENT kernel via verbatim-function KLD (netmap itself cannot compile on this tree - if_unused7 bit-rot): diagnostic mode proves k=1025 unreachable vs one-line-fixed variant terminating in exactly 1023; pure mode hung the syscall forever, pegged a CPU, flooded 40,088 console lines, stalled ssh, prevented shutdown. Same root line as DF-0358 (which reported only wrong-slot misdelivery) - the non-termination/DoS consequence is pass 2's addition. Any principal that can open /dev/netmap (0660 root:wheel). Fix: one line (k -= n), also repairs DF-0358's wrong-slot delivery.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3332 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3332_kmod.c | trigger-source | KLD with VERBATIM netmap_grab_packets(); -DDD3332_FIXED applies the one-line fix | 8.7 KB | view raw |
| Makefile | build | KMOD build | 314 B | β download |
| build.sh | build-script | builds vulnerable + fixed variants | 128 B | view raw |
| build.log | build-log | full cc output, successful | 6.0 KB | view raw |
| run.sh | run-script | vulnerable|fixed|pure modes | 115 B | view raw |
| run.log | run-log | diagnostic vulnerable run: k=1025, bailed at 10241 | 656 B | view raw |
| run.fixed.log | run-log | diagnostic fixed run: k=1023, terminated at 1023 iters | 470 B | view raw |
| run.pure.log | run-log | pure mode: syscall never returned; load 0.00->0.63+; ssh stalls; shutdown never completed | 814 B | view raw |
| run.kernelflood.full.log | run-log | full serial console: 40088 'bad pkt' flood lines (2.1 MB) | 2.0 MB | β download |
| panic.txt | console-excerpt | 3000-line flood excerpt | 151.9 KB | view raw |
| dmesg.txt | dmesg | all df3332 dmesg lines incl. A/B | 853 B | view raw |
| env.txt | env | guest env + netmap subsystem unavailability proof | 1.6 KB | view raw |
| fix.diff | suggested-fix | one-line k += n -> k -= n (also fixes DF-0358) | 258 B | view raw |
| VERDICT.md | verdict | full narrative | 5.4 KB | β raw |
DF-3332 β VERDICT
Finding: netmap_grab_packets() (sys/net/netmap/netmap.c:723-760) computes
its loop terminator k from the user-writable shared-memory field
ring->reserved with a sign error (k += n at netmap.c:740 instead of
k -= n). netmap_txsync_to_host() validates ring->cur (netmap.c:851-857)
but nothing validates reserved; with cur < reserved the computed k
(= cur + num_slots + reserved β₯ num_slots) lies outside the range the loop
variable can ever reach (nm_next() wraps within [0, num_slots-1]), so
for (n = kring->nr_hwcur; n != k;) never terminates.
Was it reproduced? YES β in a real kernel, with the verbatim function.
Constraint: the netmap subsystem of this DragonFly tree does not compile
(netmap_kern.h:747 dereferences ifnet.if_unused7, which no longer exists in
sys/net/if_var.h; sys/conf/files contains no netmap entries; no kernel
config registers it; no netmap.ko exists in /boot/kernel β kldload netmap
fails and cd /usr/src/sys/net/netmap && make dies with 15 hard errors,
all if_unused7). This was already established by the earlier evidence
packs of DF-0401/DF-0546/DF-0547/DF-0616 and re-confirmed this run
(env.txt).
The vulnerable loop itself depends only on ring->cur, ring->reserved,
nkr_num_slots, nr_hwcur, and slot[].len/flags β none of the rotted
machinery. df3332_kmod.c therefore carries the function verbatim
(only additions: iteration counter + bail-out for diagnostic mode, both
marked INSTRUMENTATION; m_devget/BDG_NMB/NETMAP_BDG_BUF_SIZE/D
stubbed β values irrelevant to loop control, the real macros are
bounded/clamped per netmap_kern.h:838-857) and runs it inside a stock
DragonFly 6.5-DEVELOPMENT kernel as a KLD, driven by exactly the ring state
an mmap()ed unprivileged (wheel-group) thread can write:
Run 1 β diagnostic (mode 0), vulnerable variant (dmesg.txt, run.log)
df3332: k=1025 lim=1023 nr_hwcur=0 num_slots=1024
df3332: RESULT iterations=10241 bailed=1 mbufs=0
(loop WOULD NOT TERMINATE (bailed out))
k=1025 is unreachable by the loop variable (0..1023) β without the
instrumentation bail-out the loop provably never exits. Each iteration also
executes the unconditional D() kprintf of netmap.c:749.
Run 2 β diagnostic, FIXED variant (k -= n) (run.fixed.log)
df3332: k=1023 lim=1023 nr_hwcur=0 num_slots=1024 [FIXED] df3332: RESULT iterations=1023 bailed=0 mbufs=0 (loop terminated)
Exactly the expected 1023 iterations (hwcur 0 β k 1023). The one-line fix eliminates the non-termination.
Run 3 β pure mode (mode 1), vulnerable variant (run.kernelflood.full.log)
sysctl -n debug.df3332.trigger=1 executed from ssh never returned
("NEVER_PRINTS" marker absent after the call; ssh client killed at 25 s).
Observed afterwards:
- load average 0.00 β 0.63+ and climbing (1 of 6 CPUs pegged);
- serial console flooding at ~100 lines/s with
grab_packets [153] bad pkt at N len 0β 40,088 flood lines captured (run.kernelflood.full.log; excerpt in panic.txt); - new ssh logins intermittently stall during banner exchange (three sessions timed out, one succeeded) β system-wide degradation;
kill -9cannot be delivered: the thread never returns to user mode, so no signal can take effect (definitional for a kernel-mode non-yielding loop; the process is still present and spinning β ps attempts themselves stalled behind console contention);shutdown -p nownever completed βvm.sh resethad to force-kill QEMU (the spinning thread blocks system shutdown: further proof of the DoS severity).
Exploit chain / impact
Local denial of service by any principal that can open /dev/netmap
(mode 0660 root:wheel per netmap.c:2237 on this vintage; the code lineage is
FreeBSD's netmap.c r257176 where the same loop shipped): one NIOCREGIF
(NETMAP_SW_RING) + mmap + one NIOCTXSYNC β unkillable kernel thread, console
flood, unbounded mbuf allocation variant (lens β₯ 14) exhausts mbufs.
Not a memory-corruption primitive: all slot accesses stay in-bounds
(n cycles 0..lim; slot->len checked against 14..NETMAP_BDG_BUF_SIZE at
netmap.c:748; BDG_NMB clamps buf_idx) β impact is purely availability.
The write variant can also corrupt ring accounting (avail/hwcur reset at
netmap.c:865-866 after break), but no privilege boundary is crossed.
Fix validation
fix.diff (one line: k += n; β k -= n; at netmap.c:740, which also
fixes DF-0358's wrong-slot delivery). A full fixed netmap kernel build is
impossible on this tree (subsystem bit-rot), so validation was done with the
-DDD3332_FIXED variant of the same verbatim function inside the same real
kernel: non-termination eliminated (Run 2). fix_status="fixed" with that
caveat.
Kernel references
- sys/net/netmap/netmap.c:731-741 β the arithmetic (sign error)
- sys/net/netmap/netmap.c:742-759 β the non-terminating loop
- sys/net/netmap/netmap.c:851-857 β only
curis validated,reservednever - sys/net/netmap/netmap.c:1443-1445 β NIOCTXSYNC host-ring dispatch
- sys/net/netmap/netmap_kern.h:196-201 β nm_next() wrap range
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.
Fix verification
fixedBaseline (vulnerable verbatim function): non-terminating (bailed at 10241). Patched (-DDD3332_FIXED == fix.diff's one line): terminates in 1023 iterations. A/B ran in the same stock kernel as a KLD because a netmap-including kernel cannot be built on this bit-rotted tree.
run.fixed.log; dmesg.txt A/B lines
Confirmed kernel references
Detail
Exploit chain
open /dev/netmap -> NIOCREGIF(ifname, NETMAP_SW_RING) -> mmap shared region -> write ring->cur=0, ring->reserved=1, slot len<14 -> NIOCTXSYNC -> netmap_txsync_to_host -> netmap_grab_packets spins forever (kernel-mode, no signal delivery possible; console flood; repeatable per-CPU)
Evidence (decisive lines)
['run.log: k=1025 lim=1023, 10241 iterations, bailed (WOULD NOT TERMINATE)', 'run.fixed.log: k=1023 [FIXED], 1023 iterations, terminated', 'run.pure.log + run.kernelflood.full.log: syscall never returned; 40,088 flood lines; load 0.63+; ssh banner stalls; shutdown never completed', 'dmesg.txt: complete A/B dmesg', 'env.txt: netmap subsystem build failure (bit-rot) + kldload failure']
PoC changes
seed sketch did not exist; wrote a KLD carrying the VERBATIM vulnerable function (kernel stubs for m_devget/BDG_NMB/NETMAP_BDG_BUF_SIZE/D documented in-file; INSTRUMENTATION bail-out added for diagnostic mode) because the netmap subsystem itself cannot compile on this tree; -DDD3332_FIXED variant for the A/B
Verified recommended fix
netmap.c:740: change 'k += n' to 'k -= n' (also fixes DF-0358's wrong-slot delivery); defense-in-depth: validate ring->reserved < nkr_num_slots in netmap_txsync_to_host next to the existing cur check
Verdict
netmap_grab_packets() (netmap.c:723-760) never validates the user-writable ring->reserved and computes its loop terminator with a sign error (k += n at netmap.c:740), so with cur < reserved (e.g. cur=0, reserved=1) the terminator k=1025 lies outside nm_next()'s wrap range and the kernel loop never exits: unkillable non-preemptible thread + unconditional per-iteration D() kprintf console flood + (with valid lens) unbounded mbuf allocation. The netmap subsystem itself does not compile on current DragonFly (if_unused7 bit-rot, no sys/conf/files entry), so the verbatim function was run in a real DragonFly 6.5-DEVELOPMENT kernel as a KLD: diagnostic mode proves non-termination (k=1025 unreachable, instrumented bail-out at 10241 iterations) vs the -DDD3332_FIXED variant terminating in exactly 1023; pure mode (verbatim, no bail-out) hung the sysctl forever, pegged a CPU (load 0.00->0.63+), flooded 40,088 console lines, intermittently blocked ssh logins, and prevented shutdown. Same root line as DF-0358 (which reported only wrong-slot misdelivery); the livelock/DoS consequence is what DF-3332 adds. Reachable by any principal that can open /dev/netmap (0660 root:wheel; FreeBSD-lineage systems where netmap builds).
No comments yet.