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

config_pipe accepts negative flowset weight: pipe->sum driven to exactly 0 β†’ integer divide-by-zero panic in ready_event_wfq (missed by both v1 and v3 pass-1)

Summary

Flowset weight is clamped only against 0 and >100; weight=-1 is stored verbatim and added into pipe->sum on first flow arrival. One flow on a weight=+1 flowset plus one flow on a weight=-1 flowset of the same pipe makes sum exactly 0 while a packet is still queued in scheduler_heap; the next WF2Q credit tick executes p->V += (len << MY_M) / p->sum with sum==0 -> Fatal trap 18. Negative sum also stalls virtual time V and corrupts WF2Q eligibility. VERIFIED deterministically (~12 ms after the second trigger packet) on stock INVARIANTS guest: Fatal trap 18 'Stopped at ready_event_wfq+0xd1: divq %rcx,%eax', current process Idle. Fixed on rebuilt kernel (weight clamped <=0->1, no panic, positive control passes traffic). Root-configured local DoS (raw socket SYSCAP_NONET_RAW).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3352 Β· 12 files
FileTypeDescriptionSize
README.md β€” 901 B ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
dfv3.c β€” 10.6 KB view raw
setup.sh β€” 896 B view raw
build.sh β€” 130 B view raw
run.sh β€” 120 B view raw
run.log β€” 922 B view raw
panic.txt β€” 409 B view raw
fix.diff β€” 1.8 KB view raw
env.txt β€” 447 B view raw
verdict.json β€” 4.1 KB view raw
manifest.json β€” 967 B view raw

DF-3352 β€” negative flowset weight reaches p->sum==0 -> integer divide-by-zero panic in ready_event_wfq

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 negweight

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: clamp weight <=0 to 1 at flowset creation (fix.diff hunk 3)).

VERDICT.md
↓ download raw

DF-3352 VERDICT β€” reproduced

Root cause

config_pipe() clamps flowset weight only against 0 and >100 (ip_dummynet3.c:1547-1551); a negative weight (e.g. -1) is stored verbatim in the flowset. dummynet_io() then does pipe->sum += fs->weight (:1150), so one flow on a weight=+1 flowset and one flow on a weight=-1 flowset of the same pipe drive pipe->sum to exactly 0 while a packet of the second flow sits in scheduler_heap. When the WF2Q credit timer fires, ready_event_wfq() moves that packet and executes p->V += (len << MY_M) / p->sum (:574) with sum==0 -> integer divide fault. Reproduced 1/1 on the stock INVARIANTS guest: Fatal trap 18 'integer divide fault', Stopped at ready_event_wfq+0xd1: divq %rcx,%eax, current process Idle (netisr drain on cpu0), CPU0 stopped. Deterministic, ~12ms after the second trigger packet. Root-configured (raw-socket setsockopt IP_DUMMYNET_CONFIGURE behind SYSCAP_NONET_RAW), so local privileged DoS; same validation gap also lets sum go negative (V stalls, WF2Q eligibility corrupted). v1 twin file has the identical bug; it was missed by both pass-1 audits.

Exploit chain

root: setsockopt pipe{bw=1Mbit} + fs{w=+1} + fs{w=-1} on one pipe; 2 UDP packets via lo0 ipfw3 queue rules (different dst-ports -> different flowsets/flows); first flow sum=1, second flow sum=0; next wfq_ready_heap fire -> divq by zero -> kernel panic. No uid=0: divide-fault crash primitive only.

Evidence

  • panic.txt: Fatal trap 18 integer divide fault, Stopped at ready_event_wfq+0xd1 divq %rcx,%eax
  • run.log: full setup+trigger transcript (panic ~12ms after 2nd packet)
  • dfv3.c: negweight subcommand (raw setsockopt structs verbatim from ip_dummynet3.h)

Kernel refs

Fix validation

fix.diff hunk: clamp weight <=0 to 1 at flowset creation (fix.diff hunk 3). 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). weight<=0 clamped to 1 at flowset creation: negweight trigger survives 8s with no divide fault (baseline panicked within ~12ms); same config doubles as positive control showing WF2Q still passes traffic. 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=1Mbit} + fs{w=+1} + fs{w=-1} on one pipe; 2 UDP packets via lo0 ipfw3 queue rules (different dst-ports -> different flowsets/flows); first flow sum=1, second flow sum=0; next wfq_ready_heap fire -> divq by zero -> kernel panic. No uid=0: divide-fault crash primitive only.

Evidence (decisive lines)

['panic.txt: Fatal trap 18 integer divide fault, Stopped at ready_event_wfq+0xd1 divq %rcx,%eax', 'run.log: full setup+trigger transcript (panic ~12ms after 2nd packet)', 'dfv3.c: negweight subcommand (raw setsockopt structs verbatim from ip_dummynet3.h)']

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 weight <=0 to 1 at flowset creation (fix.diff hunk 3)

Verdict

config_pipe() clamps flowset weight only against 0 and >100 (ip_dummynet3.c:1547-1551); a negative weight (e.g. -1) is stored verbatim in the flowset. dummynet_io() then does pipe->sum += fs->weight (:1150), so one flow on a weight=+1 flowset and one flow on a weight=-1 flowset of the same pipe drive pipe->sum to exactly 0 while a packet of the second flow sits in scheduler_heap. When the WF2Q credit timer fires, ready_event_wfq() moves that packet and executes p->V += (len << MY_M) / p->sum (:574) with sum==0 -> integer divide fault. Reproduced 1/1 on the stock INVARIANTS guest: Fatal trap 18 'integer divide fault', Stopped at ready_event_wfq+0xd1: divq %rcx,%eax, current process Idle (netisr drain on cpu0), CPU0 stopped. Deterministic, ~12ms after the second trigger packet. Root-configured (raw-socket setsockopt IP_DUMMYNET_CONFIGURE behind SYSCAP_NONET_RAW), so local privileged DoS; same validation gap also lets sum go negative (V stalls, WF2Q eligibility corrupted). v1 twin file has the identical bug; it was missed by both pass-1 audits.