DragonFlyBSD Kernel Audit
DF-0903 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/ufs/ffs_vfsops.c b/sys/vfs/ufs/ffs_vfsops.c
--- a/sys/vfs/ufs/ffs_vfsops.c
+++ b/sys/vfs/ufs/ffs_vfsops.c
@@ -644,6 +644,27 @@
 		error = EINVAL;		/* XXX needs translation */
 		goto out;
 	}
+	/*
+	 * Validate the block/frag geometry used by the blkoff()/fragoff()/
+	 * blkroundup() macros (ufs/fs.h:487-517).  blkoff(fs, loc) is
+	 * `loc & fs_qbmask' and must yield a value in [0, fs_bsize).  This
+	 * holds iff fs_qbmask == fs_bsize - 1 (and fs_bsize is a power of 2,
+	 * which the power-of-two check below enforces).  fs_qbmask is loaded
+	 * verbatim from disk for modern filesystems (ffs_oldfscompat only
+	 * re-derives it when fs_inodefmt < FS_44INODEFMT; newfs sets =2), so
+	 * a crafted fs_qbmask larger than fs_bsize-1 makes blkoff() exceed
+	 * fs_bsize and drives xfersize = fs_bsize - blkoffset negative in
+	 * ffs_write (ufs_readwrite.c:293), panicking the kernel in ffs_balloc.
+	 * Reject the mount instead of trusting on-disk mask values.
+	 */
+	if (fs->fs_fsize <= 0 || fs->fs_fsize > fs->fs_bsize ||
+	    (fs->fs_bsize & (fs->fs_bsize - 1)) != 0 ||
+	    (fs->fs_fsize & (fs->fs_fsize - 1)) != 0 ||
+	    fs->fs_qbmask != (int64_t)(fs->fs_bsize - 1) ||
+	    fs->fs_qfmask != (int64_t)(fs->fs_fsize - 1)) {
+		error = EINVAL;		/* malformed superblock geometry */
+		goto out;
+	}
 	fs->fs_fmod = 0;
 	fs->fs_flags &= ~FS_UNCLEAN;
 	if (fs->fs_clean == 0) {