From: Ana Bobillo <[email protected]>

ext4fs_get_extent_block() walks the extent tree of an inode using the
eh_entries, eh_max and eh_depth fields of each node as found on disk,
checking nothing but the magic number. read_allocated_block() then
scans eh_entries extents of the leaf it returns.

A node can claim more entries than it can hold: up to 4 in the
60-byte i_block area of the inode, (blocksize - 12) / 12 in a tree
block. Both loops then read past the end of the node, into the rest
of the inode or past the cache buffer. An index node can also point
back at itself or at any node of the same or a greater depth, so the
walk never ends.

Check each node as Linux __ext4_ext_check() does: eh_max is non-zero
and fits in the node, eh_entries does not exceed eh_max, the depth
drops by exactly one at each level and an index node has at least one
entry; and, as ext4_find_extent() requires, the depth is at most 5.
A node that fails is reported as an invalid extent block, the error
read_allocated_block() already returns for a bad magic number.

Signed-off-by: Ana Bobillo <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
(no changes since v1)

 fs/ext4/ext4_common.c | 42 +++++++++++++++++++++++++++++++++++++++++-
 include/ext4fs.h      |  1 +
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/ext4_common.c b/fs/ext4/ext4_common.c
index 9e1d574e5b1..3aab1c87233 100644
--- a/fs/ext4/ext4_common.c
+++ b/fs/ext4/ext4_common.c
@@ -1529,6 +1529,38 @@ void ext4fs_allocate_blocks(struct ext2_inode 
*file_inode,
 
 #endif
 
+/**
+ * ext4fs_check_extent_header() - check an extent tree node before using it
+ *
+ * Some of the checks of Linux __ext4_ext_check() and ext4_find_extent(): the
+ * node must not claim more entries than fit in it, and the depth must be at
+ * most 5 and drop by one at each level, so that a corrupt node can neither be
+ * walked past its end nor make the tree loop.
+ *
+ * @eh:                extent header of the node
+ * @max:       number of entries that fit in the node
+ * @depth:     depth this node must have, or -1 for the root in the inode
+ * Return:     0 if the node is valid, -EINVAL otherwise
+ */
+static int ext4fs_check_extent_header(struct ext4_extent_header *eh, int max,
+                                     int depth)
+{
+       if (le16_to_cpu(eh->eh_magic) != EXT4_EXT_MAGIC)
+               return -EINVAL;
+       if (!eh->eh_max || le16_to_cpu(eh->eh_max) > max)
+               return -EINVAL;
+       if (le16_to_cpu(eh->eh_entries) > le16_to_cpu(eh->eh_max))
+               return -EINVAL;
+       if (le16_to_cpu(eh->eh_depth) > EXT4_MAX_EXTENT_DEPTH)
+               return -EINVAL;
+       if (depth >= 0 && le16_to_cpu(eh->eh_depth) != depth)
+               return -EINVAL;
+       if (eh->eh_depth && !eh->eh_entries)
+               return -EINVAL;
+
+       return 0;
+}
+
 static struct ext4_extent_header *ext4fs_get_extent_block
        (struct ext2_data *data, struct ext_block_cache *cache,
                struct ext4_extent_header *ext_block,
@@ -1537,16 +1569,24 @@ static struct ext4_extent_header 
*ext4fs_get_extent_block
        struct ext4_extent_idx *index;
        unsigned long long block;
        int blksz = EXT2_BLOCK_SIZE(data);
+       /* The root node is held in the inode's 60-byte i_block area */
+       int max = (sizeof(((struct ext2_inode *)0)->b) -
+                  sizeof(struct ext4_extent_header)) /
+                 sizeof(struct ext4_extent);
+       int depth = -1;
        int i;
 
        while (1) {
                index = (struct ext4_extent_idx *)(ext_block + 1);
 
-               if (le16_to_cpu(ext_block->eh_magic) != EXT4_EXT_MAGIC)
+               if (ext4fs_check_extent_header(ext_block, max, depth))
                        return NULL;
 
                if (ext_block->eh_depth == 0)
                        return ext_block;
+               depth = le16_to_cpu(ext_block->eh_depth) - 1;
+               max = (blksz - sizeof(struct ext4_extent_header)) /
+                     sizeof(struct ext4_extent);
                i = -1;
                do {
                        i++;
diff --git a/include/ext4fs.h b/include/ext4fs.h
index fe3fb301ec8..19eda150cb6 100644
--- a/include/ext4fs.h
+++ b/include/ext4fs.h
@@ -35,6 +35,7 @@ struct disk_partition;
 #define EXT4_TOPDIR_FL         0x00020000 /* Top of directory hierarchies*/
 #define EXT4_EXTENTS_FL                0x00080000 /* Inode uses extents */
 #define EXT4_EXT_MAGIC                 0xf30a
+#define EXT4_MAX_EXTENT_DEPTH          5
 
 #define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER  0x0001
 #define EXT4_FEATURE_RO_COMPAT_LARGE_FILE    0x0002

Reply via email to