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

Negative qsize survives set_fs_parms clamping: u_int-vs-int queue-limit compare in dummynet_io never fires β†’ unbounded queue growth / kernel memory exhaustion (DF-0376 family confirmed in v3, missed by pass 1)

Summary

set_fs_parms clamps qsize only against the upper bound and zero; qsize=-1 passes and is stored. The limit checks 'q->len_bytes > fs->qsize' / 'q->len >= fs->qsize' compare u_int against a negative int promoted to ~4e9, so packets are never dropped and the flow queue (each entry pinning an mbuf + dn_pkt) grows until kernel memory is exhausted. DEMONSTRATED live without crashing: 400-packet hammer with qsize=-1 holds len=399 drops=0 vs control qsize=50 len=50 drops=349. Fixed on rebuilt kernel (qsize clamped; identical to control). Root-configured memory-exhaustion DoS; once such a flowset exists, any traffic-matching user (or remote flow) can drive unbounded kernel memory growth.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3356 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.0 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
dfv3.c β€” 10.6 KB view raw
setup.sh β€” 896 B view raw
build.sh β€” 130 B view raw
run.sh β€” 152 B view raw
run.log β€” 981 B view raw
fix.diff β€” 1.8 KB view raw
fix.diff β€” 1.8 KB view raw
env.txt β€” 447 B view raw
verdict.json β€” 3.7 KB view raw
manifest.json β€” 1.1 KB view raw

DF-3356 β€” negative qsize survives set_fs_parms clamping: signed/unsigned compare in dummynet_io never fires -> unbounded queue growth / memory-exhaustion DoS (DF-0376 family in v3)

File: sys/net/dummynet3/ip_dummynet3.c (pass 2) Severity: Low (CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L) β€” CWE-195 Signed to Unsigned Conversion Error Verdict: reproduced (dos) on stock INVARIANTS guest, see verdict.json.

Build

copy dfv3.c + setup.sh to the guest, then (root): cc -O -o /tmp/dfv3 /tmp/dfv3.c

Run

sh /tmp/setup.sh      # kldloads ipfw3 (default-accept) + dummynet3, adds queue rules
/tmp/dfv3 qsize -1 400 (then control: qsize 50 400)

Expected

GET dump shows len=399 drops=0 with qsize=-1 vs len=50 drops=349 control

setup.sh sets net.filters_default_to_accept=1 BEFORE kldload ipfw3 β€” ipfw3 otherwise boots default-deny and locks out ssh.

Fix

fix.diff (union patch for DF-3352..DF-3356; the hunk for THIS finding is: clamp negative qsize to the defaults in set_fs_parms (fix.diff hunk 4)).

VERDICT.md
↓ download raw

DF-3356 VERDICT β€” reproduced

Root cause

set_fs_parms() clamps qsize only against the upper bound and 0 (ip_dummynet3.c:1442-1448: qsize > 1024*1024 / qsize == 0 || qsize > 100); a negative qsize passes both checks (slot mode) and is stored. dummynet_io() then enforces the limit with if (q->len >= fs->qsize) (:1104) where q->len is u_int and the negative int is converted to ~4e9, so the drop check never fires and the flow queue grows without bound, pinning kernel memory (mbuf clusters) until the system exhausts memory. Demonstrated live on the stock guest with no panic: hammering 400 packets into a qsize=-1 flowset yields len=399, drops=0 in the IP_DUMMYNET_GET dump, while the byte-for-byte identical control run with qsize=50 yields len=50, drops=349. Growth is bounded only by available kernel memory. Identical code to v1 DF-0376; missed by pass-1 of this file.

Exploit chain

root: setsockopt pipe{bw 8k} + fs{qsize=-1}; unprivileged UDP flood to a port diverted by an ipfw3 queue rule -> queue accumulates without limit -> kmalloc'd dn_pkt/mbuf exhaustion -> OOM DoS. No uid=0.

Evidence

  • run.log: GET dump qsize=-1 (len=399 drops=0) vs control qsize=50 (len=50 drops=349)
  • dfv3.c: qsize subcommand + GET walker

Kernel refs

Fix validation

fix.diff hunk: clamp negative qsize to the defaults in set_fs_parms (fix.diff hunk 4). See verdict.json fix_* fields β€” a single guest kernel rebuilt with the union fix.diff (all five findings) was re-tested with the exact same PoCs: baseline panic/growth reproduced on the stock kernel, patched kernel rejects or sanitizes the bad configs and shows none of the five defects (details in fix section of verdict.json).

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

Union fix.diff (all five DF-3352..DF-3356 hunks; this finding's hunk is the one noted in recommended_fix) applied to the guest /usr/src, kernel rebuilt with make -j6 nativekernel KERNCONF=X86_64_GENERIC and installed (>>> Kernel build completed 07:41:12, install completed 07:42:30 UTC). set_fs_parms clamps qsize<=0 to 50 (slots): the qsize=-1 run now behaves byte-for-byte like the healthy control (len=50 drops=349); baseline held 399 packets with 0 drops. All other four PoCs re-run on the same patched kernel: all clean.

['run.2.log (patched-kernel validation transcript)', 'fix.diff (git-apply-able unified diff, 6 hunks vs sys/net/dummynet3/ip_dummynet3.c)']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 07:37:10 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (dummynet3 fix.diff applied, rebuilt via make nativekernel)

Confirmed kernel references

Detail

Exploit chain

root: setsockopt pipe{bw 8k} + fs{qsize=-1}; unprivileged UDP flood to a port diverted by an ipfw3 queue rule -> queue accumulates without limit -> kmalloc'd dn_pkt/mbuf exhaustion -> OOM DoS. No uid=0.

Evidence (decisive lines)

['run.log: GET dump qsize=-1 (len=399 drops=0) vs control qsize=50 (len=50 drops=349)', 'dfv3.c: qsize subcommand + GET walker']

PoC changes

PoC authored fresh (multi-trigger dfv3.c): raw setsockopt with ABI structs verbatim from ip_dummynet3.h; ipfw3 (not ipfw) queue <fs> udp ... via lo0 rules divert lo0 UDP into the WF2Q path; setup.sh sets net.filters_default_to_accept=1 before kldload ipfw3 because ipfw3 defaults to deny-all and otherwise locks out ssh.

Verified recommended fix

clamp negative qsize to the defaults in set_fs_parms (fix.diff hunk 4)

Verdict

set_fs_parms() clamps qsize only against the upper bound and 0 (ip_dummynet3.c:1442-1448: qsize > 1024*1024 / qsize == 0 || qsize > 100); a negative qsize passes both checks (slot mode) and is stored. dummynet_io() then enforces the limit with if (q->len >= fs->qsize) (:1104) where q->len is u_int and the negative int is converted to ~4e9, so the drop check never fires and the flow queue grows without bound, pinning kernel memory (mbuf clusters) until the system exhausts memory. Demonstrated live on the stock guest with no panic: hammering 400 packets into a qsize=-1 flowset yields len=399, drops=0 in the IP_DUMMYNET_GET dump, while the byte-for-byte identical control run with qsize=50 yields len=50, drops=349. Growth is bounded only by available kernel memory. Identical code to v1 DF-0376; missed by pass-1 of this file.