NUL-termination write underflows when aux_size==0 (latent OOB write, dead code today; live twin at hammer2_iocom.c:149)
| Field | Value |
|---|---|
| ID | DF-0936 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-787 Out-of-bounds Write |
| File | sys/vfs/hammer2/hammer2_msgops.c |
| Lines | 72-74 |
| Area | vfs (hammer2) |
| Confidence | certain |
| Discovered | 2026-07-05 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
hammer2_msg_dbg_rcvmsg writes msg->aux_data[msg->aux_size - 1] = 0
with only an aux_data != NULL guard. aux_size is size_t
(unsigned, sys/sys/dmsg.h:774), so if the precondition
aux_data != NULL && aux_size == 0 ever arises, the index underflows
to SIZE_MAX and the kernel writes a NUL byte at
aux_data + 2^64-1 β a guaranteed page-fault panic or worse. The bug
is currently unreachable because the kdmsg receive path
(kern_dmsg.c:380-391) only allocates aux_data when aux_size > 0,
and both functions in this file are dead code (no callers in
sys/). The identical pattern is live at hammer2_iocom.c:149-152
with the same protection.
Root cause
At hammer2_msgops.c:72-74:
case DMSG_DBG_SHELL | DMSGF_REPLY:
if (msg->aux_data) {
msg->aux_data[msg->aux_size - 1] = 0;
kprintf("DEBUGMSG: %s\n", msg->aux_data);
}
The guard tests only aux_data != NULL. The subtraction
aux_size - 1 is on a size_t (sys/sys/dmsg.h:774 declares
size_t aux_size;), so when aux_size == 0 it evaluates to
(size_t)-1 = 0xFFFFFFFFFFFFFFFF on amd64, and the assignment writes
a NUL byte at that offset. The author assumed
aux_data != NULL implies aux_size > 0, which is an undocumented
invariant maintained only by kern_dmsg.c:380-391
(if (msg->aux_size) { ... kmalloc ... }). The invariant is not
enforced locally and is easy to break.
Auxiliary reachability notes:
hammer2_msg_adhoc_input(hammer2_msgops.c:42-47) is harmless (prints only the 32-bit cmd).hammer2_msg_dbg_rcvmsgis dead code:rgfinds only the definitions (hammer2_msgops.c:43,50) and the prototypes (hammer2.h:1869-1870). No C code in the tree takes their address or calls them.- The live twin is
hammer2_rcvdmsgathammer2_iocom.c:149-152; same bug shape, same kdmsg receive-path protection.
Threat model & preconditions
- Attacker position: No attacker-reachable path exists today. To
trigger from an unauthenticated cluster peer one would first need a
code change that violates the kdmsg receive invariant
(
aux_dataallocated only whenaux_size > 0), or that wireshammer2_msg_dbg_rcvmsg/hammer2_msg_adhoc_inputup to a msg source that does not preserve it. - Privileges gained or impact: If such a change is ever made, an
unauthenticated HAMMER2 cluster peer (plain TCP, no auth in the wire
protocol) sending a
DMSG_DBG_SHELL | DMSGF_REPLYmessage withaux_bytes == 0in the header but withaux_datapopulated by some other path would convert this into an immediate kernel panic (write to unmapped address) or, if the address space maps that offset, an arbitrary kernel-memory NUL byte write. - Required config or capabilities: None today (dead code + invariant holds).
- Reachability: Not reachable against an unmodified tree.
Proof of concept
No working exploit can be produced against an unmodified tree because
(a) the function is never called and (b) the kdmsg receive path never
produces aux_data != NULL with aux_size == 0. A demonstrative PoC
must synthesize the precondition in-kernel (KLD), which only proves the
bug exists in the code; it cannot be triggered by an unprivileged user
or a network peer against a stock kernel.
Impact
Nil today (dead code + invariant holds). Reported as defense-in-depth /
hardening: the bug is a landmine waiting for a future code change, and
the live twin at hammer2_iocom.c:149-152 carries the same risk.
Recommended fix
Guard the write with aux_size != 0, making the function
self-defensive regardless of the caller's invariant:
--- a/sys/vfs/hammer2/hammer2_msgops.c
+++ b/sys/vfs/hammer2/hammer2_msgops.c
@@ -69,8 +69,11 @@ hammer2_msg_dbg_rcvmsg(kdmsg_msg_t *msg)
* This is a one-way packet but if not (e.g. if part of
* a streaming transaction), we will have already closed
* our end.
+ *
+ * Be defensive: aux_data may be non-NULL with aux_size==0
+ * on some paths; bail out instead of underflowing.
*/
- if (msg->aux_data) {
+ if (msg->aux_data != NULL && msg->aux_size != 0) {
msg->aux_data[msg->aux_size - 1] = 0;
kprintf("DEBUGMSG: %s\n", msg->aux_data);
}
The identical fix should be applied to the live twin at
sys/vfs/hammer2/hammer2_iocom.c:149 (the if (msg->aux_data) there
should likewise become if (msg->aux_data != NULL && msg->aux_size != 0)).
Additionally, since both functions in this file are dead code, consider
deleting them (and their prototypes at hammer2.h:1869-1870) to reduce
attack surface and the chance of someone wiring them up later without
re-auditing.
References
sys/sys/dmsg.h:774βsize_t aux_size.sys/kern/kern_dmsg.c:380-391β the kdmsg receive path that maintains theaux_data != NULL βΉ aux_size > 0invariant.sys/vfs/hammer2/hammer2_iocom.c:149-152β the live twin carrying the same bug.
Timeline
- 2026-07-05 Discovered during automated audit.
- pending Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0936 Β· 1 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | NUL-termination write underflows when aux_size==0 (latent OOB write, dead code t | 405 B | view raw |
Fix verification
fixedfix.diff applied + combined nativekernel build rc=0 (-Werror)
fix.diff applied + combined nativekernel build rc=0 (-Werror)
Confirmed kernel references
β
Detail
Exploit chain
none (Info severity)
Evidence (decisive lines)
Source-confirmed at sys/vfs/hammer2/hammer2_msgops.c:72: NUL-termination write underflows when aux_size==0 (dead code; live twin at iocom.c)
Verified recommended fix
Source-confirmed at sys/vfs/hammer2/hammer2_msgops.c:72: NUL-termination write underflows when aux_size==0 (dead code; live twin at iocom.c)
Verdict
Source-confirmed at sys/vfs/hammer2/hammer2_msgops.c:72: NUL-termination write underflows when aux_size==0 (dead code; live twin at iocom.c)
No comments yet.