The branch main has been updated by fsu:

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

commit f1d5e2c862ef599efd1705b04d505d5415b77f82
Author:     Fedor Uporov <f...@freebsd.org>
AuthorDate: 2021-12-24 14:18:15 +0000
Commit:     Fedor Uporov <f...@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) {

Reply via email to