DF-2921 / fix.diff
--- a/sys/kern/vfs_vm.c +++ b/sys/kern/vfs_vm.c @@ -145,9 +145,18 @@ */ if (boff < 0) boff = (int)(length % blksize); - if (boff) - info.truncloffset = length + (blksize - boff); - else + if (boff) { + /* + * Clamp: when the truncation point is within (blksize - boff) + * of OFF_MAX the round-up would overflow a signed off_t and + * wrap negative, which makes the RB_SCAN compare match every + * buffer on the vnode (including metadata buffers). + */ + if (length > OFF_MAX - (blksize - boff)) + info.truncloffset = OFF_MAX; + else + info.truncloffset = length + (blksize - boff); + } else info.truncloffset = length; info.vp = vp; lwkt_gettoken(&vp->v_token); @@ -460,10 +469,20 @@ boff = (int)(length % blksize); truncboffset = length - boff; oobjsize = object->size; - if (boff) - nobjsize = OFF_TO_IDX(truncboffset + blksize + PAGE_MASK); - else - nobjsize = OFF_TO_IDX(truncboffset + PAGE_MASK); + if (boff) { + /* + * Do the rounding in unsigned 64-bit math. For lengths + * within blksize of 2^63 the signed expression overflows, + * arithmetic-shifts negative, and produces a vm_pindex_t of + * ~0xFFF8000000000000. The unmap loop below then iterates + * ~2^63 times while holding the vnode token and object lock, + * permanently wedging the cpu and the vnode (unkillable). + */ + nobjsize = ((vm_pindex_t)truncboffset + + (vm_pindex_t)blksize + PAGE_MASK) >> PAGE_SHIFT; + } else + nobjsize = ((vm_pindex_t)truncboffset + PAGE_MASK) >> + PAGE_SHIFT; object->size = nobjsize; if (length < vp->v_filesize) { @@ -483,7 +502,7 @@ * The pages remain part of the (last) buffer and are not * invalidated. */ - pi = OFF_TO_IDX(length + PAGE_MASK); + pi = ((vm_pindex_t)length + PAGE_MASK) >> PAGE_SHIFT; while (pi < nobjsize) { m = vm_page_lookup_busy_wait(object, pi, FALSE, "vmpg"); if (m) { |