Squashfs in many places uses a signed int to store the block number and block count of a file. If the block size is using the Linux page size (normally 4 KiB), it means any file larger than 8 TiB (2^43) will overflow a signed int. If the block number and block count is in terms of the Squashfs filesystem block size (which can be between 4 kiB and 1 MiB), it depends on the block size as to when a signed int will overflow, but a file larger than 256 TiB (2^48) using 128 KiB blocks will overflow a signed int. This commit widens all appropriate variables to long long (s64) to avoid overflow. Reproducer ========== The following reproducer creates a Squashfs filesystem with a 8 TiB + 4096 byte sparse file. The filestem is mounted, and the last 4096 bytes are read from the file. If the reproducer is successful the process will hang. This is because signed int overflow occurs in squashfs_copy_cache() with the result that folio_end_read() is not called. ==================================================================== finish() { popd 2>&1 > /dev/null rm -rf $DIR exit 1 } DIR=$(mktemp -d) pushd $DIR 2>&1 > /dev/null echo "Create an 8 Tib (2^43) + 4096 byte sparse file" if ! truncate --size 8796093026304 bigfile; then echo Create of sparse file failed. finish fi echo Generate the Squashfs filesystem containing the sparse file echo This will be VERY slow on Squashfs-tools older than 4.7.3 mksquashfs bigfile bigfile.sqsh -b 1M -quiet echo Mount the filesystem to /mnt if ! mount -t squashfs bigfile.sqsh /mnt; then echo Mount of Squashfs filesystem failed finish fi echo Read the last 4096 bytes from the sparse file. echo If the exploit succeeds the process will hang dd if=/mnt/bigfile bs=1 skip=8796093022208 popd 2>&1 > /dev/null rm -rf $DIR exit 0 ==================================================================== Fixes: 1701aecb6849c ("Squashfs: regular file operations") Signed-off-by: Phillip Lougher --- v2: - Ensure return value of fill_meta_data() is evaluated using 64-bit arithmetic - Widen int n to s64 in read_indexes() --- fs/squashfs/file.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c index ad6382cd22d3..2396fad1de61 100644 --- a/fs/squashfs/file.c +++ b/fs/squashfs/file.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "squashfs_fs.h" #include "squashfs_fs_sb.h" @@ -160,7 +161,7 @@ static void release_meta_index(struct inode *inode, struct meta_index *meta) * Read the next n blocks from the block list, starting from * metadata block . */ -static long long read_indexes(struct super_block *sb, int n, +static long long read_indexes(struct super_block *sb, s64 n, u64 *start_block, int *offset) { int err, i; @@ -173,7 +174,7 @@ static long long read_indexes(struct super_block *sb, int n, } while (n) { - int blocks = min_t(int, n, PAGE_SIZE >> 2); + int blocks = min_t(s64, n, PAGE_SIZE >> 2); err = squashfs_read_metadata(sb, blist, start_block, offset, blocks << 2); @@ -216,7 +217,7 @@ static long long read_indexes(struct super_block *sb, int n, */ static inline int calculate_skip(u64 blocks) { - u64 skip = blocks / ((SQUASHFS_META_ENTRIES + 1) + u64 skip = div_u64(blocks, (SQUASHFS_META_ENTRIES + 1) * SQUASHFS_META_INDEXES); return min((u64) SQUASHFS_CACHED_BLKS - 1, skip + 1); } @@ -227,7 +228,7 @@ static inline int calculate_skip(u64 blocks) * on-disk locations of the datablock and block list metadata block * for index (scaled to nearest cache index). */ -static int fill_meta_index(struct inode *inode, int index, +static s64 fill_meta_index(struct inode *inode, s64 index, u64 *index_block, int *index_offset, u64 *data_block) { struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; @@ -243,7 +244,7 @@ static int fill_meta_index(struct inode *inode, int index, /* * Scale index to cache index (cache slot entry) */ - index /= SQUASHFS_META_INDEXES * skip; + index = div_u64(index, SQUASHFS_META_INDEXES * skip); while (offset < index) { meta = locate_meta_index(inode, offset + 1, index); @@ -315,7 +316,7 @@ static int fill_meta_index(struct inode *inode, int index, /* * Scale cache index (cache slot entry) to index */ - return offset * SQUASHFS_META_INDEXES * skip; + return (s64)offset * SQUASHFS_META_INDEXES * skip; failed: release_meta_index(inode, meta); @@ -327,14 +328,14 @@ static int fill_meta_index(struct inode *inode, int index, * Get the on-disk location and compressed size of the datablock * specified by index. Fill_meta_index() does most of the work. */ -static int read_blocklist_ptrs(struct inode *inode, int index, u64 *start, +static int read_blocklist_ptrs(struct inode *inode, s64 index, u64 *start, int *offset, u64 *block) { long long blks; __le32 size; - int res = fill_meta_index(inode, index, start, offset, block); + s64 res = fill_meta_index(inode, index, start, offset, block); - TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset 0x%x, block 0x%llx\n", + TRACE("read_blocklist: res %lld, index %lld, start 0x%llx, offset 0x%x, block 0x%llx\n", res, index, *start, *offset, block ? *block : 0); if (res < 0) @@ -364,7 +365,7 @@ static int read_blocklist_ptrs(struct inode *inode, int index, u64 *start, return squashfs_block_size(size); } -static inline int read_blocklist(struct inode *inode, int index, u64 *block) +static inline int read_blocklist(struct inode *inode, s64 index, u64 *block) { u64 start; int offset; @@ -397,8 +398,8 @@ void squashfs_copy_cache(struct folio *folio, struct address_space *mapping = folio->mapping; struct inode *inode = mapping->host; struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; - int i, mask = (1 << (msblk->block_log - PAGE_SHIFT)) - 1; - int start_index = folio->index & ~mask, end_index = start_index | mask; + s64 i, mask = (1 << (msblk->block_log - PAGE_SHIFT)) - 1; + s64 start_index = folio->index & ~mask, end_index = start_index | mask; /* * Loop copying datablock into pages. As the datablock likely covers @@ -412,7 +413,7 @@ void squashfs_copy_cache(struct folio *folio, size_t avail = buffer ? min(bytes, PAGE_SIZE) : 0; bool updated = false; - TRACE("bytes %zu, i %d, available_bytes %zu\n", bytes, i, avail); + TRACE("bytes %zu, i %lld, available_bytes %zu\n", bytes, i, avail); push_folio = (i == folio->index) ? folio : __filemap_get_folio(mapping, i, @@ -464,8 +465,8 @@ static int squashfs_read_folio(struct file *file, struct folio *folio) { struct inode *inode = folio->mapping->host; struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; - int index = folio->index >> (msblk->block_log - PAGE_SHIFT); - int file_end = i_size_read(inode) >> msblk->block_log; + s64 index = folio->index >> (msblk->block_log - PAGE_SHIFT); + s64 file_end = i_size_read(inode) >> msblk->block_log; int expected = index == file_end ? (i_size_read(inode) & (msblk->block_size - 1)) : msblk->block_size; @@ -584,7 +585,7 @@ static void squashfs_readahead(struct readahead_control *ractl) unsigned int nr_pages = 0; struct page **pages; int i; - loff_t file_end = i_size_read(inode) >> msblk->block_log; + s64 file_end = i_size_read(inode) >> msblk->block_log; unsigned int max_pages = 1UL << shift; readahead_expand(ractl, start, (len | mask) + 1); -- 2.47.3