DF-2921 — unprivileged permanent kernel livelock (unkillable) via signed overflow in nvnode_pager_setsize()/nvtruncbuf() OBSERVED ON GUEST (DragonFly 6.5-DEVELOPMENT #0, stock INVARIANTS kernel, hammer2 root), triggered as unprivileged user "maxx": 1. run: /tmp/op2921b (source: op2921.c) - ftruncate(fd, 0x7ffffffffffff000) [extend, sparse, 8 EB] - pwrite 1 byte @ 0x7fffffffffffefff [dirty the last 64K block] - ftruncate(fd, 0x7fffffffffff8000) [truncate mid-64K-block] 2. Process enters state R0 (running on cpu) inside the kernel and never returns: PID STAT WCHAN COMMAND 1025 R0 - /tmp/op2921x <- first run, observed >5 min 3. kill -9 has NO effect (syscall never returns; lwkt_yield() does not check signals). Verified twice, >5 minutes each time. 4. A second process touching the same file blocks forever behind the spinner's vp->v_token / vm_object lock: 944 R0 /tmp/op2921b 953 D5 /tmp/op2921b 5. Even `rm` of the file hangs (serial console): [diagnostic] cache_lock_shared: rm blocked on 0xfffff8008f54dc00 "df2921.bin" 6. Guest can no longer be shut down: three `shutdown -p now` attempts logged and all hung; guest only recoverable by killing QEMU. 7. A forced crash dump was taken (debug.panic=1) after first wedging. ROOT CAUSE (sys/kern/vfs_vm.c): - nvtruncbuf() line 149: info.truncloffset = length + (blksize - boff); with length = 0x7fffffffffff8000, H2 nblksize = HAMMER2_PBUFSIZE = 64K (hammer2_calc_logical() always returns HAMMER2_PBUFSIZE, sys/vfs/hammer2/hammer2_subr.c:276), boff = 0x8000: 0x7fffffffffff8000 + 0x8000 = 0x8000000000000000 (signed overflow) -> truncloffset = INT64_MIN -> nvtruncbuf_bp_trunc_cmp() matches EVERY buffer on the vnode (including negative-loffset metadata buffers on UFS) -> all destroyed with B_INVAL (silently discards dirty buffers). - nvnode_pager_setsize() line 464: nobjsize = OFF_TO_IDX(truncboffset + blksize + PAGE_MASK); truncboffset = 0x7fffffffffff0000; + 0x10000 + 0xfff = 0x8000000000000fff -> negative -> arithmetic >> 12 -> vm_pindex_t 0xFFF8000000000000. - unmap loop line 486-495: pi = OFF_TO_IDX(length + PAGE_MASK); /* ~ 2^51 */ while (pi < nobjsize) { /* nobjsize ~ 2^63.9 */ vm_page_lookup_busy_wait(...); ++pi; lwkt_yield(); } ~1.8e19 iterations, each an RB-tree miss + yield -> years, while holding vp->v_token (acquired at nvtruncbuf:153) and the vm_object hold -> everything touching the vnode blocks forever. REACHABILITY: unprivileged local user on any filesystem whose nvtruncbuf block size can straddle a length within blksize of 2^63 (hammer2 always passes 64K; tmpfs/ufs/nfs unreachable because their max file sizes are far below; ext2fs/hpfs use the old vnode_pager_setsize path). HAMMER2 imposes no va_size clamp in setattr (hammer2_vnops.c:512-525) and ftruncate() accepts any non-negative off_t.