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-addhistory 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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2614 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
-
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 issueHAMMER2IOC_REMOTE_ADD(hammer2_ioctl.c:308 -> hammer2_volconf_update) as root:dmesggainsvolconf update 0xfffff801177797c0β the live kmalloc'dkdmsg_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. -
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 00000001kprintf 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.ctrigger (mount with cluster_fd, REMOTE_ADD ioctl, PAD-frame flood, unmount)run_df2614.shguest-side orchestration (image, newfs, build, run, dmesg deltas)build.sh/run.shhost-side wrappersrun.logFULL untrimmed stock-kernel run (set -x), incl. pointer values and countstrigger.stock.outtrigger stdout, stock kernelrun.fix.logsame trigger on the fix kernel (0/0 deltas)unpriv_read.loglive capture: unprivileged user reads the pointerfix.diffthe fix (drop %p print, gate the rest behind hammer2_debug & 0x0100)build.logcc 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.
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
fixedbaseline (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)
Confirmed kernel references
- sys/vfs/hammer2/hammer2_iocom.c:135
- sys/vfs/hammer2/hammer2_iocom.c:220
- sys/vfs/hammer2/hammer2_iocom.c:236
- sys/vfs/hammer2/hammer2_iocom.c:241
- sys/vfs/hammer2/hammer2_iocom.c:376
- sys/vfs/hammer2/hammer2_iocom.c:378
- sys/vfs/hammer2/hammer2_vfsops.c:1350
- sys/vfs/hammer2/hammer2_ioctl.c:308
- sys/kern/kern_dmsg.c:189
- sys/kern/kern_dmsg.c:343
- sys/kern/kern_dmsg.c:1220
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).
No comments yet.