The ext4 driver takes several indices from the filesystem image and uses them without checking them against the structures they index. A corrupt or crafted image can then make ext4load read past an extent tree node, or make ext4write (and saveenv with the environment on ext4) write outside the block bitmaps, the inode bitmaps and the group descriptor table when it replaces an existing file.
This series checks each of those indices where it is read: 1. extent tree nodes: entry count against the node's capacity, and the depth, as Linux does 2. the triple indirect lookup compares the cached block with the wrong index, read past the end of its buffer 3. the write path refuses block group layouts its bitmap code cannot handle, including zero blocks per group, which divides by zero 4. block numbers released by the delete path must lie inside the filesystem, and a failed read of an indirect block stops the delete 5. the block group of an inode is (n - 1) / inodes_per_group in the write path too, and inode numbers from directory entries are checked as Linux checks them 6. tests in test/py for each of the above With sandbox_defconfig and CONFIG_ASAN, the new tests pass with the series applied. On master the corrupt extent header is not rejected, the looping extent tree hangs ext4load, ext4fs_init() divides by zero, ASan reports segfaults in ext4fs_reset_block_bmap() and in ext4fs_reset_inode_bmap(), the journal's inode is deleted, and replacing the file in the last inode of a group fails with "no inode left to assign". The triple indirect test passes on master too: there the wrong compare reads past the buffer but seldom changes which block is read, so the test checks that blocks reached through the later entries of the triple indirect block still read back correctly. The existing ext4 and env tests still pass, and repeatedly overwriting files on clean images, with and without extents, with 1 KiB and 4 KiB blocks, keeps the images clean for e2fsck. Tom asked for the results of a CI run. This series, exactly as posted here, went through U-Boot's Azure pipeline on a pull request opened only for CI, together with three other series of mine that touch other files (block device block sizes, USB Ethernet rx headers and EFI capsule sizes), on top of v2027.01-rc1. Every job passed: https://github.com/u-boot/u-boot/pull/1064 https://dev.azure.com/u-boot/u-boot/_build/results?buildId=13698 Simon suggested that ext4fs_get_new_blk_no() could use ext4fs_blk_to_group() too. I would rather do that as a follow-up: there, running past the last group means that the filesystem is full, not that a block number is corrupt, so it needs a quiet check rather than the helper's error message. Changes in v2: - Add Simon's Reviewed-by tags to patches 1 to 3 - Use printf() for every error message, as the rest of the driver does - Patch 4: compute the block group from the first data block, as Linux does; check the reads of the indirect blocks; return -EIO for read and journal errors - Patch 5: add ext4fs_inode_to_group() and use it for every inode bitmap index; reject reserved inodes other than the root - Patch 6: clean up after a failing test; use f-strings; add tests for patch 1's depth check, patch 2, the last inode of a group and the journal's inode - Rebase on main Link to v1: https://patchwork.ozlabs.org/project/uboot/list/?series=529698 Ana Bobillo (6): fs: ext4: check extent tree nodes before walking them fs: ext4: fix the triple indirect cache check in read_allocated_block() fs: ext4: refuse to write with an unsupported block group layout fs: ext4: check block numbers before releasing them fs: ext4: fix the block group of an inode in the write path test: fs: ext4: add tests for corrupt on-disk indices fs/ext4/ext4_common.c | 132 ++++++++++- fs/ext4/ext4_common.h | 2 + fs/ext4/ext4_write.c | 151 ++++++++----- include/ext4fs.h | 1 + scripts/pylint.base | 1 + test/py/tests/test_fs/test_ext_oob.py | 310 ++++++++++++++++++++++++++ 6 files changed, 530 insertions(+), 67 deletions(-) create mode 100644 test/py/tests/test_fs/test_ext_oob.py
