DragonFlyBSD Kernel Audit
DF-2921 / fix.diff
← back to finding ↓ download raw
--- 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) {