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
This commit is contained in:
Fedor Uporov 2019-03-04 10:42:25 +00:00
parent 647915ff20
commit 6e38bf94e5
4 changed files with 218 additions and 66 deletions

View File

@ -457,7 +457,7 @@ printf("ext2_valloc: allocated inode %d\n", ino);
/*
* 64-bit compatible getters and setters for struct ext2_gd from ext2fs.h
*/
static uint64_t
uint64_t
e2fs_gd_get_b_bitmap(struct ext2_gd *gd)
{
@ -465,7 +465,7 @@ e2fs_gd_get_b_bitmap(struct ext2_gd *gd)
gd->ext2bgd_b_bitmap);
}
static uint64_t
uint64_t
e2fs_gd_get_i_bitmap(struct ext2_gd *gd)
{
@ -754,7 +754,7 @@ ext2_hashalloc(struct inode *ip, int cg, long pref, int size,
return (0);
}
static unsigned long
static uint64_t
ext2_cg_number_gdb_nometa(struct m_ext2fs *fs, int cg)
{
@ -768,7 +768,7 @@ ext2_cg_number_gdb_nometa(struct m_ext2fs *fs, int cg)
EXT2_DESCS_PER_BLOCK(fs));
}
static unsigned long
static uint64_t
ext2_cg_number_gdb_meta(struct m_ext2fs *fs, int cg)
{
unsigned long metagroup;
@ -784,7 +784,7 @@ ext2_cg_number_gdb_meta(struct m_ext2fs *fs, int cg)
return (0);
}
static unsigned long
uint64_t
ext2_cg_number_gdb(struct m_ext2fs *fs, int cg)
{
unsigned long first_meta_bg, metagroup;

View File

@ -91,6 +91,7 @@ int ext2_dirrewrite(struct inode *,
int ext2_dirempty(struct inode *, ino_t, struct ucred *);
int ext2_checkpath(struct inode *, struct inode *, struct ucred *);
int ext2_cg_has_sb(struct m_ext2fs *fs, int cg);
uint64_t ext2_cg_number_gdb(struct m_ext2fs *fs, int cg);
int ext2_inactive(struct vop_inactive_args *);
int ext2_htree_add_entry(struct vnode *, struct ext2fs_direct_2 *,
struct componentname *);
@ -104,6 +105,8 @@ int ext2_htree_lookup(struct inode *, const char *, int, struct buf **,
int ext2_search_dirblock(struct inode *, void *, int *, const char *, int,
int *, doff_t *, doff_t *, doff_t *, struct ext2fs_searchslot *);
uint32_t e2fs_gd_get_ndirs(struct ext2_gd *gd);
uint64_t e2fs_gd_get_b_bitmap(struct ext2_gd *);
uint64_t e2fs_gd_get_i_bitmap(struct ext2_gd *);
uint64_t e2fs_gd_get_i_tables(struct ext2_gd *);
void ext2_sb_csum_set_seed(struct m_ext2fs *);
int ext2_sb_csum_verify(struct m_ext2fs *);

View File

@ -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);
es->e3fs_desc_size != E2FS_64BIT_GD_SIZE) {
printf("ext2fs: unsupported 64bit descriptor size %u\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);
}
/* 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[

View File

@ -395,6 +395,7 @@ struct ext2_gd {
};
#define E2FS_REV0_GD_SIZE (sizeof(struct ext2_gd) / 2)
#define E2FS_64BIT_GD_SIZE (sizeof(struct ext2_gd))
/*
* Macro-instructions used to manage several block sizes
@ -408,8 +409,8 @@ struct ext2_gd {
* Macro-instructions used to manage fragments
*/
#define EXT2_MIN_FRAG_SIZE 1024
#define EXT2_MAX_FRAG_SIZE 4096
#define EXT2_MIN_FRAG_LOG_SIZE 10
#define EXT2_MIN_FRAG_LOG_SIZE 10
#define EXT2_MAX_FRAG_LOG_SIZE 30
#define EXT2_FRAG_SIZE(s) (EXT2_SB(s)->e2fs_fsize)
#define EXT2_FRAGS_PER_BLOCK(s) (EXT2_SB(s)->e2fs_fpb)