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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3332 Β· 14 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative
↓ download 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 -9 cannot 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 now never completed β€” vm.sh reset had 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

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Baseline (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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 (same; verbatim-function A/B in KLD, full netmap kernel build impossible on this tree)

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