vkbd devfs clone unit numbers are never released on close β permanent unbounded unit/bitmap leak on the device's normal lifecycle
Summary
vkbd_dev_clone allocates a unit via devfs_clone_bitmap_get (unbounded) on every alias open, but close destroys the dev and never calls devfs_clone_bitmap_put. Every open/close cycle permanently consumes a unit and grows the kernel bitmap: REPRODUCED live with 1000 fully-closed cycles advancing the allocator to unit 117699 (cumulative ~116.7k units burned on one boot; a reconnecting input bridge burns ~90k in 6 minutes). devfs_clone_bitmap_put fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4302 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| vkbd_unit_leak.c | β | 1.8 KB | view raw | |
| build.sh | β | 96 B | view raw | |
| run.sh | β | 113 B | view raw | |
| build.log | β | 98 B | view raw | |
| run.log | β | 166 B | view raw | |
| env.txt | β | 538 B | view raw | |
| fix.diff | β | 294 B | view raw | |
| manifest.json | β | 605 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-4302 β vkbd devfs clone unit numbers are never released (unbounded, permanent leak)
Finding
vkbd_dev_clone() (sys/dev/misc/vkbd/vkbd.c:222-231) allocates a unit
from the devfs clone bitmap on every open of the /dev/vkbdctl alias:
int unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(vkbd), 0);
ap->a_dev = make_only_dev(&vkbd_dev_cdevsw, unit, ...);
but vkbd_dev_close() (vkbd.c:288-334) β the only teardown path for a
cloned unit β calls destroy_dev(dev) and never calls
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(vkbd), unit). With
limit == 0 (unbounded, devfs_helper.c:221-237), every open/close cycle
permanently consumes one unit number and grows the kernel-side bitmap:
a vkbd consumer that reconnects (desktop input bridges, VM input
passthrough) leaks unit numbers for the uptime of the system.
Reproduced (live, guest)
DragonFly 6.5-DEVELOPMENT X86_64_GENERIC, 6 vCPU, kldload vkbd:
highest vkbdctl unit before: -1 (no node: everything closed) vkbdctl unit handed out after 1000 full open/write/close cycles: 117699 units leaked: 117700
1000 fully-closed cycles advanced the allocator to unit 117,699 (the earlier missed-wakeup hammer on the same boot had already consumed ~116,699 the same way β ~90k of those within ~6 minutes at ~250 cycles/s). The counter never rewinds; nodes are removed by destroy_dev but the numbers are gone forever, and the bitmap (and unit number magnitude) grow without bound.
Run: build.sh / run.sh (trigger: vkbd_unit_leak.c).
DF-4302 verdict
Reproduced?
YES β reproduced live on the audit guest (status reproduced,
impact dos-adjacent resource exhaustion, severity Low).
Trigger: vkbd_unit_leak.c β scans /dev for the highest vkbdctlN,
performs 1000 completely balanced open("/dev/vkbdctl")/write/close
cycles, then opens once more and re-scans.
Observed (run.log): before: -1 (no vkbdctlN node β prior cycles all closed) after 1000 closed cycles + probe open: unit 117699 handed out. => every cycle permanently consumed a unit number. Cumulative on this boot: ~116,699 units burned by earlier hammers (~90k in ~6 min at ~250 cycles/s), matching 1 unit per open regardless of close.
Root cause (path:line)
- sys/dev/misc/vkbd/vkbd.c:225 β
devfs_clone_bitmap_get(..., 0)allocates; limit 0 => unbounded (sys/vfs/devfs/devfs_helper.c:221-237). - sys/dev/misc/vkbd/vkbd.c:316-328 β close resets queue, tears down
kbd,
destroy_dev(dev), but neverdevfs_clone_bitmap_put(...)(contrast: devfs_core.c:2672 shows the intended put pattern).
Impact
Unbounded kernel resource-number leak on the device's normal lifecycle; bitmap chunks (kmalloc'd, devfs_helper.c) grow forever; /dev/vkbdctlN naming becomes unstable for tooling; combined with keyboard_info_t kb_unit lookups the namespace grows monotonically per boot. Requires the control node (root), hence Low.
Fix
Put the unit back in vkbd_dev_close (see fix.diff). Note destroy_dev() may be async in devfs; the put is idempotent-safe under devfs_bitmap_lock.
Fix validation
Not rebuilt (Low severity, non-corruption): the one-line fix directly closes the missing put; validated by inspection that no other consumer of the vkbd bitmap exists (only vkbd_dev_clone's get and destroy_autoclone_dev at MOD_UNLOAD, which resets the whole bitmap).
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
not_testableOne-line put added in close (fix.diff); kernel not rebuilt for this Low non-corruption finding β fix validated by inspection (bitmap has no other consumer; put is the documented devfs pattern, devfs_core.c:2672)
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (before=-1, after 1000 closed cycles next unit=117699); env.txt documents the earlier cumulative hammer; trigger vkbd_unit_leak.c
PoC changes
n/a β written fresh for this verification
Verified recommended fix
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(vkbd), dev2unit(dev)) in vkbd_dev_close().
Verdict
Reproduced live: vkbd_dev_clone() consumes a devfs clone unit on every /dev/vkbdctl open (vkbd.c:225, unbounded limit) and vkbd_dev_close() never calls devfs_clone_bitmap_put() β 1000 fully-closed open/write/close cycles advanced the allocator to unit 117699 (cumulative ~116.7k units burned on the boot, ~90k within ~6 minutes at ~250 cycles/s). Permanent, unbounded kernel resource-number leak on the device's normal lifecycle; unit numbers never reused; bitmap grows forever.
No comments yet.