# DF-3006 VERDICT

**Status: reproduced — impact: silent kernel memory leak (~400 B/cycle, unbounded, root-triggerable).**

## Reproduction

Guest DragonFly 6.5-DEVELOPMENT #0, root. `leak.sh` (this pack):

- `leak.c` opens `/dev/devfs` and issues the stock rule ioctls:
  `DEVFS_RULE_ADD` (NAME `vn0`, LINK `leaklink`, mntpoint `/mnt/dt`), then per
  iteration `mount -t devfs df3006 /mnt/dt` (node creation auto-applies the
  rule -> `devfs_alias_create(..., rule_based=1)` -> `target->nlinks = 1`,
  devfs_core.c:1895), `DEVFS_RULE_RESET` (fires `devfs_rule_reset_node`),
  `umount /mnt/dt`.
- 300 bugged iterations: M_DEVFS 888 allocs/130K -> 1.66K allocs/251K
  (+~780 allocations, +121K, ≈400 B per cycle = `sizeof(struct devfs_node)` +
  the `d_dir.d_name` kmalloc that `devfs_freep` skips under `DEVFS_NLINKSWAIT`).
- 300 control iterations (no RESET): 251K -> 249K — flat. Deterministic
  difference, single variable (the RESET ioctl).

## Root cause (lines)

- `sys/vfs/devfs/devfs_rules.c:222-226`: reset decrements
  `node->link_target->nlinks` and then calls `devfs_gc(node)`.
- `sys/vfs/devfs/devfs_core.c:729`: `devfs_gc` -> `devfs_unlinkp(node)`.
- `sys/vfs/devfs/devfs_core.c:610-618`: `devfs_unlinkp` Nlink branch
  decrements `target->nlinks` again (this is the *correct*, universal
  decrement used by every other link-removal path).
- `sys/vfs/devfs/devfs_core.c:526-527`: `devfs_freep` with `nlinks != 0`
  (size_t underflow, can never return to 0) sets `DEVFS_NLINKSWAIT` and never
  frees -> node + name leaked. `leak_count` is decremented in the same pass
  (devfs_core.c:466), so the `DEVFS_MOUNT_DEL` warning at 1330-1334 never
  fires — the leak is silent.

## Why not higher severity

- No memory-safety consequence: the underflow drives `nlinks` *away* from 0,
  so the `target->nlinks == 0 && DEVFS_DESTROYED` premature-free branch
  (devfs_core.c:615-618) can never be taken via this bug; the result is a
  pure allocation leak.
- Trigger requires root (rule ioctls on /dev/devfs are privileged; jail
  devfs ruleset resets are system-initiated).
- Unbounded but slow: each cycle needs a mount/umount; a jailed service
  churning devfs mounts with rules could accumulate over time.

## Fix validation

Applied `fix.diff` (delete the manual decrement in `devfs_rule_reset_node`;
`devfs_gc`->`devfs_unlinkp` provides the single legitimate decrement) in the
guest `/usr/src`, rebuilt with `make nativekernel`, installed, rebooted.
Re-ran `leak.sh 300` on the patched kernel: M_DEVFS stays flat across the
bugged loop (see `fix/fix_run.log`), control unchanged. Fixed.
