DragonFlyBSD Kernel Audit
DF-3047 / fix.diff
← back to finding ↓ download raw
--- sys/vfs/ext2fs/ext2_vfsops.c
+++ sys/vfs/ext2fs/ext2_vfsops.c
@@ -49,6 +49,7 @@
 #include <sys/bio.h>
 #include <sys/buf2.h>
 #include <sys/conf.h>
+#include <sys/disk.h>
 #include <sys/endian.h>
 #include <sys/fcntl.h>
 #include <sys/malloc.h>
@@ -545,6 +546,19 @@
 		}
 	}
 
+	/*
+	 * DF-3049: ext2_gd_csum() consumes s_desc_size as a read length
+	 * beyond offsetof(struct ext2_gd, ext4bgd_csum)+2 out of every
+	 * fs->e2fs_gd[i]; an unvalidated value reads past the end of the
+	 * group descriptor allocation.  No layout beyond the in-kernel
+	 * struct exists, so bound it by sizeof(struct ext2_gd).
+	 */
+	if (le16toh(es->e3fs_desc_size) > E2FS_64BIT_GD_SIZE) {
+		SDT_PROBE1(ext2fs, , vfsops, ext2_compute_sb_data_error,
+		    "invalid desc size");
+		return (EINVAL);
+	}
+
 	/* Check group descriptors */
 	if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT) &&
 	    le16toh(es->e3fs_desc_size) != E2FS_64BIT_GD_SIZE) {
@@ -625,6 +639,39 @@
 		return (EINVAL);
 	}
 
+	/*
+	 * DF-3048: ext2_vget()/ino_to_fsba() index
+	 * fs->e2fs_gd[(ino-1)/s_inodes_per_group] for any inode number up
+	 * to s_inodes_count (ext2_check_direntry, ext2_ei2i).  Require the
+	 * advertised inode count to fit inside the group descriptor table.
+	 */
+	if (le32toh(es->e2fs_icount) >
+	    (uint64_t)fs->e2fs_gcount * fs->e2fs_ipg) {
+		SDT_PROBE1(ext2fs, , vfsops, ext2_compute_sb_data_error,
+		    "inode count exceeds groups x inodes per group");
+		return (EINVAL);
+	}
+
+	/*
+	 * DF-3050: the geometry must fit on the medium.  Without this a
+	 * crafted superblock claiming ~2^45 blocks drives a ~256GB
+	 * kmalloc() (and a 16GB one) before any group descriptor is even
+	 * read, wedging the kernel in the page daemon.
+	 */
+	if (devvp->v_rdev != NULL && devvp->v_rdev->si_disk != NULL) {
+		uint64_t medblocks;
+
+		medblocks = devvp->v_rdev->si_disk->d_info.d_media_blocks *
+		    (uint64_t)devvp->v_rdev->si_disk->d_info.d_media_blksize /
+		    fs->e2fs_fsize;
+		if (fs->e2fs_bcount > medblocks) {
+			SDT_PROBE1(ext2fs, , vfsops,
+			    ext2_compute_sb_data_error,
+			    "filesystem larger than device");
+			return (EINVAL);
+		}
+	}
+
 	/* Check for extra isize in big inodes. */
 	if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_EXTRA_ISIZE) &&
 	    EXT2_INODE_SIZE(fs) < sizeof(struct ext2fs_dinode)) {
@@ -644,6 +691,14 @@
 		    fs->e2fs_bsize / sizeof(struct ext2_gd));
 	}
 	fs->e2fs_gdbcount = howmany(fs->e2fs_gcount, e2fs_descpb);
+	/*
+	 * DF-3047: on MNT_RELOAD this function runs against a live mount
+	 * whose previous allocations must be released, not leaked.
+	 */
+	if (fs->e2fs_gd != NULL)
+		free(fs->e2fs_gd, M_EXT2MNT);
+	if (fs->e2fs_contigdirs != NULL)
+		free(fs->e2fs_contigdirs, M_EXT2MNT);
 	fs->e2fs_gd = malloc(e2fs_gdbcount_alloc * fs->e2fs_bsize,
 	    M_EXT2MNT, M_WAITOK | M_ZERO);
 	fs->e2fs_contigdirs = malloc(fs->e2fs_gcount *
@@ -816,6 +871,34 @@
 		return (EIO);		/* XXX needs translation */
 	}
 	fs = VFSTOEXT2(mp)->um_e2fs;
+
+	/*
+	 * DF-3047: e2fs_maxcluster/e2fs_clustersum, um_nindir and every
+	 * inode already in core are sized from the mounted superblock.
+	 * ext2_compute_sb_data() re-derives fs->e2fs_gcount from the new
+	 * superblock, but the cluster summary arrays are never resized, so
+	 * step 3 below (and ext2_unmount) would run past their end.  Only
+	 * accept a reload whose geometry is identical.
+	 */
+	if (es->e2fs_bcount != fs->e2fs->e2fs_bcount ||
+	    es->e4fs_bcount_hi != fs->e2fs->e4fs_bcount_hi ||
+	    es->e2fs_log_bsize != fs->e2fs->e2fs_log_bsize ||
+	    es->e2fs_log_fsize != fs->e2fs->e2fs_log_fsize ||
+	    es->e2fs_bpg != fs->e2fs->e2fs_bpg ||
+	    es->e2fs_fpg != fs->e2fs->e2fs_fpg ||
+	    es->e2fs_ipg != fs->e2fs->e2fs_ipg ||
+	    es->e2fs_first_dblock != fs->e2fs->e2fs_first_dblock ||
+	    es->e2fs_icount != fs->e2fs->e2fs_icount ||
+	    es->e2fs_rev != fs->e2fs->e2fs_rev ||
+	    es->e2fs_inode_size != fs->e2fs->e2fs_inode_size ||
+	    es->e2fs_first_ino != fs->e2fs->e2fs_first_ino ||
+	    es->e2fs_features_compat != fs->e2fs->e2fs_features_compat ||
+	    es->e2fs_features_rocompat != fs->e2fs->e2fs_features_rocompat ||
+	    es->e2fs_features_incompat != fs->e2fs->e2fs_features_incompat ||
+	    es->e3fs_desc_size != fs->e2fs->e3fs_desc_size) {
+		brelse(bp);
+		return (EINVAL);
+	}
 	bcopy(bp->b_data, fs->e2fs, sizeof(struct ext2fs));
 
 	if ((error = ext2_compute_sb_data(devvp, es, fs)) != 0) {