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

red_drops divides by fs->lookup_step taken verbatim from config: lookup_step==0 β†’ kernel divide-by-zero panic on first packet to an idle RED flow (DF-0375 family confirmed in v3, missed by pass 1)

Summary

config_red copies ioc_fs->lookup_step with no validation; when a RED flowset with lookup_step==0 has a queue that goes idle with avg>0 and then receives one packet, red_drops computes (curr_time - q->q_time) / fs->lookup_step -> integer divide fault. Distinct from known DF-0382 (config_red's own max_th divisions). VERIFIED on stock guest: Fatal trap 18 'Stopped at dummynet_io+0x8c2: divq %rsi,%eax' (inlined red_drops). Fixed on rebuilt kernel (lookup_step<1 sanitized to 1). Root-configured local DoS; RED with an unvalidated step is reachable from ordinary 'ipfw3 queue ... config red' management traffic patterns.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3355 Β· 12 files
FileTypeDescriptionSize
README.md β€” 940 B ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
dfv3.c β€” 10.6 KB view raw
setup.sh β€” 896 B view raw
build.sh β€” 130 B view raw
run.sh β€” 118 B view raw
run.log β€” 1019 B view raw
panic.txt β€” 409 B view raw
fix.diff β€” 1.8 KB view raw
env.txt β€” 447 B view raw
verdict.json β€” 3.9 KB view raw
manifest.json β€” 961 B view raw

DF-3355 β€” red_drops divides by fs->lookup_step taken verbatim from config: lookup_step==0 -> kernel divide-by-zero panic (DF-0375 family in v3)

File: sys/net/dummynet3/ip_dummynet3.c (pass 2) Severity: Medium (CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H) β€” CWE-369 Divide By Zero Verdict: reproduced (panic) 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 redzero

Expected

kernel panic signature in serial console (see panic.txt)

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: sanitize lookup_step<1 to 1 in config_red (fix.diff hunk 5)).

VERDICT.md
↓ download raw

DF-3355 VERDICT β€” reproduced

Root cause

config_red() copies ioc_fs->lookup_step into the flowset without any validation (ip_dummynet3.c:1374). When a RED flowset with lookup_step==0 has a flow queue that goes idle with a non-zero average and then receives one more packet, red_drops() executes u_int t = (curr_time - q->q_time) / fs->lookup_step (:904) -> integer divide fault. Reproduced 1/1 on the stock guest: 'Fatal trap 18: integer divide fault', Stopped at dummynet_io+0x8c2: divq %rsi,%eax (red_drops is inlined into dummynet_io); the only #DE-capable division reachable in that function under this config is the lookup_step one (fs weight is 1). Trigger: RED fs (w_q=65536 so avg jumps to SCALE(1) on the 2nd packet), 3 packets to build avg>0, wait for the queue to empty, one more packet. Identical code to v1 DF-0375 (sys/net/dummynet/ip_dummynet.c); missed by pass-1 of this file (DF-0382 only covers config_red's own max_th/(max_th-min_th) divisions).

Exploit chain

root: setsockopt pipe{slow} + fs{DN_IS_RED, w_q=65536, lookup_step=0, mask dst-port}; 3 quick UDP pkts to one flow (3rd sees q_size!=0 -> avg>0), 1 pkt to a second flow (keeps scheduler busy so the idle-flush at :614 cannot mark/expire the victim queue), wait 200ms, 1 more pkt to the first flow -> red_drops q_size==0 && avg!=0 branch -> divq by zero -> panic. No uid=0.

Evidence

  • panic.txt: Fatal trap 18 integer divide fault, dummynet_io+0x8c2 divq %rsi,%eax
  • run.log: RED config + 5-packet choreography transcript
  • dfv3.c: redzero subcommand

Kernel refs

Fix validation

fix.diff hunk: sanitize lookup_step<1 to 1 in config_red (fix.diff hunk 5). 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). config_red sanitizes lookup_step<1 to 1: redzero trigger survives 8s with no divide fault (baseline: Fatal trap 18 in dummynet_io+0x8c2 on the 5th packet). 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{slow} + fs{DN_IS_RED, w_q=65536, lookup_step=0, mask dst-port}; 3 quick UDP pkts to one flow (3rd sees q_size!=0 -> avg>0), 1 pkt to a second flow (keeps scheduler busy so the idle-flush at :614 cannot mark/expire the victim queue), wait 200ms, 1 more pkt to the first flow -> red_drops q_size==0 && avg!=0 branch -> divq by zero -> panic. No uid=0.

Evidence (decisive lines)

['panic.txt: Fatal trap 18 integer divide fault, dummynet_io+0x8c2 divq %rsi,%eax', 'run.log: RED config + 5-packet choreography transcript', 'dfv3.c: redzero subcommand']

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

sanitize lookup_step<1 to 1 in config_red (fix.diff hunk 5)

Verdict

config_red() copies ioc_fs->lookup_step into the flowset without any validation (ip_dummynet3.c:1374). When a RED flowset with lookup_step==0 has a flow queue that goes idle with a non-zero average and then receives one more packet, red_drops() executes u_int t = (curr_time - q->q_time) / fs->lookup_step (:904) -> integer divide fault. Reproduced 1/1 on the stock guest: 'Fatal trap 18: integer divide fault', Stopped at dummynet_io+0x8c2: divq %rsi,%eax (red_drops is inlined into dummynet_io); the only #DE-capable division reachable in that function under this config is the lookup_step one (fs weight is 1). Trigger: RED fs (w_q=65536 so avg jumps to SCALE(1) on the 2nd packet), 3 packets to build avg>0, wait for the queue to empty, one more packet. Identical code to v1 DF-0375 (sys/net/dummynet/ip_dummynet.c); missed by pass-1 of this file (DF-0382 only covers config_red's own max_th/(max_th-min_th) divisions).