Divide-by-zero kernel panic when ns_per_byte==0: CBQ class add/modify unconditionally divides by user-supplied value
Summary
rmc_newclass(:237): cl->allotment_=RM_NS_PER_SEC/nsecPerByte. rmc_modclass(:350): same divide. nsecPerByte passed verbatim from user struct cbq_opts.ns_per_byte(u_int) through cbq_add_queue_locked(altq_cbq.c:367,374) with NO zero check anywhere. Single pfaltq config ns_per_byte=0 -> integer divide-by-zero -> kernel panic netisr cpu0. Privileged local DoS. Fix: validate nsecPerByte!=0 at entry.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0441 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| cbq_div0.c | trigger-source | DIOCADDALTQ with cbq_opts.ns_per_byte=0 -> div0 | 5.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o cbq_div0 cbq_div0.c | 89 B | view raw |
| run.sh | run-script | kldload pf.ko; ./cbq_div0 | 672 B | view raw |
| VERDICT.md | verdict | panic reproduction + fix validation | 2.7 KB | β raw |
| fix.diff | suggested-fix | clamp nsecPerByte to 1 in rmc_newclass and rmc_modclass | 1.2 KB | view raw |
| panic.txt | panic-signature | Fatal trap 18 divl %r9d,%eax at rmc_newclass+0x10a | 577 B | view raw |
| env.txt | environment | uname, pf.ko loaded, securelevel | 829 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0441 β CBQ rmc_newclass / rmc_modclass divide-by-zero
Verdict: REPRODUCED (panic / privileged local DoS).
Mechanism
User-controlled struct pf_altq.pq_u.cbq_opts.ns_per_byte (a u_int, no
zero check) flows verbatim from a DIOCADDALTQ/DIOCCHANGEALTQ ioctl
through:
altq_cbq.c:374 rmc_newclass(..., opts->ns_per_byte, ...) altq_rmclass.c:237 cl->allotment_ = RM_NS_PER_SEC / nsecPerByte; <-- DIV0
The same pattern is at altq_rmclass.c:350 in rmc_modclass().
pfctl computes ns_per_byte from bandwidth ratios and won't produce 0
(pfctl_altq.c:465), but a malicious root can issue DIOCADDALTQ directly
with cbq_opts.ns_per_byte = 0 and trigger the panic.
PoC
cbq_div0.c opens /dev/pf, starts pf + ALTQ, begins an altq transaction
(DIOCXBEGIN PF_RULESET_ALTQ), adds a CBQ discipline + a root class on
vtnet0 (calls rmc_init, no div), then adds a non-root child class with
cbq_opts.ns_per_byte = 0. The third DIOCADDALTQ calls rmc_newclass
which panics on the divide.
Trigger requires root: /dev/pf ioctls need root. Privileged local DoS.
Reproduction
On DragonFly 6.5-DEVELOPMENT #0 (unpatched baseline):
# kldload pf.ko # ./cbq_div0 altq ticket=1 CBQ discipline added; altq_disc=0xfffff80118e63600 Root class added; qid=1 *** ABOUT TO ADD CHILD CLASS WITH ns_per_byte=0 -- EXPECT DIV0 PANIC ***
Panic signature (from dfbsd-qemu/boot.log, see panic.txt):
Fatal trap 18: integer divide fault while in kernel mode cpuid = 4; lapic id = 4 instruction pointer = 0x8:0xffffffff8072611a kernel: type 18 trap, code=0 Stopped at rmc_newclass+0x10a: divl %r9d,%eax
The IP rmc_newclass+0x10a matches the divide at altq_rmclass.c:237.
Fix validation
Applied fix.diff (clamps nsecPerByte to 1 if zero in both rmc_newclass
and rmc_modclass), rebuilt the kernel, rebooted into
6.5-DEVELOPMENT #1 (2026-07-18):
# ./cbq_div0 altq ticket=1 CBQ discipline added; altq_disc=0xfffff80118592d00 Root class added; qid=1 *** ABOUT TO ADD CHILD CLASS WITH ns_per_byte=0 -- EXPECT DIV0 PANIC *** UNEXPECTED: child class added without panic (qid=2) RUN_EXIT=0
The previously-panicking child-class add now succeeds (nsecPerByte clamped to 1 by the fix). No panic, no trap. Fix is VALIDATED.
Notes
The fix clamps to 1 ns/byte instead of returning EINVAL, because the CBQ config path is several layers deep and returning an error mid-transaction would require unwinding the altq state. Clamping is the cheapest correct behavior: allotment_ becomes RM_NS_PER_SEC (1 byte/sec in some unit interpretation), which is a safe degenerate value.
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live panic). CBQ ns_per_byte=0 -> div-by-zero at rmc_newclass. Root-only /dev/pf.
No comments yet.