DragonFlyBSD Kernel Audit
DF-0888 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/ext2fs/ext2_inode.c b/sys/vfs/ext2fs/ext2_inode.c
index 0000000..1111111 100644
--- a/sys/vfs/ext2fs/ext2_inode.c
+++ b/sys/vfs/ext2fs/ext2_inode.c
@@ -138,6 +138,14 @@
 	last = lastbn;
 	if (lastbn > 0)
 		last /= factor;
+	/*
+	 * Defense-in-depth: the caller (ext2_ind_truncate) must reject lengths
+	 * beyond the indirect-block scheme's capacity, but never let `last'
+	 * index past bap[NINDR-1] -- otherwise &bap[last+1] and the bzero size
+	 * (NINDR-(last+1))*sizeof would go out of bounds / underflow.
+	 */
+	if (last >= NINDIR(fs))
+		last = NINDIR(fs) - 1;
 	nblocks = btodb(fs->e2fs_bsize);
 	/*
 	 * Get buffer of block pointers, zero those entries corresponding
@@ -466,6 +474,25 @@
 		return (EINVAL);
 
 	ip = VTOI(vp);
+	/*
+	 * Reject lengths beyond the maximum size addressable by the inode's
+	 * block-mapping scheme. ext2's e2fs_maxfilesize is set to INT64_MAX
+	 * under HUGE_FILE (sys/vfs/ext2fs/ext2_vfsops.c:703) and does NOT
+	 * reflect the actual capacity of the classic indirect-block scheme
+	 * (12 direct + NINDR + NINDR^2 + NINDR^3). Truncating to a size the
+	 * scheme cannot represent would overflow lastiblock[TRIPLE] in
+	 * ext2_ind_truncate and the index/size math in ext2_indirtrunc,
+	 * causing a heap OOB write in its bzero(). Bound length here.
+	 */
+	if (vp->v_type == VREG && (ip->i_flag & IN_E4EXTENTS) == 0) {
+		struct m_ext2fs *fs = ip->i_e2fs;
+		off_t nindir = NINDIR(fs);
+		off_t maxaddr = EXT2_NDADDR
+		    + nindir + nindir * nindir + nindir * nindir * nindir;
+		off_t maxsize = maxaddr * fs->e2fs_bsize;
+		if (length > maxsize)
+			return (EFBIG);
+	}
 	if (vp->v_type == VLNK &&
 	    ip->i_size < vp->v_mount->mnt_maxsymlinklen) {
 #ifdef INVARIANTS