DF-2615 / verdict.json
{ "finding_id": "DF-2615", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED (memory leak, Info-class) on an instrumented kernel and FIX VALIDATED on a rebuilt kernel. Mechanism confirmed exactly as filed: kdmsg_iocom_uninit allocates a LNK_PING (kern_dmsg.c:280-281) which kdmsg_msg_write_locked TAILQ_INSERTs onto iocom->msgq (:2018-2021) because EXITNOACC is clear, the thread-join loop (:284-294) is a no-op with no threads, and nothing drains msgq before the embedded iocom is freed with hmp โ instrumentation shows per cycle: 'uninit entered rd_td=0 wr_td=0 flags=00000003', 'after ping write msgq.first=0xfffff801168ef4c0', 'uninit exit msgq_nleak=1'; 20/20 cycles leak exactly one kdmsg_msg each (run_base.dmesg_after). IMPORTANT precondition correction: stock mount_hammer2(8) auto-starts the hammer2 service daemon and passes cluster_fd >= 0 (mount_hammer2.c:225-247), so plain 'mount -t hammer2' cycles do NOT leak (write thread sets EXITNOACC at kern_dmsg.c:545 and the PING self-drains โ verified live); the leak is taken when the mount proceeds without a cluster connection (daemon unavailable -> cluster_fd=-1 at mount_hammer2.c:162-167, or any direct mount(2) caller) โ reproduced with df2615_trigger.c (mount(2) with cluster_fd=-1). The leak is invisible to vmstat on a stock kernel because the msg's per-device malloc type is kmalloc_destroy'ed at unmount (hammer2_vfsops.c:1176/1893) while the allocation lives. Fix (set EXITNOACC + kdmsg_drain_msgq in uninit, mirroring the write-thread exit path :545-554, msglk held): same 20-cycle trigger on the rebuilt kernel gives 'after ping write msgq.first=<ptr>' (same path), 'uninit exit msgq_nleak=0' x20, 0 DF2615_LEAK lines, CYCLES_DONE=20 โ leak eliminated, mount/umount behavior unchanged; the DF-2614 cluster-mount trigger also runs clean. Impact ceiling: slow root-driven kernel heap growth under mount cycling; no attacker-controlled data, no dereference after free (pure leak) โ Info stands.", "exploit_chain": "root mount(2) hammer2 with cluster_fd=-1 (or mount_hammer2 with service daemon unavailable) -> no iocom threads -> umount -> kdmsg_iocom_uninit queues LNK_PING on msgq -> threads absent so nothing drains -> kdmsg_msg leaked with embedded iocom when hmp is freed; repeat per mount/umount cycle", "evidence": [ "findings/poc/DF-2615/run_base.dmesg_after โ decisive baseline: 20x 'DF2615_LEAK: 1 kdmsg_msg(s) queued at iocom_uninit exit' with msgq.first pointers and flags=00000003 (no EXITNOACC, no threads)", "findings/poc/DF-2615/run_fix.dmesg_after โ decisive fixed run: 0 leak lines, 20x 'uninit exit msgq_nleak=0', ping still queued then drained", "findings/poc/DF-2615/instrument.diff โ verification counters (not the fix)", "findings/poc/DF-2615/fix.diff โ EXITNOACC + kdmsg_drain_msgq(iocom) in kdmsg_iocom_uninit (pristine-tree diff)", "findings/poc/DF-2615/build.log / fix_build.log โ instrumented and fixed kernel builds (nativekernel X86_64_GENERIC)" ], "kernel_refs": [ "sys/kern/kern_dmsg.c:264", "sys/kern/kern_dmsg.c:280", "sys/kern/kern_dmsg.c:284", "sys/kern/kern_dmsg.c:545", "sys/kern/kern_dmsg.c:554", "sys/kern/kern_dmsg.c:619", "sys/kern/kern_dmsg.c:2018", "sys/vfs/hammer2/hammer2_iocom.c:66", "sys/vfs/hammer2/hammer2_vfsops.c:1341", "sys/vfs/hammer2/hammer2_vfsops.c:1778", "sys/vfs/hammer2/hammer2_vfsops.c:1176", "sys/vfs/hammer2/hammer2_vfsops.c:1893" ], "poc_changes": "no seed existed. df2615_trigger.c written fresh (direct mount(2) with cluster_fd=-1 after live testing proved stock mount(8) cycles do NOT leak โ see VERDICT.md precondition correction). Instrumentation v1 (counter at uninit exit only) returned 0 lines because stock mount(8) mounts are daemon-connected; v2 added entry/mid probes which exposed wr_td alive + flags=0x8003 on mount(8) mounts, isolating cluster_fd<0 as the true leak precondition.", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline #0 stock; leak proven on instrumented #2/#4; fix validated on #3/#5)", "runtime_sec": 4800, "guest_dirty": 0, "build_cmd": "cc -O -I/usr/src/sys -o df2615_trigger df2615_trigger.c && cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC", "run_cmd": "dfbsd-qemu/vm.sh run_root 'vnconfig -c vn0 /root/poc/df2615/img; dmesg > /tmp/b; /root/poc/df2615/df2615_trigger 20; dmesg > /tmp/a; grep -c DF2615_LEAK /tmp/a; vnconfig -u vn0'", "code_hash": "58a07f9c1e1a475c0c272e8f36aa125a25816f078e5be5185b84d94326e4b93e", "notes": "Three nativekernel rebuilds (#2 instrumented, #4 baseline-recapture, #3/#5 fix). Also observed, out of scope: global M_HAMMER2 ('HAMMER2-mount') zone grows ~3.5KB per mount/umount cycle even on daemon-connected mounts โ a separate accounting leak not covered by this finding; noted for future audit. Side observation: mount(2) via generic mount(8) zeroes hammer2_mount_info, so cluster_fd=0 (stdin) can be held as the cluster descriptor โ related to why VOLDATA DUMP prints appear at root boot (see DF-2614 notes).", "recommended_fix": "in kdmsg_iocom_uninit (kern_dmsg.c, after the thread-join/cache cleanup, before dropping msg_fp): iocom->flags |= KDMSG_IOCOMF_EXITNOACC; kdmsg_drain_msgq(iocom);", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #5: Sat Aug 29 11:12:52 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "baseline (instrumented, fix reverted, kernel #4): 20 leak lines / 20 cycles, exit nleak=1 each; patched (kernels #3/#5): 0 leak lines, exit nleak=0 x20, ping still queued (msgq.first non-NULL) then drained, 20/20 cycles succeed โ leak eliminated, no behavior change", "fix_evidence": "findings/poc/DF-2615/run_fix.dmesg_after vs run_base.dmesg_after; fix_build.log" } |