[PATCH 12/18] Squashfs: very large files can exceed an int block count
From: Phillip Lougher
Date: Sat Nov 07 2026 - 00:35:11 EST
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 <phillip@xxxxxxxxxxxxxxx>
---
fs/squashfs/file.c | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index ad6382cd22d3..05ae61dd6be2 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -35,6 +35,7 @@
#include <linux/string.h>
#include <linux/pagemap.h>
#include <linux/mutex.h>
+#include <linux/math64.h>
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
@@ -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
* <index_block, index_offset> 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);
@@ -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