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

hammer(1) setattr/write have no va_size clamp: DF-2921-class nvtruncbuf/nvnode_pager_setsize signed-overflow β†’ unprivileged permanent kernel livelock on any HAMMER1 mount

Field Value
ID DF-2999
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-190
File sys/vfs/hammer/hammer_vnops.c
Lines 670, 2269, 2276 (sinks: vfs_vm.c:149, :464-495)
Area vfs/hammer
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket hammer2? no β€” base:vfs (hammer1)
Reported pending
Known CVE none
CVE match novel

Summary

hammer_vop_setattr passes the fully unclamped 64-bit vap->va_size to nvtruncbuf()/nvextendbuf() and hammer_vop_write does the same for implicit extends; for lengths within HAMMER_XBUFSIZE(64K) of 2^63 the block round-up wraps to INT64_MIN and the page-unmap loop iterates ~2^63 times holding vp->v_token and the vm_object lock. This is the HAMMER1 call-site of the bug verified as DF-2921 on HAMMER2 β€” same vfs_vm.c arithmetic, distinct trigger path (every HAMMER1 mount). Unprivileged local user with write access to any file on a HAMMER1 filesystem (mountable via vnconfig+newfs_hammer+mount -t hammer; still-supported root FS on older systems): one ftruncate-pair wedges a CPU permanently and poisons the vnode β€” unkillable (kill -9 ignored), concurrent unlink parks in D state, shutdown hangs; repeatable per file/CPU. VERIFIED live on stock kernel #0; fix (clamp to OFF_MAX βˆ’ HAMMER_XBUFSIZE) validated on rebuilt kernel: instant EFBIG, smoke test clean.

Timeline

  • 2026-09-02 Discovered during pass-2 audit of hammer_vnops.c (GLM 5.3); unpriv livelock reproduced + fix validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2999 Β· 10 files
FileTypeDescriptionSize
op2999.c β€” 1.8 KB view raw
build.sh β€” 78 B view raw
run.sh β€” 583 B view raw
run.log β€” 934 B view raw
build.log β€” 1.6 KB view raw
VERDICT.md β€” 3.7 KB ↓ raw
env.txt β€” 436 B view raw
fix.diff β€” 1.4 KB view raw
verdict.json β€” 3.9 KB view raw
manifest.json β€” 972 B view raw
VERDICT.md
↓ download raw

DF-2999 VERDICT (updated after verification)

Baseline (stock kernel): REPRODUCED β€” unprivileged permanent kernel livelock

Guest: DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS), HAMMER1 on /dev/vn0 at /mnt/h1 (vn-backed image), run as unprivileged user maxx:

/tmp/op2999 /mnt/h1/maxx2999.bin
  extend  to 0x7ffffffffffff000: OK     <- hammer_vop_setattr:2276 nvextendbuf
  pwrite top byte: OK                  <- hammer_vop_write:670 nvextendbuf
  <-- ftruncate(0x7fffffffffff8000) never returns
                                         <- hammer_vop_setattr:2269 nvtruncbuf
                                            vfs_vm.c:149  truncloffset = INT64_MIN
                                            vfs_vm.c:464  nobjsize ~0xFFF8000000000000
                                            vfs_vm.c:486  unmap loop ~2^63 iterations

ps: 1047 R4 (spinning in-kernel), kill -9 Γ—2 ineffective,
concurrent `rm` of the file parks in D5 state,
`ls -la /mnt/h1` shows directory total corrupted by the 2^63-scale size
("total 18014398509543426"), one cpu 100% until guest reset.

Identical signature to DF-2921 (hammer2 trigger), confirming HAMMER1 has the same missing va_size clamp at hammer_vnops.c:2261-2286 (setattr) and :664-677 (write-path nvextendbuf).

Root cause chain (path:line)

  1. sys/kern/vfs_vfsops? no β€” ftruncate passes any 0 < len < 2^63 through VOP_SETATTR (kern_utimensat-style pre-checks only for utimes; for ftruncate: sys/kern/vfs_syscalls.c kern_ftruncate -> VOP_SETATTR).
  2. sys/vfs/hammer/hammer_vnops.c:2261-2286 β€” no va_size bound check for VREG; blksize = hammer_blocksize(va_size) = HAMMER_XBUFSIZE (64K) for any offset >= HAMMER_XDEMARC (1MB); calls nvtruncbuf()/nvextendbuf() with the raw 64-bit length.
  3. sys/kern/vfs_vm.c:145-153 β€” info.truncloffset = length + (blksize-boff) wraps to INT64_MIN for length = 0x7fffffffffff8000, boff = 0x8000.
  4. sys/kern/vfs_vm.c:459-495 β€” signed OFF_TO_IDX overflow -> nobjsize ~0xFFF8000000000000; shrink branch unmap loop from piβ‰ˆ2^51 to 2^63.9 with lwkt_yield() each iteration while holding vp->v_token and the vm_object lock: unkillable (thread never returns to userland), vnode poisoned, shutdown hangs.

