git: f1d5e2c862ef - main - Improve extents verification logic

From: Fedor Uporov <fsu_at_FreeBSD.org>
Date: Thu, 30 Dec 2021 06:15:20 UTC
The branch main has been updated by fsu:

URL: https://cgit.FreeBSD.org/src/commit/?id=f1d5e2c862ef599efd1705b04d505d5415b77f82

commit f1d5e2c862ef599efd1705b04d505d5415b77f82
Author:     Fedor Uporov <fsu@FreeBSD.org>
AuthorDate: 2021-12-24 14:18:15 +0000
Commit:     Fedor Uporov <fsu@FreeBSD.org>
CommitDate: 2021-12-30 06:14:45 +0000

    Improve extents verification logic
    
    Add functionality for extents validation inside the filesystem
    extents block. The main logic is implemented under
    ext4_validate_extent_entries() function, which verifies extents
    or extents indexes depending of extent depth value.
    
    PR:                     259112
    Reported by:            Robert Morris
    Reviewed by:            pfg
    MFC after:              2 weeks
    Differential Revision:  https://reviews.freebsd.org/D33375
---
 sys/fs/ext2fs/ext2_extents.c   | 291 +++++++++++++++++++++++++++++++----------
 sys/fs/ext2fs/ext2_inode_cnv.c |   8 +-
 2 files changed, 224 insertions(+), 75 deletions(-)

diff --git a/sys/fs/ext2fs/ext2_extents.c b/sys/fs/ext2fs/ext2_extents.c
index 3d393c4b7bbe..7aac71cf3fbc 100644
--- a/sys/fs/ext2fs/ext2_extents.c
+++ b/sys/fs/ext2fs/ext2_extents.c
@@ -61,8 +61,10 @@ static MALLOC_DEFINE(M_EXT2EXTENTS, "ext2_extents", "EXT2 extents");
 #ifdef EXT2FS_PRINT_EXTENTS
 static const bool print_extents_walk = true;
 
-static int ext4_ext_check_header(struct inode *, struct ext4_extent_header *);
-static int ext4_ext_walk_header(struct inode *, struct ext4_extent_header *);
+static int ext4_ext_check_header(struct inode *, struct ext4_extent_header *,
+    int);
+static int ext4_ext_walk_header(struct inode *, struct ext4_extent_header *,
+    int);
 static inline e4fs_daddr_t ext4_ext_index_pblock(struct ext4_extent_index *);
 static inline e4fs_daddr_t ext4_ext_extent_pblock(struct ext4_extent *);
 
@@ -80,7 +82,8 @@ ext4_ext_blk_check(struct inode *ip, e4fs_daddr_t blk)
 }
 
 static int
-ext4_ext_walk_index(struct inode *ip, struct ext4_extent_index *ex, bool do_walk)
+ext4_ext_walk_index(struct inode *ip, struct ext4_extent_index *ex, int depth,
+    bool do_walk)
 {
 	struct m_ext2fs *fs;
 	struct buf *bp;
@@ -91,7 +94,8 @@ ext4_ext_walk_index(struct inode *ip, struct ext4_extent_index *ex, bool do_walk
 
 	if (print_extents_walk)
 		printf("    index %p => (blk %u pblk %ju)\n", ex,
-		    le32toh(ex->ei_blk), (uint64_t)le16toh(ex->ei_leaf_hi) << 32 |
+		    le32toh(ex->ei_blk),
+		    (uint64_t)le16toh(ex->ei_leaf_hi) << 32 |
 		    le32toh(ex->ei_leaf_lo));
 
 	if(!do_walk)
@@ -108,7 +112,8 @@ ext4_ext_walk_index(struct inode *ip, struct ext4_extent_index *ex, bool do_walk
 		return (error);
 	}
 
-	error = ext4_ext_walk_header(ip, (struct ext4_extent_header *)bp->b_data);
+	error = ext4_ext_walk_header(ip,
+	    (struct ext4_extent_header *)bp->b_data, depth);
 
 	brelse(bp);
 
@@ -135,42 +140,62 @@ ext4_ext_walk_extent(struct inode *ip, struct ext4_extent *ep)
 }
 
 static int
-ext4_ext_walk_header(struct inode *ip, struct ext4_extent_header *eh)
+ext4_ext_walk_header(struct inode *ip, struct ext4_extent_header *eh, int depth)
 {
 	int i, error = 0;
 
-	error = ext4_ext_check_header(ip, eh);
+	error = ext4_ext_check_header(ip, eh, depth);
 	if (error)
 		return (error);
 
 	if (print_extents_walk)
 		printf("header %p => (entries %d max %d depth %d gen %d)\n",
 		    eh, le16toh(eh->eh_ecount),
-		    le16toh(eh->eh_max), le16toh(eh->eh_depth), le32toh(eh->eh_gen));
+		    le16toh(eh->eh_max), le16toh(eh->eh_depth),
+		    le32toh(eh->eh_gen));
 
 	for (i = 0; i < le16toh(eh->eh_ecount) && error == 0; i++)
 		if (eh->eh_depth != 0)
 			error = ext4_ext_walk_index(ip,
-			    (struct ext4_extent_index *)(eh + 1 + i), true);
+			    (struct ext4_extent_index *)(eh + 1 + i), depth - 1,
+			    true);
 		else
