--- sys/vfs/ext2fs/ext2_vfsops.c +++ sys/vfs/ext2fs/ext2_vfsops.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -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) {