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.
