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

generic_netmap_rxsync: 32-bit wrap in the ring->reserved sanitization drives the userspace-release loop index out of [0,lim] β€” unkillable kernel livelock holding the kring lock

Summary

ring->reserved and ring->avail are mmap'd userspace-writable u_int32. The guard sums them in 32 bits, so reserved=0xFFFFFFFF with avail=1 wraps to 0 and passes; k then underflows (k = cur+lim+2 mod 2^32, always > lim), and the release loop 'for (n = 0; j != k; n++)' never terminates because j only cycles [0..lim]. The loop runs under the kring lock taken by the NIOCRXSYNC/poll paths: unkillable thread, wedged ring, pinned CPU. Slot writes stay in-bounds (livelock, not corruption). Local DoS behind /dev/netmap. The sibling host-ring code (netmap.c rxsync_from_host) has the same wrap but no loop. Fix: reject reserved > lim outright.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3428 Β· 9 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
trigger_reserved_livelock.c β€” 2.0 KB view raw
build.sh β€” 114 B view raw
run.sh β€” 105 B view raw
netmap_build.log β€” 8.8 KB view raw
fix.diff β€” 1.0 KB view raw
verdict.json β€” 2.6 KB view raw
manifest.json β€” 836 B view raw

DF-3428 β€” generic_netmap_rxsync reserved wrap β†’ infinite kernel loop

Build / Run

  • ./build.sh β€” module build attempt (fails on this tree; shared netmap_build.log).
  • ./run.sh β€” trigger sketch only; module absent.

The math (certain from source)

ring->reserved and ring->avail are mmap'd userspace-writable u_int32 (sys/net/netmap/netmap.h:221-223). With lim = nkr_num_slots - 1 = 1023:

  • resvd = 0xFFFFFFFF, avail = 1 β†’ resvd + ring->avail == 0 (mod 2^32) β†’ validation at netmap_generic.c:694 (>= lim + 1) passes.
  • k = cur ≀ lim < resvd β†’ else-branch: k = cur + 1024 + 1 - 0xFFFFFFFF ≑ cur + 1026 (mod 2^32) β†’ k ∈ [1026, 2049], always > lim.
  • Release loop for (n = 0; j != k; n++) (:702) β€” j only ever takes values in [0, lim] β†’ never terminates.

Locking: generic_netmap_rxsync runs under the kring busy/lock protocol (netmap.c:1474 ioctl path / netmap.c:1768 poll path, nm_kr_get β†’ q_lock), so the looping thread is unkillable and every other user of the ring blocks: unkillable local DoS. Slot writes stay in-bounds (livelock, not corruption).

Precondition

/dev/netmap open (created 0660 root:wheel, netmap.c:2237) β€” same gate as DF-0616/DF-3426. Severity Medium: local DoS behind a privileged device.

Verdict

build_failed β€” module unbuildable on this tree (identical blocker as DF-3426/3427); arithmetic verified by hand and by contrast with the non-looping host-ring variant (netmap.c:886-930).

VERDICT.md
↓ download raw

DF-3428 VERDICT β€” rxsync reserved-overflow livelock

status: build_failed | reproduced: 0 | impact: none (not exercisable) | finding severity: Medium (local DoS, source-certain)

Reproduced?

No β€” netmap KLD unbuildable on this tree (see netmap_build.log). The arithmetic, however, is fully determined:

resvd = ring->reserved   // user-writable u_int32 (netmap.h:223, mmap'd)
resvd = 0xFFFFFFFF, ring->avail = 1
resvd + ring->avail      // netmap_generic.c:694, 32-bit: == 0
0 >= lim + 1 (1024)?     // NO -> check passes, resvd NOT reset
k = ring->cur = 0        // validated <= lim at :643
k < resvd  ->  k = k + lim + 1 - resvd   // :698, mod 2^32
          =  0 + 1024 + 1 - 4294967295
          =  1026  (> lim = 1023)
for (n = 0; j != k; n++) // :702; j ∈ [0..1023] forever, k = 1026

Every slot write in the loop is in-bounds (slot[j], j wraps at lim) β€” this is a pure livelock of the calling thread while it holds the kring (nm_kr_get/q_lock via netmap.c:1474 NIOCRXSYNC or :1768 poll), wedging the ring and pinning the CPU at 100% in-kernel; the thread cannot be killed (never sleeps).

Why the wrap check fails

:694 if (resvd + ring->avail >= lim + 1) performs the sum in 32-bit u_int; two attacker-chosen large values wrap to a small one and bypass the "invalid reserve" reset. The subtraction at :698 then underflows k out of the [0, lim] range the loop can never reach.

Sibling check: netmap_rxsync_from_host (netmap.c:886-930) has the same 32-bit wrap but computes only deltas (n = k >= j ? k-j : k+lim-j) β€” no loop, so only userspace-visible accounting is corrupted there. The infinite loop is unique to generic_netmap_rxsync (netmap_generic.c:700-711).

Fix

Validate resvd against lim before use (it can never legitimately exceed the slot count); see fix.diff. not_testable (module unbuildable).

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff adds a hard bound (resvd > lim -> reset); not validatable - module unbuildable.

fix.diff; netmap_build.log
↓ fix.diffper-fix-DF-3428

Confirmed kernel references

Detail

Exploit chain

would-be: open /dev/netmap (0660 root:wheel) -> NIOCREGIF generic adapter -> mmap ring -> set cur=0, reserved=0xFFFFFFFF, avail=1 -> NIOCRXSYNC -> ioctl never returns, thread unkillable, ring wedged. Repeat per ring/CPU.

Evidence (decisive lines)

VERDICT.md (arithmetic trace); trigger_reserved_livelock.c; netmap_build.log; fix.diff

PoC changes

No seed; authored. Build attempt only (module unbuildable).

Verified recommended fix

Reject ring->reserved > lim outright before the release loop (resvd can never legitimately exceed the slot count); see fix.diff.

Verdict

generic_netmap_rxsync trusts the mmap'd userspace-writable ring->reserved: the sanitization at netmap_generic.c:694 sums resvd + ring->avail in 32 bits, so resvd=0xFFFFFFFF with avail=1 wraps to 0 and passes; the subsequent k = (k >= resvd) ? ... : k + lim + 1 - resvd (:698) underflows k into [lim+2, 2*lim+2], and the userspace-release loop for (n = 0; j != k; n++) (:702) can never satisfy j == k because j only cycles [0..lim] -> unbounded kernel loop with the kring lock held (NIOCRXSYNC/poll path, netmap.c:1474/1768): unkillable local DoS. Slot writes stay in-bounds (livelock, not corruption). Runtime verification impossible: netmap KLD unbuildable on this tree (netmap_build.log); arithmetic verified manually and contrasted with the non-looping host-ring variant (netmap.c:886-930).