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
