aboutsummaryrefslogtreecommitdiff
path: root/sys/fs/ext2fs/ext2_vfsops.c
diff options
context:
space:
mode:
authorFedor Uporov <fsu@FreeBSD.org>2019-03-04 10:42:25 +0000
committerFedor Uporov <fsu@FreeBSD.org>2019-03-04 10:42:25 +0000
commit6e38bf94e5dae929958d4283a15a771c30aeab7a (patch)
tree04d01909c3cc73729a18607daebd9d3e4afabdde /sys/fs/ext2fs/ext2_vfsops.c
parent647915ff208d298bf644d5aca9de7d78e7958a51 (diff)
downloadsrc-6e38bf94e5dae929958d4283a15a771c30aeab7a.tar.gz
src-6e38bf94e5dae929958d4283a15a771c30aeab7a.zip
Make superblock reading logic more strict.
Add more on-disk superblock consistency checks to ext2_compute_sb_data() function. It should decrease the probability of mounting filesystems with corrupted superblock data. Reviewed by: pfg MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D19322
Notes
Notes: svn path=/head/; revision=344751
Diffstat (limited to 'sys/fs/ext2fs/ext2_vfsops.c')
-rw-r--r--sys/fs/ext2fs/ext2_vfsops.c266
1 files changed, 207 insertions, 59 deletions
diff --git a/sys/fs/ext2fs/ext2_vfsops.c b/sys/fs/ext2fs/ext2_vfsops.c
index cf74e9389a38..47a158403236 100644
--- a/sys/fs/ext2fs/ext2_vfsops.c
+++ b/sys/fs/ext2fs/ext2_vfsops.c
@@ -98,7 +98,7 @@ VFS_SET(ext2fs_vfsops, ext2fs, 0);
static int ext2_check_sb_compat(struct ext2fs *es, struct cdev *dev,
int ronly);
-static int compute_sb_data(struct vnode * devvp,
+static int ext2_compute_sb_data(struct vnode * devvp,
struct ext2fs * es, struct m_ext2fs * fs);
static const char *ext2_opts[] = { "acls", "async", "noatime", "noclusterr",
@@ -321,7 +321,7 @@ ext2_check_sb_compat(struct ext2fs *es, struct cdev *dev, int ronly)
}
static e4fs_daddr_t
-cg_location(struct m_ext2fs *fs, int number)
+ext2_cg_location(struct m_ext2fs *fs, int number)
{
int cg, descpb, logical_sb, has_super = 0;
@@ -350,80 +350,194 @@ cg_location(struct m_ext2fs *fs, int number)
fs->e2fs->e2fs_first_dblock);
}
+static int
+ext2_cg_validate(struct m_ext2fs *fs)
+{
+ uint64_t b_bitmap;
+ uint64_t i_bitmap;
+ uint64_t i_tables;
+ uint64_t first_block, last_block, last_cg_block;
+ struct ext2_gd *gd;
+ unsigned int i, cg_count;
+
+ first_block = fs->e2fs->e2fs_first_dblock;
+ last_cg_block = ext2_cg_number_gdb(fs, 0);
+ cg_count = fs->e2fs_gcount;
+
+ for (i = 0; i < fs->e2fs_gcount; i++) {
+ gd = &fs->e2fs_gd[i];
+
+ if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_FLEX_BG) ||
+ i == fs->e2fs_gcount - 1) {
+ last_block = fs->e2fs_bcount - 1;
+ } else {
+ last_block = first_block +
+ (EXT2_BLOCKS_PER_GROUP(fs) - 1);
+ }
+
+ if ((cg_count == fs->e2fs_gcount) &&
+ !(gd->ext4bgd_flags & EXT2_BG_INODE_ZEROED))
+ cg_count = i;
+
+ b_bitmap = e2fs_gd_get_b_bitmap(gd);
+ if (b_bitmap == 0) {
+ printf("ext2fs: cg %u: block bitmap is zero\n", i);
+ return (EINVAL);
+
+ }
+ if (b_bitmap <= last_cg_block) {
+ printf("ext2fs: cg %u: block bitmap overlaps gds\n", i);
+ return (EINVAL);
+ }
+ if (b_bitmap < first_block || b_bitmap > last_block) {
+ printf("ext2fs: cg %u: block bitmap not in group, blk=%ju\n",
+ i, b_bitmap);
+ return (EINVAL);
+ }
+
+ i_bitmap = e2fs_gd_get_i_bitmap(gd);
+ if (i_bitmap == 0) {
+ printf("ext2fs: cg %u: inode bitmap is zero\n", i);
+ return (EINVAL);
+ }
+ if (i_bitmap <= last_cg_block) {
+ printf("ext2fs: cg %u: inode bitmap overlaps gds\n", i);
+ return (EINVAL);
+ }
+ if (i_bitmap < first_block || i_bitmap > last_block) {
+ printf("ext2fs: cg %u: inode bitmap not in group blk=%ju\n",
+ i, i_bitmap);
+ return (EINVAL);
+ }
+
+ i_tables = e2fs_gd_get_i_tables(gd);
+ if (i_tables == 0) {
+ printf("ext2fs: cg %u: inode table is zero\n", i);
+ return (EINVAL);
+ }
+ if (i_tables <= last_cg_block) {
+ printf("ext2fs: cg %u: inode talbes overlaps gds\n", i);
+ return (EINVAL);
+ }
+ if (i_tables < first_block ||
+ i_tables + fs->e2fs_itpg - 1 > last_block) {
+ printf("ext2fs: cg %u: inode tables not in group blk=%ju\n",
+ i, i_tables);
+ return (EINVAL);
+ }
+
+ if (!EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_FLEX_BG))
+ first_block += EXT2_BLOCKS_PER_GROUP(fs);
+ }
+
+ return (0);
+}
+
/*
* This computes the fields of the m_ext2fs structure from the
* data in the ext2fs structure read in.
*/
static int
-compute_sb_data(struct vnode *devvp, struct ext2fs *es,
+ext2_compute_sb_data(struct vnode *devvp, struct ext2fs *es,
struct m_ext2fs *fs)
{
- int g_count = 0, error;
- int i, j;
struct buf *bp;
uint32_t e2fs_descpb, e2fs_gdbcount_alloc;
+ int i, j;
+ int g_count = 0;
+ int error;
- fs->e2fs_bcount = es->e2fs_bcount;
- fs->e2fs_rbcount = es->e2fs_rbcount;
- fs->e2fs_fbcount = es->e2fs_fbcount;
- if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT)) {
- fs->e2fs_bcount |= (uint64_t)(es->e4fs_bcount_hi) << 32;
- fs->e2fs_rbcount |= (uint64_t)(es->e4fs_rbcount_hi) << 32;
- fs->e2fs_fbcount |= (uint64_t)(es->e4fs_fbcount_hi) << 32;
+ /* Check checksum features */
+ if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_GDT_CSUM) &&
+ EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_METADATA_CKSUM)) {
+ printf("ext2fs: incorrect checksum features combination\n");
+ return (EINVAL);
+ }
+
+ /* Precompute checksum seed for all metadata */
+ ext2_sb_csum_set_seed(fs);
+
+ /* Verify sb csum if possible */
+ if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_METADATA_CKSUM)) {
+ error = ext2_sb_csum_verify(fs);
+ if (error) {
+ return (error);
+ }
}
+
+ /* Check for block size = 1K|2K|4K */
+ if (es->e2fs_log_bsize > 2) {
+ printf("ext2fs: bad block size: %d\n", es->e2fs_log_bsize);
+ return (EINVAL);
+ }
+
fs->e2fs_bshift = EXT2_MIN_BLOCK_LOG_SIZE + es->e2fs_log_bsize;
fs->e2fs_bsize = 1U << fs->e2fs_bshift;
fs->e2fs_fsbtodb = es->e2fs_log_bsize + 1;
fs->e2fs_qbmask = fs->e2fs_bsize - 1;
+
+ /* Check for fragment size */
+ if (es->e2fs_log_fsize >
+ (EXT2_MAX_FRAG_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE)) {
+ printf("ext2fs: invalid log cluster size: %u\n",
+ es->e2fs_log_fsize);
+ return (EINVAL);
+ }
+
fs->e2fs_fsize = EXT2_MIN_FRAG_SIZE << es->e2fs_log_fsize;
- if (fs->e2fs_fsize)
- fs->e2fs_fpb = fs->e2fs_bsize / fs->e2fs_fsize;
- fs->e2fs_bpg = es->e2fs_bpg;
- fs->e2fs_fpg = es->e2fs_fpg;
- fs->e2fs_ipg = es->e2fs_ipg;
+ if (fs->e2fs_fsize != fs->e2fs_bsize) {
+ printf("ext2fs: fragment size (%u) != block size %u\n",
+ fs->e2fs_fsize, fs->e2fs_bsize);
+ return (EINVAL);
+ }
+
+ fs->e2fs_fpb = fs->e2fs_bsize / fs->e2fs_fsize;
+
+ /* Check reserved gdt blocks for future filesystem expansion */
+ if (es->e2fs_reserved_ngdb > (fs->e2fs_bsize / 4)) {
+ printf("ext2fs: number of reserved GDT blocks too large: %u\n",
+ es->e2fs_reserved_ngdb);
+ return (EINVAL);
+ }
+
if (es->e2fs_rev == E2FS_REV0) {
fs->e2fs_isize = E2FS_REV0_INODE_SIZE;
} else {
fs->e2fs_isize = es->e2fs_inode_size;
/*
+ * Check first ino.
+ */
+ if (es->e2fs_first_ino < EXT2_FIRSTINO) {
+ printf("ext2fs: invalid first ino: %u\n",
+ es->e2fs_first_ino);
+ return (EINVAL);
+ }
+
+ /*
* Simple sanity check for superblock inode size value.
*/
if (EXT2_INODE_SIZE(fs) < E2FS_REV0_INODE_SIZE ||
EXT2_INODE_SIZE(fs) > fs->e2fs_bsize ||
(fs->e2fs_isize & (fs->e2fs_isize - 1)) != 0) {
- printf("ext2fs: invalid inode size %d\n",
+ printf("ext2fs: invalid inode size %u\n",
fs->e2fs_isize);
- return (EIO);
+ 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)) {
- printf("ext2fs: no space for extra inode timestamps\n");
- return (EINVAL);
- }
- /* Check checksum features */
- if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_GDT_CSUM) &&
- EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_METADATA_CKSUM)) {
- printf("ext2fs: incorrect checksum features combination\n");
- return (EINVAL);
- }
- /* Check for group descriptor size */
+
+ /* Check group descriptors */
if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT) &&
- (es->e3fs_desc_size != sizeof(struct ext2_gd))) {
- printf("ext2fs: group descriptor size unsupported %d\n",
- es->e3fs_desc_size);
- return (EINVAL);
- }
- /* Check for block size = 1K|2K|4K */
- if (es->e2fs_log_bsize > 2) {
- printf("ext2fs: bad block size: %d\n", es->e2fs_log_bsize);
- return (EINVAL);
+ es->e3fs_desc_size != E2FS_64BIT_GD_SIZE) {
+ printf("ext2fs: unsupported 64bit descriptor size %u\n",
+ es->e3fs_desc_size);
+ return (EINVAL);
}
- /* Check for group size */
- if (fs->e2fs_bpg == 0) {
- printf("ext2fs: zero blocks per group\n");
+
+ fs->e2fs_bpg = es->e2fs_bpg;
+ fs->e2fs_fpg = es->e2fs_fpg;
+ if (fs->e2fs_bpg == 0 || fs->e2fs_fpg == 0) {
+ printf("ext2fs: zero blocks/fragments per group\n");
return (EINVAL);
}
if (fs->e2fs_bpg != fs->e2fs_bsize * 8) {
@@ -433,26 +547,56 @@ compute_sb_data(struct vnode *devvp, struct ext2fs *es,
}
fs->e2fs_ipb = fs->e2fs_bsize / EXT2_INODE_SIZE(fs);
- if (fs->e2fs_ipg == 0) {
- printf("ext2fs: zero inodes per group\n");
+ if (fs->e2fs_ipb == 0 ||
+ fs->e2fs_ipb > fs->e2fs_bsize / E2FS_REV0_INODE_SIZE) {
+ printf("ext2fs: bad inodes per block size\n");
return (EINVAL);
}
- fs->e2fs_itpg = fs->e2fs_ipg / fs->e2fs_ipb;
- /* Check for block consistency */
- if (es->e2fs_first_dblock >= fs->e2fs_bcount) {
- printf("ext2fs: invalid first data block\n");
+
+ fs->e2fs_ipg = es->e2fs_ipg;
+ if (fs->e2fs_ipg < fs->e2fs_ipb || fs->e2fs_ipg > fs->e2fs_bsize * 8) {
+ printf("ext2fs: invalid inodes per group: %u\n", fs->e2fs_ipb);
return (EINVAL);
}
+
+ fs->e2fs_itpg = fs->e2fs_ipg / fs->e2fs_ipb;
+
+ fs->e2fs_bcount = es->e2fs_bcount;
+ fs->e2fs_rbcount = es->e2fs_rbcount;
+ fs->e2fs_fbcount = es->e2fs_fbcount;
+ if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT)) {
+ fs->e2fs_bcount |= (uint64_t)(es->e4fs_bcount_hi) << 32;
+ fs->e2fs_rbcount |= (uint64_t)(es->e4fs_rbcount_hi) << 32;
+ fs->e2fs_fbcount |= (uint64_t)(es->e4fs_fbcount_hi) << 32;
+ }
if (fs->e2fs_rbcount > fs->e2fs_bcount ||
fs->e2fs_fbcount > fs->e2fs_bcount) {
printf("ext2fs: invalid block count\n");
return (EINVAL);
}
- /* s_resuid / s_resgid ? */
+ if (es->e2fs_first_dblock >= fs->e2fs_bcount) {
+ printf("ext2fs: first data block out of range\n");
+ return (EINVAL);
+ }
+
fs->e2fs_gcount = howmany(fs->e2fs_bcount - es->e2fs_first_dblock,
EXT2_BLOCKS_PER_GROUP(fs));
+ if (fs->e2fs_gcount > ((uint64_t)1 << 32) - EXT2_DESCS_PER_BLOCK(fs)) {
+ printf("ext2fs: groups count too large: %u\n", fs->e2fs_gcount);
+ 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)) {
+ printf("ext2fs: no space for extra inode timestamps\n");
+ return (EINVAL);
+ }
+
+ /* s_resuid / s_resgid ? */
+
if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT)) {
- e2fs_descpb = fs->e2fs_bsize / sizeof(struct ext2_gd);
+ e2fs_descpb = fs->e2fs_bsize / E2FS_64BIT_GD_SIZE;
e2fs_gdbcount_alloc = howmany(fs->e2fs_gcount, e2fs_descpb);
} else {
e2fs_descpb = fs->e2fs_bsize / E2FS_REV0_GD_SIZE;
@@ -467,7 +611,7 @@ compute_sb_data(struct vnode *devvp, struct ext2fs *es,
for (i = 0; i < fs->e2fs_gdbcount; i++) {
error = bread(devvp,
- fsbtodb(fs, cg_location(fs, i)),
+ fsbtodb(fs, ext2_cg_location(fs, i)),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
free(fs->e2fs_contigdirs, M_EXT2MNT);
@@ -489,9 +633,13 @@ compute_sb_data(struct vnode *devvp, struct ext2fs *es,
brelse(bp);
bp = NULL;
}
- /* Precompute checksum seed for all metadata */
- ext2_sb_csum_set_seed(fs);
- /* Verfy cg csum */
+
+ /* Validate cgs consistency */
+ error = ext2_cg_validate(fs);
+ if (error)
+ return (error);
+
+ /* Verfy cgs csum */
if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_GDT_CSUM) ||
EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_METADATA_CKSUM)) {
error = ext2_gd_csum_verify(fs, devvp->v_rdev);
@@ -578,7 +726,7 @@ ext2_reload(struct mount *mp, struct thread *td)
fs = VFSTOEXT2(mp)->um_e2fs;
bcopy(bp->b_data, fs->e2fs, sizeof(struct ext2fs));
- if ((error = compute_sb_data(devvp, es, fs)) != 0) {
+ if ((error = ext2_compute_sb_data(devvp, es, fs)) != 0) {
brelse(bp);
return (error);
}
@@ -715,7 +863,7 @@ ext2_mountfs(struct vnode *devvp, struct mount *mp)
M_EXT2MNT, M_WAITOK);
mtx_init(EXT2_MTX(ump), "EXT2FS", "EXT2FS Lock", MTX_DEF);
bcopy(es, ump->um_e2fs->e2fs, (u_int)sizeof(struct ext2fs));
- if ((error = compute_sb_data(devvp, ump->um_e2fs->e2fs, ump->um_e2fs)))
+ if ((error = ext2_compute_sb_data(devvp, ump->um_e2fs->e2fs, ump->um_e2fs)))
goto out;
/*
@@ -1204,7 +1352,7 @@ ext2_cgupdate(struct ext2mount *mp, int waitfor)
for (i = 0; i < fs->e2fs_gdbcount; i++) {
bp = getblk(mp->um_devvp, fsbtodb(fs,
- cg_location(fs, i)),
+ ext2_cg_location(fs, i)),
fs->e2fs_bsize, 0, 0, 0);
if (EXT2_HAS_INCOMPAT_FEATURE(fs, EXT2F_INCOMPAT_64BIT)) {
memcpy(bp->b_data, &fs->e2fs_gd[