β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2614

Debug kprintf's in hammer2 cluster message path leak kernel heap pointer to msgbuf and allow peer-driven console flooding

Field Value
ID DF-2614
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:L
CWE CWE-200 Exposure of Sensitive Information in a Message
File sys/vfs/hammer2/hammer2_iocom.c
Lines 135-376
Area vfs
Confidence certain
Discovered 2026-08-28
Pass 2 (GLM 5.3 second pass)
Bucket hammer2
Reported pending
Known CVE none
CVE match novel (same class as DF-0815, different site)

Summary

Leftover debug prints in the hammer2 iocom message path print a kernel heap pointer (the kmalloc'd kdmsg_state_t in hmp->iocom.conn_state) to the console/msgbuf on every volume-config update, and print an unconditional line for every unhandled cluster message. The pointer leak defeats KASLR (same class as DF-0815, different site); the per-message print lets a malicious cluster peer flood/stall the console and log buffer.

Root cause

hammer2_iocom.c:376 executes kprintf("volconf update %p\n", hmp->iocom.conn_state) on every hammer2_volconf_update() call. Callers: (a) hammer2_iocom.c:227-233 in the DMSG_LNK_CONN|CREATE|REPLY handler, which fires whenever a connected cluster peer answers the kernel's auto-CONN (kern_dmsg.c:184-191 autoinitiate, kdmsg_lnk_conn_reply at kern_dmsg.c:196-238); and (b) the privileged REMOTE_ADD/REMOTE_DEL ioctls (hammer2_ioctl.c:308, 349). conn_state is a heap pointer from kmalloc(sizeof(kdmsg_state_t)) (kern_dmsg.c:1796) β€” its address lands in the msgbuf where unprivileged users read it (dmesg / /var/log).

Additionally hammer2_iocom.c:135 prints RCVMSG %08x for EVERY message that falls through to hammer2_rcvdmsg (kern_dmsg.c:1220 default branch); a peer can stream unhandled one-off messages without limit, monopolizing the console lock and filling the ring buffer (severe on serial consoles, where each line costs milliseconds of synchronized output in the rx thread). Related unconditional prints at lines 220, 236, 241, 378 are the same class but lower volume.

Threat model & preconditions

  • Attacker position 1 (info leak): any unprivileged local user reads the leaked heap address from msgbuf/log after any cluster CONN handshake or a root REMOTE_ADD/DEL ioctl.
  • Attacker position 2 (DoS): a cluster peer reachable through the userland hammer2 service daemon's connection (same position as DF-0017) sends unlimited unknown one-off messages.
  • Privileges gained or impact: KASLR-defeating heap-layout disclosure usable to refine other kernel exploits; console/log flood degrading system-wide console throughput.
  • Required config or capabilities: clustered hammer2 mount or any hammer2 remote-add history for (1); dmsg link for (2).
  • Reachability: dmesg read; dmsg message stream.

Proof of concept

Build & run

1) on a machine with a clustered hammer2 mount (or after any root
   'hammer2 remote-add' while dmesg is world-readable):
   dmesg | grep 'volconf update'    # as an unprivileged user
2) as the remote end of a dmsg link accepted by the hammer2 service daemon,
   spray one-off unknown messages (e.g. cmd=DMSG_LNK_PAD, no CREATE, 64-byte
   header with valid magic/CRC), ~300 lines/s saturates a 115200 serial console.

Expected output

1) 'volconf update 0xfffffe00ab12cd40' β€” a live kmalloc'd kdmsg_state_t
   address readable by an unprivileged user.
2) console becomes unresponsive during the spray; rx thread pegged in kprintf.

Impact

One kernel heap pointer per volconf update, readable by any local user; and a peer-driven console/log flood on clustered systems. No privilege gained directly.

Remove the pointer print and gate all message-path debug prints behind hammer2_debug. (Never format kernel pointers into kprintf; if debug output is required, print only the boolean conn_state != NULL.)