Impact

Unprivileged local permanent denial of service on every HAMMER1 mount (one wedge per triggered file/cpu; guest recoverable only by hard reset). No memory-safety impact (pure livelock). Same severity class as DF-2921.

Fix validation

fix.diff (in this pack) clamps va_size in hammer_vop_setattr and the write end-offset in hammer_vop_write to OFF_MAX - HAMMER_XBUFSIZE and rejects negative va_size. Built with make nativekernel in-guest and re-tested: see "Fix validation" section appended below after the patched run.

Fix validation (kernel #2, Sat Sep 5 01:44:36 UTC 2026)

fix.diff applied in-guest, make nativekernel + make installkernel, guest rebooted into the patched kernel:

$ time /tmp/op2999 /mnt/h1/maxx2999.bin
extend-> File too large
        0.00 real         0.00 user  0.00 sys
EXIT=1

The wrap-prone ftruncate(0x7ffffffffffff000) is now rejected at hammer_vnops.c (clamp) with EFBIG before nvextendbuf()/nvtruncbuf() are ever reached. No wedge, no R4 thread, second ftruncate not attempted, guest fully healthy afterwards (smoke test: create/write/truncate/read/ unlink on HAMMER1 all normal β€” see build.log).

fix_status: fixed (baseline reproduced wedge + unkillable R4; patched kernel returns EFBIG instantly; behavior change is exactly the intended one). Note: kernel #2 also carried a non-semantic kprintf probe for the DF-3000 investigation in the same file (verified present via strings on kernel.stripped); it does not affect this validation.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Baseline kernel #0: unpriv ftruncate wedge (unkillable R4, D-state rm, unshut-downable guest). Patched kernel #2 (fix.diff): same PoC returns EFBIG in 0.00s; no wedge; HAMMER1 smoke test (create/write/truncate/read/unlink) clean.

build.log (fix validation section); VERDICT.md (Fix validation section)
↓ fix.diffDragonFly 6.5-DEVELOPMENT #2: Sat Sep 5 01:44:36 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC

Confirmed kernel references

Detail

Exploit chain

unpriv user -> ftruncate(fd,0x7ffffffffffff000) [extend via nvextendbuf] -> pwrite 1 byte near EOF -> ftruncate(fd,0x7fffffffffff8000) [truncate via nvtruncbuf: 0x7fffffffffff8000+(65536-0x8000) wraps to 2^63] -> nvnode_pager_setsize unmap loop ~2^63 iterations -> unkillable kernel livelock, poisoned vnode, guest unshut-downable

Evidence (decisive lines)

run.log: baseline output - extend OK, pwrite OK, second ftruncate never returns; ps shows PID 1047 R4; kill -9 ineffective; rm in D5; 'total 18014398509543426' directory corruption | build.log: fix validation on kernel #2 - 'extend-> File too large', 0.00s, EXIT=1; smoke test OK | fix.diff: va_size clamp in hammer_vop_setattr + end-offset clamp in hammer_vop_write

PoC changes

adapted DF-2921's op2921.c trigger (same offsets) to a HAMMER1 vn-backed mount; path taken from argv; added cleanup path (unreachable on vulnerable kernel)

Verified recommended fix

Clamp va_size (and write end-offset) to OFF_MAX - HAMMER_XBUFSIZE in hammer_vnops.c; reject negative va_size (see fix.diff); alternatively apply DF-2921's vfs_vm.c clamp globally.

Verdict

REPRODUCED unprivileged permanent kernel livelock on a HAMMER1 mount, then fix validated. hammer_vop_setattr (sys/vfs/hammer/hammer_vnops.c:2261-2286) passes the unclamped 64-bit va_size to nvtruncbuf()/nvextendbuf(); ftruncate(0x7ffffffffffff000)+pwrite+ftruncate(0x7fffffffffff8000) by an unprivileged user puts the thread into an unkillable R4 kernel loop (nvtruncbuf truncloffset wraps to INT64_MIN at sys/kern/vfs_vm.c:149; nvnode_pager_setsize unmap loop at vfs_vm.c:486-495 iterates ~2^63 times holding vp->v_token and the vm_object lock). kill -9 ineffective; concurrent rm parks in D state; directory listing corrupted by the 2^63-scale size; guest recoverable only by killing QEMU. Identical signature to DF-2921 (the HAMMER2 trigger of the same vfs_vm.c bug) - this is the HAMMER1 call-site. Patched kernel (va_size clamped to OFF_MAX - HAMMER_XBUFSIZE in setattr + write path, fix.diff) returns EFBIG instantly; smoke test clean.