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)
PoC verification
Evidence pack
findings/poc/DF-3352 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)).
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
- sys/net/dummynet3/ip_dummynet3.c:1547
- sys/net/dummynet3/ip_dummynet3.c:1150
- sys/net/dummynet3/ip_dummynet3.c:574
- sys/net/dummynet3/ip_dummynet3.c:1182
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
fixedUnion 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)']
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.
No comments yet.