-			error = ext4_ext_walk_extent(ip, (struct ext4_extent *)(eh + 1 + i));
+			error = ext4_ext_walk_extent(ip,
+			    (struct ext4_extent *)(eh + 1 + i));
 
 	return (error);
 }
 
+int
+ext4_ext_walk(struct inode *ip)
+{
+	struct ext4_extent_header *ehp;
+
+	ehp = (struct ext4_extent_header *)ip->i_db;
+
+	if (print_extents_walk)
+		printf("Extent status:ip=%ju\n", ip->i_number);
+
+	if (!(ip->i_flag & IN_E4EXTENTS))
+		return (0);
+
+	return (ext4_ext_walk_header(ip, ehp, 0));
+}
+
 static int
 ext4_ext_print_path(struct inode *ip, struct ext4_extent_path *path)
 {
-	int k, l, error = 0;
+	int k, depth, error = 0;
 
-	l = path->ep_depth;
+	depth = path->ep_depth;
 
 	if (print_extents_walk)
 		printf("ip=%ju, Path:\n", ip->i_number);
 
-	for (k = 0; k <= l && error == 0; k++, path++) {
+	for (k = 0; k <= depth && error == 0; k++, path++) {
 		if (path->ep_index) {
-			error = ext4_ext_walk_index(ip, path->ep_index, false);
+			error = ext4_ext_walk_index(ip, path->ep_index,
+			    depth - 1, false);
 		} else if (path->ep_ext) {
 			error = ext4_ext_walk_extent(ip, path->ep_ext);
 		}
@@ -178,22 +203,6 @@ ext4_ext_print_path(struct inode *ip, struct ext4_extent_path *path)
 
 	return (error);
 }
-
-int
-ext4_ext_walk(struct inode *ip)
-{
-	struct ext4_extent_header *ehp;
-
-	ehp = (struct ext4_extent_header *)ip->i_db;
-
-	if (print_extents_walk)
-		printf("Extent status:ip=%ju\n", ip->i_number);
-
-	if (!(ip->i_flag & IN_E4EXTENTS))
-		return (0);
-
-	return (ext4_ext_walk_header(ip, ehp));
-}
 #endif
 
 static inline struct ext4_extent_header *
@@ -277,8 +286,168 @@ ext4_ext_in_cache(struct inode *ip, daddr_t lbn, struct ext4_extent *ep)
 	return (ret);
 }
 
+static inline int
+ext4_ext_space_root(struct inode *ip)
+{
+	int size;
+
+	size = sizeof(ip->i_data);
+	size -= sizeof(struct ext4_extent_header);
+	size /= sizeof(struct ext4_extent);
+
+	return (size);
+}
+
+static inline int
+ext4_ext_space_block(struct inode *ip)
+{
+	struct m_ext2fs *fs;
+	int size;
+
+	fs = ip->i_e2fs;
+
+	size = (fs->e2fs_bsize - sizeof(struct ext4_extent_header)) /
+	    sizeof(struct ext4_extent);
+
+	return (size);
+}
+
+static inline int
+ext4_ext_space_root_idx(struct inode *ip)
+{
+	int size;
+
+	size = sizeof(ip->i_data);
+	size -= sizeof(struct ext4_extent_header);
+	size /= sizeof(struct ext4_extent_index);
+
+	return (size);
+}
+
+static inline int
+ext4_ext_space_block_idx(struct inode *ip)
+{
+	struct m_ext2fs *fs;
+	int size;
+
+	fs = ip->i_e2fs;
+
+	size = (fs->e2fs_bsize - sizeof(struct ext4_extent_header)) /
+	    sizeof(struct ext4_extent_index);
+
+	return (size);
+}
+
 static int
-ext4_ext_check_header(struct inode *ip, struct ext4_extent_header *eh)
+ext4_ext_max_entries(struct inode *ip, int depth)
+{
+
+	if (depth == ext4_ext_inode_depth(ip)) {
+		if (depth == 0)
+			return (ext4_ext_space_root(ip));
+		else
+			return (ext4_ext_space_root_idx(ip));
+	} else {
+		if (depth == 0)
+			return (ext4_ext_space_block(ip));
+		else
+			return (ext4_ext_space_block_idx(ip));
+	}
+}
+
+static inline uint16_t
+ext4_ext_get_actual_len(struct ext4_extent *ext)
+{
+
+	return (le16toh(ext->e_len) <= EXT_INIT_MAX_LEN ?
+	    le16toh(ext->e_len) : (le16toh(ext->e_len) - EXT_INIT_MAX_LEN));
+}
+
+
+static int
+ext4_inode_block_validate(struct inode *ip, e4fs_daddr_t start_blk,
+    unsigned int count)
+{
+	struct m_ext2fs *fs;
+
+	fs = ip->i_e2fs;
+
+	if ((start_blk <= le32toh(fs->e2fs->e2fs_first_dblock)) ||
+	    (start_blk + count < start_blk) ||
+	    (start_blk + count > fs->e2fs_bcount))
+		return (EIO);
+
+	return (0);
+}
+
+static int
+ext4_validate_extent(struct inode *ip, struct ext4_extent *ext)
+{
+	e4fs_daddr_t blk = ext4_ext_extent_pblock(ext);
+	uint32_t lblk = le32toh(ext->e_blk);
+	int len = ext4_ext_get_actual_len(ext);
+
+	if (lblk + len <= lblk)
+		return (EIO);
+
+	return (ext4_inode_block_validate(ip, blk, len));
+}
+
+static int
+ext4_validate_extent_idx(struct inode *ip, struct ext4_extent_index *ext_idx)
+{
+	e4fs_daddr_t blk = ext4_ext_index_pblock(ext_idx);
+
+	return (ext4_inode_block_validate(ip, blk, 1));
+}
+
+static int
+ext4_validate_extent_entries(struct inode *ip, struct ext4_extent_header *eh,
+    int depth)
+{
+	unsigned int count;
+
+	count = le16toh(eh->eh_ecount);
+	if (count == 0)
+		return (0);
+
+	if (depth == 0) {
+		struct ext4_extent *ext = EXT_FIRST_EXTENT(eh);
+		uint32_t lblk = 0;
+		uint32_t prev = 0;
+		int len = 0;
+		while (count) {
+			/* leaf entries */
+			if (ext4_validate_extent(ip, ext))
+				return (EIO);
+
+			/* Check for overlapping extents */
+			lblk = le32toh(ext->e_blk);
+			len = ext4_ext_get_actual_len(ext);
+			if ((lblk <= prev) && prev)
+				return (EIO);
+
+			ext++;
+			count--;
+			prev = lblk + len - 1;
+		}
+	} else {
+		struct ext4_extent_index *ext_idx = EXT_FIRST_INDEX(eh);
+		while (count) {
+			if (ext4_validate_extent_idx(ip, ext_idx))
+				return (EIO);
+
+			ext_idx++;
+			count--;
+		}
+	}
+
+	return (0);
+}
+
+static int
+ext4_ext_check_header(struct inode *ip, struct ext4_extent_header *eh,
+    int depth)
 {
 	char *error_msg;
 
@@ -286,18 +455,32 @@ ext4_ext_check_header(struct inode *ip, struct ext4_extent_header *eh)
 		error_msg = "header: invalid magic";
 		goto corrupted;
 	}
+	if (le16toh(eh->eh_depth) != depth ||
+	    le16toh(eh->eh_depth) > EXT4_EXT_DEPTH_MAX)
+	{
+		error_msg = "header: invalid eh_depth";
+		goto corrupted;
+	}
 	if (eh->eh_max == 0) {
 		error_msg = "header: invalid eh_max";
 		goto corrupted;
 	}
+	if (le16toh(eh->eh_max) > ext4_ext_max_entries(ip, depth)) {
+		error_msg = "header: too large eh_max";
+		goto corrupted;
+	}
 	if (le16toh(eh->eh_ecount) > le16toh(eh->eh_max)) {
 		error_msg = "header: invalid eh_entries";
 		goto corrupted;
 	}
-	if (eh->eh_depth > 5) {
+	if (le16toh(eh->eh_depth) > EXT4_EXT_DEPTH_MAX) {
 		error_msg = "header: invalid eh_depth";
 		goto corrupted;
 	}
+	if (ext4_validate_extent_entries(ip, eh, depth)) {
+		error_msg = "header: invalid extent entries";
+		goto corrupted;
+	}
 
 	return (0);
 
@@ -426,7 +609,7 @@ ext4_ext_find_extent(struct inode *ip, daddr_t block,
 	ppos = 0;
 	alloc = 0;
 
-	error = ext4_ext_check_header(ip, eh);
+	error = ext4_ext_check_header(ip, eh, depth);
 	if (error)
 		return (error);
 
@@ -472,7 +655,7 @@ ext4_ext_find_extent(struct inode *ip, daddr_t block,
 		bqrelse(bp);
 
 		eh = ext4_ext_block_header(path[ppos].ep_data);
-		if (ext4_ext_check_header(ip, eh) ||
+		if (ext4_ext_check_header(ip, eh, i - 1) ||
 		    ext2_extent_blk_csum_verify(ip, path[ppos].ep_data)) {
 			error = EIO;
 			goto error;
@@ -483,7 +666,7 @@ ext4_ext_find_extent(struct inode *ip, daddr_t block,
 		i--;
 	}
 
-	error = ext4_ext_check_header(ip, eh);
+	error = ext4_ext_check_header(ip, eh, 0);
 	if (error)
 		goto error;
 
@@ -505,32 +688,6 @@ error:
 	return (error);
 }
 
-static inline int
-ext4_ext_space_root(struct inode *ip)
-{
-	int size;
-
-	size = sizeof(ip->i_data);
-	size -= sizeof(struct ext4_extent_header);
-	size /= sizeof(struct ext4_extent);
-
-	return (size);
-}
-
-static inline int
-ext4_ext_space_block(struct inode *ip)
-{
-	struct m_ext2fs *fs;
-	int size;
-
-	fs = ip->i_e2fs;
-
-	size = (fs->e2fs_bsize - sizeof(struct ext4_extent_header)) /
-	    sizeof(struct ext4_extent);
-
-	return (size);
-}
-
 static inline int
 ext4_ext_space_block_index(struct inode *ip)
 {
@@ -1339,14 +1496,6 @@ out2:
 	return (error);
 }
 
-static inline uint16_t
-ext4_ext_get_actual_len(struct ext4_extent *ext)
-{
-
-	return (le16toh(ext->e_len) <= EXT_INIT_MAX_LEN ?
-	    le16toh(ext->e_len) : (le16toh(ext->e_len) - EXT_INIT_MAX_LEN));
-}
-
 static inline struct ext4_extent_header *
 ext4_ext_header(struct inode *ip)
 {
@@ -1503,7 +1652,7 @@ ext4_read_extent_tree_block(struct inode *ip, e4fs_daddr_t pblk,
 		goto err;
 	}
 
-	error = ext4_ext_check_header(ip, eh);
+	error = ext4_ext_check_header(ip, eh, depth);
 	if (error)
 		goto err;
 
@@ -1544,7 +1693,7 @@ ext4_ext_remove_space(struct inode *ip, off_t length, int flags,
 	ehp = (struct ext4_extent_header *)ip->i_db;
 	depth = ext4_ext_inode_depth(ip);
 
-	error = ext4_ext_check_header(ip, ehp);
+	error = ext4_ext_check_header(ip, ehp, depth);
 	if(error)
 		return (error);
 
diff --git a/sys/fs/ext2fs/ext2_inode_cnv.c b/sys/fs/ext2fs/ext2_inode_cnv.c
index 445000c509db..c5f7d0657e36 100644
--- a/sys/fs/ext2fs/ext2_inode_cnv.c
+++ b/sys/fs/ext2fs/ext2_inode_cnv.c
@@ -65,11 +65,11 @@ ext2_print_inode(struct inode *in)
 	    in->i_uid, in->i_gid, (uintmax_t)in->i_size);
 	printf("Links: %3d Blockcount: %ju\n",
 	    in->i_nlink, (uintmax_t)in->i_blocks);
-	printf("ctime: 0x%x ", in->i_ctime);
-	printf("atime: 0x%x ", in->i_atime);
-	printf("mtime: 0x%x ", in->i_mtime);
+	printf("ctime: 0x%llx ", (unsigned long long)in->i_ctime);
+	printf("atime: 0x%llx ", (unsigned long long)in->i_atime);
+	printf("mtime: 0x%llx ", (unsigned long long)in->i_mtime);
 	if (E2DI_HAS_XTIME(in))
-		printf("crtime %#x\n", in->i_birthtime);
+		printf("crtime %llx\n", (unsigned long long)in->i_birthtime);
 	else
 		printf("\n");
 	if (in->i_flag & IN_E4EXTENTS) {