From: Phillip Lougher <phillip@squashfs.org.uk>
To: akpm@linux-foundation.org, linux-kernel@vger.kernel.org
Cc: Phillip Lougher <phillip@squashfs.org.uk>
Subject: [PATCH 13/18] Squashfs: add more sanity checking to squashfs_block_size()
Date: Sat, 7 Nov 2026 05:28:38 +0000 [thread overview]
Message-ID: <20261107052843.725239-14-phillip@squashfs.org.uk> (raw)
In-Reply-To: <20261107052843.725239-1-phillip@squashfs.org.uk>
Squashfs_block_size() sanity checks the data and fragment block size
read from disk. However, it doesn't do all the sanity checking that
it could do. This means detectably invalid block sizes are passed
down to squashfs_read_data().
This situation is sub-optimal because in the case of fragment
blocks, a fragment cache buffer has to obtained (with possible
waits for a free buffer etc.) before squashfs_read_data() is called.
In both fragment and data block cases if the buffer is compressed, it
means a free decompressor may be obtained (with possible waits for a
decompressor) by squashfs_read_data(), only to have the decompression
fail.
In both these cases this adds unnecessary overhead which may impact
performance, and this is with values that should have been detected
earlier.
This commit adds extra sanity checking to trap additional incorrect
values.
Reproducer
==========
The reproducer generates a Squashfs filesystem and then corrupts the
block size value for file zero block 0. If the exploit succeeds this
corrupted value is not detected, and it is passed to the decompressor
via squashfs_read_data() which then fails at that point.
====================================================================
#!/bin/bash
ARGS="-quiet -no-progress -no-fragments -noI -noD -no-sparse -comp gzip"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
echo Create Squashfs filesystem with one file
echo "/ d 0777 0 0" > pseudo
echo "zero f 0777 0 0 dd if=/dev/zero bs=1024 count=128 2>/dev/null" >> pseudo
if ! mksquashfs - img.sqsh -pf pseudo $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
echo Corrupt the data block size for block 0 in file zero
echo Flip the compression bit from uncompressed to compressed.
echo "This is at byte offset 131207 (96 + 128 KiB + 37)"
dd if=/dev/zero of=img.sqsh seek=131205 bs=1 count=1 conv=notrunc status=none
echo Mount the filesystem to /mnt
if ! mount -t squashfs img.sqsh /mnt; then
echo Mount of Squashfs filesystem failed
finish
fi
echo Read the file zero.
echo If the exploit succeeds, dmesg should show the following:
echo "SQUASHFS error: zlib decompression failed, data probably corrupt"
echo "SQUASHFS error: Failed to read block 0x60: -5"
echo "SQUASHFS error: zlib decompression failed, data probably corrupt"
echo "SQUASHFS error: Failed to read block 0x60: -5"
echo "SQUASHFS error: Unable to read data cache entry [60]"
echo "SQUASHFS error: Unable to read page, block 60, size 20000"
dd if=/mnt/zero
popd 2>&1> /dev/null
rm -rf $DIR
==============================================================================
Fixes: 1701aecb6849c ("Squashfs: regular file operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/file.c | 8 +++++---
fs/squashfs/fragment.c | 2 +-
fs/squashfs/squashfs_fs.h | 6 ------
fs/squashfs/squashfs_helpers.h | 32 ++++++++++++++++++++++++++++++++
4 files changed, 38 insertions(+), 10 deletions(-)
diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index 05ae61dd6be2..d3be147e1ea3 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -166,6 +166,7 @@ static long long read_indexes(struct super_block *sb, int n,
{
int err, i;
long long block = 0;
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
__le32 *blist = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (blist == NULL) {
@@ -185,7 +186,7 @@ static long long read_indexes(struct super_block *sb, int n,
}
for (i = 0; i < blocks; i++) {
- int size = squashfs_block_size(blist[i]);
+ int size = squashfs_data_size(blist[i], msblk->block_size);
if (size < 0) {
err = size;
goto failure;
@@ -333,6 +334,7 @@ static int read_blocklist_ptrs(struct inode *inode, s64 index, u64 *start,
{
long long blks;
__le32 size;
+ struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
s64 res = fill_meta_index(inode, index, start, offset, block);
TRACE("read_blocklist: res %lld, index %lld, start 0x%llx, offset 0x%x, block 0x%llx\n",
@@ -362,7 +364,7 @@ static int read_blocklist_ptrs(struct inode *inode, s64 index, u64 *start,
sizeof(size));
if (res < 0)
return res;
- return squashfs_block_size(size);
+ return squashfs_data_size(size, msblk->block_size);
}
static inline int read_blocklist(struct inode *inode, s64 index, u64 *block)
@@ -720,7 +722,7 @@ static loff_t seek_hole_data(struct file *file, loff_t offset, int whence)
goto finished;
for (i = 0; i < indexes; i++) {
- length = squashfs_block_size(blist[i]);
+ length = squashfs_data_size(blist[i], msblk->block_size);
if (length < 0) {
offset = length;
goto finished;
diff --git a/fs/squashfs/fragment.c b/fs/squashfs/fragment.c
index 9f63e5b5c513..1f372374c29d 100644
--- a/fs/squashfs/fragment.c
+++ b/fs/squashfs/fragment.c
@@ -54,7 +54,7 @@ int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
return size;
*fragment_block = le64_to_cpu(fragment_entry.start_block);
- return squashfs_block_size(fragment_entry.size);
+ return squashfs_frag_size(fragment_entry.size, msblk->block_size);
}
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index 3e7d0730b2d9..6ca8d7227a0e 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -101,12 +101,6 @@
#define SQUASHFS_XATTR_VALUE_OOL 256
#define SQUASHFS_XATTR_PREFIX_MASK 0xff
-static inline int squashfs_block_size(__le32 raw)
-{
- u32 size = le32_to_cpu(raw);
- return (size >> 25) ? -EIO : size;
-}
-
/* cached data constants for filesystem */
#define SQUASHFS_CACHED_BLKS 8
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 124ee18ed8c9..9b3466d26cd1 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -203,4 +203,36 @@ static inline s16 squashfs_xattr_offset(s64 xattr)
{
return xattr & 0xffff;
}
+
+static inline int squashfs_block_size(u32 size, s32 block_size)
+{
+ /* reject any size with bits set where they should not be */
+ if ((size & ~SQUASHFS_COMPRESSED_BIT_BLOCK) > block_size)
+ return -EIO;
+
+ /* a block can't be both full size and compressed */
+ if (size == block_size)
+ return -EIO;
+
+ return size;
+}
+
+static inline int squashfs_data_size(__le32 raw, s32 block_size)
+{
+ return squashfs_block_size(le32_to_cpu(raw), block_size);
+}
+
+static inline int squashfs_frag_size(__le32 raw, s32 block_size)
+{
+ int size = squashfs_block_size(le32_to_cpu(raw), block_size);
+
+ if (size < 0)
+ return size;
+
+ /* a fragment block can't be zero size, compressed or uncompressed */
+ if ((size & ~SQUASHFS_COMPRESSED_BIT_BLOCK) == 0)
+ return -EIO;
+
+ return size;
+}
#endif
--
2.47.3
next prev parent reply other threads:[~2026-11-07 5:29 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
2026-11-07 5:28 ` [PATCH 01/18] Squashfs: convert SQUASHFS_COMPRESSED_* to inline functions Phillip Lougher
2026-11-07 5:28 ` [PATCH 02/18] Squashfs: convert SQUASHFS_INODE_* and SQUASHFS_MKINODE " Phillip Lougher
2026-11-07 5:28 ` [PATCH 03/18] Squashfs: convert SQUASHFS_FRAGMENT_* " Phillip Lougher
2026-11-07 5:28 ` [PATCH 04/18] Squashfs: convert SQUASHFS_LOOKUP_* " Phillip Lougher
2026-11-07 5:28 ` [PATCH 05/18] Squashfs: convert SQUASHFS_ID_* " Phillip Lougher
2026-11-07 5:28 ` [PATCH 06/18] Squashfs: convert SQUASHFS_XATTR_* " Phillip Lougher
2026-11-07 5:28 ` [PATCH 07/18] Squashfs: fix fragment bytes overflow on 32-bit architectures Phillip Lougher
2026-11-07 5:28 ` [PATCH 08/18] Squashfs: fix fragment index table length too large exploit Phillip Lougher
2026-11-07 5:28 ` [PATCH 09/18] Squashfs: sanity check inode metadata block location Phillip Lougher
2026-11-07 5:28 ` [PATCH 10/18] Squashfs: sanity check directory " Phillip Lougher
2026-11-07 5:28 ` [PATCH 11/18] Squashfs: widen xattr count variables and add sanity check Phillip Lougher
2026-11-07 5:28 ` [PATCH 12/18] Squashfs: very large files can exceed an int block count Phillip Lougher
2026-11-07 5:28 ` Phillip Lougher [this message]
2026-11-07 5:28 ` [PATCH 14/18] Squashfs: sanity check fragment block location and range Phillip Lougher
2026-11-07 5:28 ` [PATCH 15/18] Squashfs: sanity check block list sizes " Phillip Lougher
2026-11-07 5:28 ` [PATCH 16/18] Squashfs: don't store large export table indexes in memory Phillip Lougher
2026-11-07 5:28 ` [PATCH 17/18] Squashfs: don't store large fragment " Phillip Lougher
2026-11-07 5:28 ` [PATCH 18/18] Squashfs: don't store large xattr id " Phillip Lougher
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261107052843.725239-14-phillip@squashfs.org.uk \
--to=phillip@squashfs.org.uk \
--cc=akpm@linux-foundation.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®