# DF-2948 — VERDICT

**Status: reproduced** (impact: panic — boot-time kernel panic via integer
truncation in callwheel sizing; pristine single-variable run)

## What was run

Stock guest, kernel `DragonFly 6.5-DEVELOPMENT #0` (X86_64_GENERIC,
6 vCPU, 4 GB). Single change: one line appended to /boot/loader.conf:

    kern.ncallout="2147483647"

then `shutdown -r now`.

## What happened (vs. what was predicted)

Predicted chain: `TUNABLE_INT_FETCH("kern.ncallout", &ncallout)` at
subr_param.c:330 runs *after* the only clamp (`5*60*hz`, lines 328-329), so
INT_MAX enters untouched → `target = ncallout/ncpus + 16` = 357913966 →
`cwheelsize` doubles to 2^30 → `int wheel_sz = 24 * 2^30` truncates to 0
(mod 2^32) → six per-CPU `kmem_alloc3(kernel_map, 0)`.

Observed (panic.txt): boot dies right after "Initialize MI interrupts for
6 cpus" — the SI_SUB_SOFTCLOCKS phase — with:

    panic: vm_map_entry_link: dup addr map 0xffffffff8159aea0 ent 0xffffffff81709b18
    Trace beginning at frame 0xffffffff817a9e30
    Debugger("panic")
    Stopped at      -0x7f433c74:    movb   $0,0xbd77f9(%rip)
    db>

i.e. the second per-CPU zero-length callwheel allocation collided with the
first in kernel_map and the map code panicked. The guest sat in DDB; ssh
never came up; `vm.sh status` → down.

The panic fired *before* the predicted OOB stomp (`for (i = 0; i <
cwheelsize; ++i) spin_init(&sc->callwheel[i].spin, ...)` — 2^30 × 24 bytes
≈ 25 GB of init writes past a zero-byte allocation, kern_timeout.c:410-413),
so the demonstrated impact is boot DoS, while the code path clearly shows
the latent memory-corruption consequence whenever the zero-length
allocation does not trip the map assertion first (e.g. differing vm_map
behavior, or values of ncallout that yield a *small positive* truncated
wheel_sz with a huge cwheelsize, such as ncallout giving cwheelsize = 2^30
with sizeof 24 → 0; ncpus=1..2 with target in (2^30, 2^31) instead
overflows the doubling loop `1 << 31` into an infinite boot loop).

Two runs performed: (1) with a stale `kern.nbuf="-1048576"` line still
present — same panic (and that line had been separately proven harmless,
see DF-2946 VERDICT.md negative results); (2) pristine single-variable
re-run after `vm.sh reset` — identical panic, identical addresses.

## Why it is a finding and not a duplicate of DF-0174

DF-0174 covers `ncallout = 16 + maxproc + maxfiles` overflow via
*unbounded kern.maxfiles*. This finding is the **direct tunable** input:
`kern.ncallout` is fetched *after* the only clamp in the file
(subr_param.c:330 vs 328-329), so it bypasses every bound regardless of
maxfiles, and the defective sink is the `int target` / `int cwheelsize` /
`int wheel_sz` arithmetic in `swi_softclock_setup` (kern_timeout.c:386-407)
— `wheel_sz` truncation to 0 and the signed-shift overflow are not
reachable through the maxfiles path on multi-CPU boxes (maxproc is capped
at `limsize*40` ≈ 327k and even INT_MAX maxfiles only wraps the *sum*,
which the `5*60*hz` clamp then re-bounds when reached through line 327).
Fixing DF-0174 by clamping maxfiles would leave `kern.ncallout` fully
unclamped.

## Trust boundary / severity

Boot tunable — requires root/loader-prompt/console. No runtime sysctl
exists for ncallout (kern_mib.c has none; it is boot-only). Ceiling:
persistent unbootable system. Rated **Low** (privileged boot DoS),
consistent with DF-0173/DF-0174 ratings.

## Fix validation

Not performed in-guest (Low boot-DoS class; the demonstrated manifestation
is a panic, not corruption — the corruption path is latent behind the
vm_map panic). `fix.diff` clamps `kern.ncallout` to the same `5*60*hz`
bound *after* the fetch (subr_param.c) and fixes the `int` math in
`swi_softclock_setup` (`size_t` wheel sizing with overflow guard).