--- a/sys/vfs/hammer2/hammer2_iocom.c
+++ b/sys/vfs/hammer2/hammer2_iocom.c
@@ -132,8 +132,6 @@ static int
 hammer2_rcvdmsg(kdmsg_msg_t *msg)
 {
-   kprintf("RCVMSG %08x\n", msg->tcmd);
-
    switch(msg->tcmd) {
    case DMSG_DBG_SHELL:
        /*
@@ -217,13 +215,13 @@ hammer2_autodmsg(kdmsg_msg_t *msg)
     */
    if (msg->any.head.cmd & DMSGF_CREATE) {
-       kprintf("HAMMER2: VOLDATA DUMP\n");
+       if (hammer2_debug & 0x0100)
+           kprintf("HAMMER2: VOLDATA DUMP\n");
        /* ... loop unchanged ... */
-       kprintf("HAMMER2: INITIATE SPANs\n");
+       if (hammer2_debug & 0x0100)
+           kprintf("HAMMER2: INITIATE SPANs\n");
    }
@@ -239,7 +237,8 @@ hammer2_autodmsg(kdmsg_msg_t *msg)
        msg->state && (msg->state->txcmd & DMSGF_DELETE) == 0) {
-       kprintf("HAMMER2: CONN WAS TERMINATED\n");
+       if (hammer2_debug & 0x0100)
+           kprintf("HAMMER2: CONN WAS TERMINATED\n");
    }
@@ -371,10 +370,8 @@ void
 hammer2_volconf_update(hammer2_dev_t *hmp, int index)
 {
    /* XXX interlock against connection state termination */
-   kprintf("volconf update %p\n", hmp->iocom.conn_state);
    if (hmp->iocom.conn_state) {
-       kprintf("TRANSMIT VOLCONF VIA OPEN CONN TRANSACTION\n");
        msg = kdmsg_msg_alloc(hmp->iocom.conn_state,
                      DMSG_LNK_HAMMER2_VOLCONF,

References

  • DF-0815 (same KASLR-leak class, different site)
  • DF-0017 (cluster peer threat position)

Timeline

  • 2026-08-28 Discovered during automated audit (pass 2, GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2614 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
df2614_trigger.c β€” 5.4 KB view raw
run_df2614.sh β€” 1.3 KB view raw
build.sh β€” 408 B view raw
run.sh β€” 755 B view raw
build.log β€” 0 B ↓ download
run.log β€” 1.8 KB view raw
trigger.stock.out β€” 131 B view raw
run.fix.log β€” 1.7 KB view raw
unpriv_read.log β€” 1.1 KB view raw
fix.diff β€” 1.8 KB view raw
verdict.json β€” 5.4 KB view raw

DF-2614 β€” hammer2 iocom debug kprintf's (heap pointer + peer-driven flood)

What was verified (stock kernel #0, then fix kernel)

  1. Heap-pointer leak (hammer2_iocom.c:376) β€” reproduced: mount a hammer2 volume with cluster_fd = our socketpair end (the mount program's documented cluster-controller hook, hammer2_vfsops.c:1350-1357), then issue HAMMER2IOC_REMOTE_ADD (hammer2_ioctl.c:308 -> hammer2_volconf_update) as root: dmesg gains volconf update 0xfffff801177797c0 β€” the live kmalloc'd kdmsg_state_t (conn_state, kern_dmsg.c:189-190). A second line appears from the unmount teardown (simulated CONN failure -> hammer2_autodmsg -> volconf_update at hammer2_iocom.c:231). Readable by an unprivileged user: run_user 'dmesg | grep "volconf update"' returns the pointer (see unpriv_read.log); the lines also land in /var/log/messages. Bonus context: HAMMER2: VOLDATA DUMP / INITIATE SPANs (:220/:236) fire even at ordinary root-fs boot and at unmount teardown, and vfsops prints more ungated heap pointers (hammer2_mount: hmp=%p pmp=%p) adjacent to this finding.

  2. Peer-driven console/msgbuf flood (hammer2_iocom.c:135) β€” reproduced: from the peer end of the cluster socket, send 1000 one-way 64-byte LNK_PAD frames (magic 0x4832, cmd 0x1; the kernel's receive path validates only magic+size, kern_dmsg.c:343-352) -> the kernel emits exactly 1000 RCVMSG 00000001 kprintf lines β€” one per message, no flag, no rate limit β€” to the console lock and the msgbuf ring. A remote cluster node (the threat position in the finding) reaches the same kernel path through the userland hammer2 service daemon's relayed connection.

Files

  • df2614_trigger.c trigger (mount with cluster_fd, REMOTE_ADD ioctl, PAD-frame flood, unmount)
  • run_df2614.sh guest-side orchestration (image, newfs, build, run, dmesg deltas)
  • build.sh/run.sh host-side wrappers
  • run.log FULL untrimmed stock-kernel run (set -x), incl. pointer values and counts
  • trigger.stock.out trigger stdout, stock kernel
  • run.fix.log same trigger on the fix kernel (0/0 deltas)
  • unpriv_read.log live capture: unprivileged user reads the pointer
  • fix.diff the fix (drop %p print, gate the rest behind hammer2_debug & 0x0100)
  • build.log cc output

Build & run (from the repo root)

scp -F dfbsd-qemu/config findings/poc/DF-2614/df2614_trigger.c findings/poc/DF-2614/run_df2614.sh dfbsd:/root/poc/df2614/
dfbsd-qemu/vm.sh run_root 'sh /root/poc/df2614/run_df2614.sh'

Stock kernel: DELTA_volconf=2, DELTA_rcvmsg=1000. Fix kernel: DELTA_volconf=0, DELTA_rcvmsg=0, operations unchanged.

VERDICT.md
↓ download raw

DF-2614 VERDICT

Reproduced: YES (stock kernel) β€” kernel heap pointer to msgbuf (world-readable) + ungated per-message console print; fix validated on rebuilt kernel.

How (stock kernel #0, unmodified source, 2026-08-29 ~10:26-10:30 UTC)

Trigger program df2614_trigger.c: 1. socketpair(AF_UNIX, SOCK_STREAM); mount("hammer2", "/mnt/h2poc", 0, &info) with info.cluster_fd = sv[0] β€” the documented cluster-controller hook (hammer2_vfsops.c:1350-1357 -> hammer2_cluster_reconnect -> kdmsg_iocom_reconnect kern_dmsg.c:162-165 -> autoinitiate kern_dmsg.c:184-191 sets iocom->conn_state = msg->state, a fresh kmalloc(sizeof(kdmsg_state_t), M_HAMMER2)-style allocation). 2. ioctl(fd, HAMMER2IOC_REMOTE_ADD, ...) on a file of the mounted volume -> hammer2_ioctl_remote_add (hammer2_ioctl.c:308) -> hammer2_volconf_update -> hammer2_iocom.c:376 kprintf("volconf update %p\n", hmp->iocom.conn_state). Observed: volconf update 0xfffff801177797c0 (twice: ioctl + unmount-teardown path via hammer2_autodmsg). 3. Peer-side flood: 1000 one-way LNK_PAD frames (64-byte header, magic=0x4832, cmd=DMSG_LNK_PAD=0x1; no CRC is verified on receive β€” kern_dmsg.c:339-352) -> each reaches hammer2_rcvdmsg through kdmsg_autorxmsg's default branch (kern_dmsg.c:1220) -> hammer2_iocom.c:135 kprintf("RCVMSG %08x\n", msg->tcmd). Observed: exactly 1000 RCVMSG 00000001 lines in the msgbuf/console.

Unprivileged exposure (live, user maxx via vm.sh run_user): dmesg | grep "volconf update" -> the pointer; the lines are also in /var/log/messages. The msgbuf is world-readable on this system.

Additional ungated prints confirmed live (same class, adjacent sites, beyond the filed lines β€” for the record): HAMMER2: VOLDATA DUMP / HAMMER2: INITIATE SPANs (:220/:236) fire at ordinary root-fs boot and unmount teardown; hammer2_mount: hmp=%p pmp=%p (vfsops) prints two more heap pointers per mount.

Reachability honesty: triggering the print locally requires root (mount/ioctl). The filed threat positions stand: (1) any local user READS the pointer after a root cluster mount/remote-add (proven: unpriv read); (2) a cluster peer (unauthenticated remote node, or any process holding the daemon's socket) drives the per-message print at wire rate (proven: 1000/1000 with the peer socket). The finding's Low severity is confirmed; the pointer-leak half is KASLR-support material on KASLR-enabled kernels (this guest has none) β€” impact class: leak.

Fix validation (kernel #3/#5, X86_64_GENERIC rebuilt via nativekernel)

fix.diff removes the %p print entirely, drops the RCVMSG print, and gates the four remaining message-path prints behind hammer2_debug & 0x0100 (the codebase's own pattern, cf. hammer2_iocom.c:258). After rebuild + reboot, the identical trigger: DELTA_volconf=0, DELTA_rcvmsg=0, REMOTE_ADD_OK copyid=1, FLOOD_SENT=1000 (frames still accepted+ignored per protocol), UNMOUNT_OK β€” no functional regression, all debug output gone.

verdict

status=reproduced (impact=leak), fix validated (fixed). Severity Low is right: root-gated trigger, unprivileged read of one heap pointer, and a peer-driven console/log flood on clustered systems.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

baseline (stock #0): 2 volconf pointer lines + 1000 RCVMSG lines; patched (#3 and #5, fix.diff applied, nativekernel rebuild + reboot): 0 and 0 with REMOTE_ADD_OK/UNMOUNT_OK and all 1000 peer frames still accepted β€” leak and flood eliminated, no functional regression

findings/poc/DF-2614/run.fix.log; findings/poc/DF-2614/run.log (baseline contrast)
↓ fix.diffDragonFly 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

Confirmed kernel references

Detail

Exploit chain

root mounts hammer2 w/ cluster_fd (or any cluster CONN/remote-add history) -> kprintf volconf update %p (heap kdmsg_state_t) -> msgbuf + /var/log/messages -> unprivileged 'dmesg' read yields live kernel heap address (KASLR defeat material); cluster peer streams one-way unknown/LNK_PAD frames -> one RCVMSG kprintf per frame -> console-lock monopolization + msgbuf ring eviction

Evidence (decisive lines)

["findings/poc/DF-2614/run.log β€” FULL untrimmed stock-kernel run: 'volconf update 0xfffff801177797c0' x2, AFTER_rcvmsg=1000, TRANSMIT=2, sample RCVMSG lines", 'findings/poc/DF-2614/unpriv_read.log β€” live capture: user maxx reads the pointer via dmesg (count=2 + values) and /var/log/messages (count=2)', 'findings/poc/DF-2614/run.fix.log β€” same trigger on fix kernel: DELTA_volconf=0, DELTA_rcvmsg=0, REMOTE_ADD_OK, UNMOUNT_OK', 'findings/poc/DF-2614/fix.diff β€” pointer print removed, message-path prints gated behind hammer2_debug & 0x0100']

PoC changes

no seed existed; trigger written fresh (had to add errno.h include on first compile). Cluster connectivity obtained without the userland service daemon by passing a socketpair end as info.cluster_fd directly to mount(2), making the PoC process the cluster peer for the PAD-frame flood; REMOTE_ADD issued via ioctl on a file of the mounted volume.

Verified recommended fix

remove the %p kprintf at hammer2_iocom.c:376 and the per-message RCVMSG print at :135; gate the remaining message-path debug prints behind (hammer2_debug & 0x0100) as fix.diff does

Verdict

REPRODUCED on the stock INVARIANTS kernel and FIX VALIDATED on a rebuilt kernel. (1) Heap-pointer leak: mounting a hammer2 volume with cluster_fd = our socketpair end (hammer2_vfsops.c:1350-1357) and issuing HAMMER2IOC_REMOTE_ADD (hammer2_ioctl.c:308) as root prints 'volconf update 0xfffff801177797c0' (hammer2_iocom.c:376, %p of the kmalloc'd conn_state kdmsg_state_t set at kern_dmsg.c:189-190) to the console/msgbuf twice (ioctl + unmount-teardown via hammer2_autodmsg); a second unprivileged local user reads the pointer with plain 'dmesg | grep "volconf update"' (live-captured; msgbuf world-readable, lines also in /var/log/messages). Related ungated prints confirmed live: VOLDATA DUMP/INITIATE SPANs (:220/:236) fire at ordinary root-fs boot and unmount teardown, and vfsops prints 'hammer2_mount: hmp=%p pmp=%p' per mount (adjacent same-class sites, for the record). (2) Peer-driven console/msgbuf flood: 1000 one-way LNK_PAD frames from the cluster-socket peer produce exactly 1000 ungated 'RCVMSG %08x' kprintf lines (hammer2_iocom.c:135 via kern_dmsg.c:1220 default branch; rx validates only magic+size, no CRC, kern_dmsg.c:343-352) β€” one console-lock+serial line per peer message, no flag, no rate limit. After fix.diff (drop the %p print, drop RCVMSG print, gate the four remaining prints behind hammer2_debug & 0x0100): identical trigger yields DELTA_volconf=0, DELTA_rcvmsg=0, REMOTE_ADD_OK, 1000 frames accepted per protocol, UNMOUNT_OK β€” no functional regression. Low severity confirmed: local trigger paths are root-gated; exposure is (unprivileged) msgbuf/log read of a heap pointer and a cluster-peer-driven console/log flood; KASLR-defeat value applies to KASLR-enabled kernels (audit guest has none).