⬢ DragonFlyBSD Kernel Audit
DF-3172 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/ufs/ffs_vfsops.c	2026-09-07 17:48:32.085811365 +0000
+++ b/sys/vfs/ufs/ffs_vfsops.c	2026-09-07 17:48:32.105811105 +0000
@@ -644,6 +644,17 @@
 		error = EINVAL;		/* XXX needs translation */
 		goto out;
 	}
+	/*
+	 * fs_nindir must describe the actual capacity of an indirect
+	 * block.  It is untrusted media data; a crafted (or re-read)
+	 * value poisons every NINDIR(fs) consumer: ufs_getlbns offset
+	 * math (ffs_balloc OOB reads), ffs_truncate lastiblock levels,
+	 * and the ffs_indirtrunc bzero/scan bounds.
+	 */
+	if (fs->fs_nindir != fs->fs_bsize / sizeof(ufs_daddr_t)) {
+		error = EINVAL;		/* crafted geometry */
+		goto out;
+	}
 	fs->fs_fmod = 0;
 	fs->fs_flags &= ~FS_UNCLEAN;
 	if (fs->fs_clean == 0) {
@@ -754,6 +765,25 @@
 	/* Enforce limit caused by vm object backing (32 bits vm_pindex_t). */
 	if (maxfilesize > (uint64_t)0x80000000u * PAGE_SIZE - 1)
 		maxfilesize = (uint64_t)0x80000000u * PAGE_SIZE - 1;
+	/*
+	 * Also cap maxfilesize at what the NDADDR + 3-level indirect
+	 * tree can actually address (same formula newfs/mkfs.c uses).
+	 * Otherwise lbn arithmetic in ffs_truncate can exceed the last
+	 * representable block of a level and ffs_indirtrunc computes an
+	 * out-of-range index for its bzero()/scan of the indirect block.
+	 */
+	{
+		uint64_t sizepb = fs->fs_bsize;
+		uint64_t representable = fs->fs_bsize * UFS_NDADDR - 1;
+		int j;
+
+		for (j = 0; j < UFS_NIADDR; j++) {
+			sizepb *= NINDIR(fs);
+			representable += sizepb;
+		}
+		if (maxfilesize > representable)
+			maxfilesize = representable;
+	}
 	if (fs->fs_maxfilesize > maxfilesize)			/* XXX */
 		fs->fs_maxfilesize = maxfilesize;		/* XXX */
 	if (ronly == 0) {
--- a/sys/vfs/ufs/ffs_inode.c	2026-09-07 17:48:32.085811365 +0000
+++ b/sys/vfs/ufs/ffs_inode.c	2026-09-07 17:48:32.109811053 +0000
@@ -485,6 +485,15 @@
 	last = lastbn;
 	if (lastbn > 0)
 		last /= factor;
+	/*
+	 * Defense in depth: `last' is an index into bap[], which holds
+	 * NINDIR(fs) entries.  The caller derives lastbn from the
+	 * (media-controlled) new file size; if the superblock geometry
+	 * is inconsistent the division above can still exceed the range
+	 * of this level, driving the bzero() below out of bounds.
+	 */
+	if (last > NINDIR(fs) - 1)
+		last = NINDIR(fs) - 1;
 	nblocks = btodb(fs->fs_bsize);
 	/*
 	 * Get buffer of block pointers, zero those entries corresponding