--- a/sys/vfs/hammer/hammer_vnops.c +++ b/sys/vfs/hammer/hammer_vnops.c @@ -566,6 +566,16 @@ return (EFBIG); } + /* + * DF-2999: also reject a final offset that would overflow the + * signed block round-up in nvextendbuf()/nvnode_pager_setsize() + * (same 2^63 wrap as the setattr path below). + */ + if (uio->uio_resid > 0 && base_offset > OFF_MAX - HAMMER_XBUFSIZE) { + hammer_done_transaction(&trans); + return (EFBIG); + } + if (uio->uio_resid > 0 && (td = uio->uio_td) != NULL && td->td_proc && base_offset > td->td_proc->p_rlimit[RLIMIT_FSIZE].rlim_cur) { hammer_done_transaction(&trans); @@ -2187,6 +2197,21 @@ hammer_start_transaction(&trans, hmp); error = 0; + /* + * DF-2999: Reject file lengths within HAMMER_XBUFSIZE of 2^63 + * (and negative lengths). nvtruncbuf()/nvnode_pager_setsize() + * round the truncation point up to the next 64K block in signed + * 64-bit arithmetic; for such lengths the round-up wraps to + * INT64_MIN and the page-unmap loop in nvnode_pager_setsize() + * iterates ~2^63 times while holding the vnode token and the + * vm_object lock (permanent, unkillable kernel livelock). + */ + if (vap->va_size != (off_t)VNOVAL && + (vap->va_size < 0 || vap->va_size > OFF_MAX - HAMMER_XBUFSIZE)) { + error = (vap->va_size < 0) ? EINVAL : EFBIG; + goto done; + } + if (vap->va_flags != VNOVAL) { flags = ip->ino_data.uflags; error = vop_helper_setattr_flags(&flags, vap->va_flags,