All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images
@ 2026-06-12  7:54 Piyush Paliwal
  2026-06-12  7:54 ` [PATCH 1/2] fs/squashfs: bound the inode table walk in sqfs_find_inode() Piyush Paliwal
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Piyush Paliwal @ 2026-06-12  7:54 UTC (permalink / raw)
  To: u-boot
  Cc: joaomarcos.costa, richard.genoud, miquel.raynal, thomas.petazzoni,
	trini, eric.kilmer, Piyush Paliwal

Two independent out-of-bounds reads in the SquashFS driver, both reachable
by pointing U-Boot at an attacker-supplied image (e.g. ls/load on a crafted
USB/SD/netboot rootfs). Either one crashes the bootloader (DoS); patch 2 can
also copy adjacent heap memory into the loaded file (information disclosure).

  1/2 sqfs_find_inode()/sqfs_inode_size() walk the decompressed inode table
      using on-disk sizes with no check that the cursor stays inside the
      buffer -> wild read / SEGV, e.g. from a simple "ls".
  2/2 sqfs_read_nest() uses the on-disk fragment offset as an unbounded
      source index into the fragment block -> out-of-bounds heap read when
      loading a fragment-backed file.

Both were found by fuzzing the sandbox build (CONFIG_ASAN) of sqfsls/sqfsload
with mutated images. With the fixes, the crashing inputs are rejected
cleanly, 2000 fuzz iterations produce no further crashes, and the valid-image
path is unchanged.

These are distinct from the 2024 SquashFS CVE cluster (CVE-2024-57254..57259,
fixed in 2025.01-rc1) and from the sqfs_frag_lookup() fix (e365a269df5): the
earlier work added NULL checks at the callers and fixed the symlink-size and
fragment-table paths, but left these inode-table-walk and fragment-data
paths unbounded.

The two patches are independent and can be applied in either order.

Piyush Paliwal (2):
  fs/squashfs: bound the inode table walk in sqfs_find_inode()
  fs/squashfs: bound fragment offset/size in sqfs_read_nest()

 fs/squashfs/sqfs.c            |  50 ++++++++++++----
 fs/squashfs/sqfs_filesystem.h |   6 +-
 fs/squashfs/sqfs_inode.c      | 106 +++++++++++++++++++++++++++++-----
 3 files changed, 134 insertions(+), 28 deletions(-)

-- 
2.41.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] fs/squashfs: bound the inode table walk in sqfs_find_inode()
  2026-06-12  7:54 [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Piyush Paliwal
@ 2026-06-12  7:54 ` Piyush Paliwal
  2026-06-12  7:54 ` [PATCH 2/2] fs/squashfs: bound fragment offset/size in sqfs_read_nest() Piyush Paliwal
  2026-06-25  8:08 ` [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Richard GENOUD
  2 siblings, 0 replies; 4+ messages in thread
From: Piyush Paliwal @ 2026-06-12  7:54 UTC (permalink / raw)
  To: u-boot
  Cc: joaomarcos.costa, richard.genoud, miquel.raynal, thomas.petazzoni,
	trini, eric.kilmer, Piyush Paliwal, stable

sqfs_find_inode() walks the decompressed inode table advancing
"offset += sqfs_inode_size(base, ...)" with no check that offset stays
within the table (metablks_count * SQFS_METADATA_BLOCK_SIZE). All sizes
come from the on-disk image, including the unbounded extended-directory
(LDIR) index walk and the regular-file block-list term in
sqfs_inode_size(). A crafted image makes base run off the end of the
buffer -> out-of-bounds read / SEGV, reachable simply by listing the
image (ls/sqfsls) or any operation that resolves a path.

The earlier fix 3fb1df1e5 ("squashfs: Check sqfs_find_inode() return
value") only added NULL checks at the call sites; it did not add the
missing internal bound, so the wild read still occurs before the
function can return. c8e929e5 fixed only the symlink case of
sqfs_inode_size(), leaving the LDIR index walk unbounded.

Thread the inode table size from sqfs_read_inode_table() through the
squashfs_dir_stream to sqfs_find_inode(), and:
 - reject an inode whose base header does not fit in the table;
 - pass the remaining byte count to sqfs_inode_size() and validate every
   variable-length read (LDIR index list, REG/LREG block list, symlink,
   device/ipc inodes) against it, with overflow-checked arithmetic;
 - reject an inode whose computed size leaves the table.

Found by fuzzing the sandbox (CONFIG_ASAN) sqfsls/sqfsload with mutated
images. Before: SEGV in sqfs_find_inode (sqfs_inode.c) and in
sqfs_inode_size() LDIR walk. After: malformed images are rejected
cleanly; 2000 fuzz iterations produce no crash and the valid-image path
is unchanged.

Fixes: c51006130370 ("fs/squashfs: new filesystem")
Cc: stable@vger.kernel.org
Signed-off-by: Piyush Paliwal <piyushthepal@gmail.com>
---
 fs/squashfs/sqfs.c            |  26 +++++----
 fs/squashfs/sqfs_filesystem.h |   6 +-
 fs/squashfs/sqfs_inode.c      | 106 +++++++++++++++++++++++++++++-----
 3 files changed, 110 insertions(+), 28 deletions(-)

diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
index 0768fc4a7b2..3548b5e07e2 100644
--- a/fs/squashfs/sqfs.c
+++ b/fs/squashfs/sqfs.c
@@ -486,7 +486,8 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
 	dirsp = (struct fs_dir_stream *)dirs;
 
 	/* Start by root inode */
-	table = sqfs_find_inode(dirs->inode_table, le32_to_cpu(sblk->inodes),
+	table = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+				le32_to_cpu(sblk->inodes),
 				sblk->inodes, sblk->block_size);
 	if (!table)
 		return -EINVAL;
@@ -543,7 +544,8 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
 			dirs->dir_header->inode_number;
 
 		/* Get reference to inode in the inode table */
-		table = sqfs_find_inode(dirs->inode_table, new_inode_number,
+		table = sqfs_find_inode(dirs->inode_table,
+					dirs->inode_table_size, new_inode_number,
 					sblk->inodes, sblk->block_size);
 		if (!table)
 			return -EINVAL;
@@ -719,7 +721,7 @@ static int sqfs_get_metablk_pos(u32 *pos_list, void *table, u32 offset,
 	return ret;
 }
 
-static int sqfs_read_inode_table(unsigned char **inode_table)
+static int sqfs_read_inode_table(unsigned char **inode_table, size_t *out_size)
 {
 	struct squashfs_super_block *sblk = ctxt.sblk;
 	u64 start, n_blks, table_offset, table_size;
@@ -773,6 +775,8 @@ static int sqfs_read_inode_table(unsigned char **inode_table)
 		goto free_itb;
 	}
 
+	*out_size = (size_t)metablks_count * SQFS_METADATA_BLOCK_SIZE;
+
 	src_table = itb + table_offset + SQFS_HEADER_SIZE;
 
 	/* Extract compressed Inode table */
@@ -920,6 +924,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
 	int j, token_count = 0, ret = 0, metablks_count;
 	struct squashfs_dir_stream *dirs;
 	char **token_list = NULL, *path = NULL;
+	size_t inode_table_size = 0;
 	u32 *pos_list = NULL;
 
 	dirs = calloc(1, sizeof(*dirs));
@@ -933,7 +938,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
 	dirs->inode_table = NULL;
 	dirs->dir_table = NULL;
 
-	ret = sqfs_read_inode_table(&inode_table);
+	ret = sqfs_read_inode_table(&inode_table, &inode_table_size);
 	if (ret) {
 		ret = -EINVAL;
 		goto out;
@@ -973,6 +978,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
 	 * a general solution for the malloc size, since 'i' is a union.
 	 */
 	dirs->inode_table = inode_table;
+	dirs->inode_table_size = inode_table_size;
 	dirs->dir_table = dir_table;
 	ret = sqfs_search_dir(dirs, token_list, token_count, pos_list,
 			      metablks_count);
@@ -1071,8 +1077,8 @@ static int sqfs_readdir_nest(struct fs_dir_stream *fs_dirs, struct fs_dirent **d
 	}
 
 	i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
-	ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
-			       sblk->block_size);
+	ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+			       i_number, sblk->inodes, sblk->block_size);
 	if (!ipos)
 		return -SQFS_STOP_READDIR;
 
@@ -1430,8 +1436,8 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
 	}
 
 	i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
-	ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
-			       sblk->block_size);
+	ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+			       i_number, sblk->inodes, sblk->block_size);
 	if (!ipos) {
 		ret = -EINVAL;
 		goto out;
@@ -1699,8 +1705,8 @@ static int sqfs_size_nest(const char *filename, loff_t *size)
 	}
 
 	i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
-	ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
-			       sblk->block_size);
+	ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+			       i_number, sblk->inodes, sblk->block_size);
 
 	if (!ipos) {
 		*size = 0;
diff --git a/fs/squashfs/sqfs_filesystem.h b/fs/squashfs/sqfs_filesystem.h
index be56498a5e3..6c97b2c3a9c 100644
--- a/fs/squashfs/sqfs_filesystem.h
+++ b/fs/squashfs/sqfs_filesystem.h
@@ -275,6 +275,8 @@ struct squashfs_dir_stream {
 	 * sqfs_opendir() and freed in sqfs_closedir().
 	 */
 	unsigned char *inode_table;
+	/* Size in bytes of the decompressed inode_table buffer */
+	size_t inode_table_size;
 	unsigned char *dir_table;
 };
 
@@ -293,8 +295,8 @@ struct squashfs_file_info {
 	bool comp;
 };
 
-void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
-		      __le32 block_size);
+void *sqfs_find_inode(void *inode_table, size_t table_size, int inode_number,
+		      __le32 inode_count, __le32 block_size);
 
 int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count);
 
diff --git a/fs/squashfs/sqfs_inode.c b/fs/squashfs/sqfs_inode.c
index ce9a8ff8e2a..20085f8912b 100644
--- a/fs/squashfs/sqfs_inode.c
+++ b/fs/squashfs/sqfs_inode.c
@@ -17,65 +17,122 @@
 #include "sqfs_filesystem.h"
 #include "sqfs_utils.h"
 
-int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
+int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size, size_t max)
 {
-	u16 inode_type = get_unaligned_le16(&inode->inode_type);
+	u16 inode_type;
+
+	/* The smallest possible inode must fit in the remaining bytes */
+	if (max < sizeof(struct squashfs_base_inode))
+		return -EINVAL;
+
+	inode_type = get_unaligned_le16(&inode->inode_type);
 
 	switch (inode_type) {
 	case SQFS_DIR_TYPE:
+		if (max < sizeof(struct squashfs_dir_inode))
+			return -EINVAL;
 		return sizeof(struct squashfs_dir_inode);
 
 	case SQFS_REG_TYPE: {
 		struct squashfs_reg_inode *reg =
 			(struct squashfs_reg_inode *)inode;
-		u32 fragment = get_unaligned_le32(&reg->fragment);
-		u32 file_size = get_unaligned_le32(&reg->file_size);
+		u32 fragment, file_size;
 		unsigned int blk_list_size;
+		int size;
+
+		if (max < sizeof(*reg))
+			return -EINVAL;
+
+		fragment = get_unaligned_le32(&reg->fragment);
+		file_size = get_unaligned_le32(&reg->file_size);
+
+		if (!blk_size)
+			return -EINVAL;
 
 		if (SQFS_IS_FRAGMENTED(fragment))
 			blk_list_size = file_size / blk_size;
 		else
 			blk_list_size = DIV_ROUND_UP(file_size, blk_size);
 
-		return sizeof(*reg) + blk_list_size * sizeof(u32);
+		if (__builtin_mul_overflow(blk_list_size, (unsigned int)sizeof(u32),
+					   &blk_list_size) ||
+		    __builtin_add_overflow((int)sizeof(*reg), (int)blk_list_size,
+					   &size))
+			return -EINVAL;
+
+		return size;
 	}
 
 	case SQFS_LDIR_TYPE: {
 		struct squashfs_ldir_inode *ldir =
 			(struct squashfs_ldir_inode *)inode;
-		u16 i_count = get_unaligned_le16(&ldir->i_count);
+		u16 i_count;
 		unsigned int index_list_size = 0, l = 0;
 		struct squashfs_directory_index *di;
+		size_t consumed;
 		u32 sz;
+		int size;
 
+		if (max < sizeof(*ldir))
+			return -EINVAL;
+
+		i_count = get_unaligned_le16(&ldir->i_count);
 		if (i_count == 0)
 			return sizeof(*ldir);
 
 		di = ldir->index;
+		consumed = sizeof(*ldir);
 		while (l < i_count) {
+			/* The directory index header must stay in bounds */
+			if (consumed + sizeof(*di) > max)
+				return -EINVAL;
 			sz = get_unaligned_le32(&di->size) + 1;
+			if (__builtin_add_overflow(consumed, sizeof(*di) + sz,
+						   &consumed) ||
+			    consumed > max)
+				return -EINVAL;
 			index_list_size += sz;
 			di = (void *)di + sizeof(*di) + sz;
 			l++;
 		}
 
-		return sizeof(*ldir) + index_list_size +
-			i_count * SQFS_DIR_INDEX_BASE_LENGTH;
+		if (__builtin_add_overflow((int)(sizeof(*ldir) + index_list_size),
+					   (int)(i_count * SQFS_DIR_INDEX_BASE_LENGTH),
+					   &size))
+			return -EINVAL;
+
+		return size;
 	}
 
 	case SQFS_LREG_TYPE: {
 		struct squashfs_lreg_inode *lreg =
 			(struct squashfs_lreg_inode *)inode;
-		u32 fragment = get_unaligned_le32(&lreg->fragment);
-		u64 file_size = get_unaligned_le64(&lreg->file_size);
+		u32 fragment;
+		u64 file_size;
 		unsigned int blk_list_size;
+		int size;
+
+		if (max < sizeof(*lreg))
+			return -EINVAL;
+
+		fragment = get_unaligned_le32(&lreg->fragment);
+		file_size = get_unaligned_le64(&lreg->file_size);
+
+		if (!blk_size)
+			return -EINVAL;
 
 		if (fragment == 0xFFFFFFFF)
 			blk_list_size = DIV_ROUND_UP(file_size, blk_size);
 		else
 			blk_list_size = file_size / blk_size;
 
-		return sizeof(*lreg) + blk_list_size * sizeof(u32);
+		if (__builtin_mul_overflow(blk_list_size, (unsigned int)sizeof(u32),
+					   &blk_list_size) ||
+		    __builtin_add_overflow((int)sizeof(*lreg), (int)blk_list_size,
+					   &size))
+			return -EINVAL;
+
+		return size;
 	}
 
 	case SQFS_SYMLINK_TYPE:
@@ -85,6 +142,9 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
 		struct squashfs_symlink_inode *symlink =
 			(struct squashfs_symlink_inode *)inode;
 
+		if (max < sizeof(*symlink))
+			return -EINVAL;
+
 		if (__builtin_add_overflow(sizeof(*symlink),
 		    get_unaligned_le32(&symlink->symlink_size), &size))
 			return -EINVAL;
@@ -94,15 +154,23 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
 
 	case SQFS_BLKDEV_TYPE:
 	case SQFS_CHRDEV_TYPE:
+		if (max < sizeof(struct squashfs_dev_inode))
+			return -EINVAL;
 		return sizeof(struct squashfs_dev_inode);
 	case SQFS_LBLKDEV_TYPE:
 	case SQFS_LCHRDEV_TYPE:
+		if (max < sizeof(struct squashfs_ldev_inode))
+			return -EINVAL;
 		return sizeof(struct squashfs_ldev_inode);
 	case SQFS_FIFO_TYPE:
 	case SQFS_SOCKET_TYPE:
+		if (max < sizeof(struct squashfs_ipc_inode))
+			return -EINVAL;
 		return sizeof(struct squashfs_ipc_inode);
 	case SQFS_LFIFO_TYPE:
 	case SQFS_LSOCKET_TYPE:
+		if (max < sizeof(struct squashfs_lipc_inode))
+			return -EINVAL;
 		return sizeof(struct squashfs_lipc_inode);
 	default:
 		printf("Error while searching inode: unknown type.\n");
@@ -114,11 +182,12 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
  * Given the uncompressed inode table, the inode to be found and the number of
  * inodes in the table, return inode position in case of success.
  */
-void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
-		      __le32 block_size)
+void *sqfs_find_inode(void *inode_table, size_t table_size, int inode_number,
+		      __le32 inode_count, __le32 block_size)
 {
 	struct squashfs_base_inode *base;
-	unsigned int offset = 0, k;
+	size_t offset = 0;
+	unsigned int k;
 	int sz;
 
 	if (!inode_table) {
@@ -127,12 +196,17 @@ void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
 	}
 
 	for (k = 0; k < le32_to_cpu(inode_count); k++) {
+		/* The base inode header must lie within the inode table */
+		if (offset + sizeof(struct squashfs_base_inode) > table_size)
+			return NULL;
+
 		base = inode_table + offset;
 		if (get_unaligned_le32(&base->inode_number) == inode_number)
 			return inode_table + offset;
 
-		sz = sqfs_inode_size(base, le32_to_cpu(block_size));
-		if (sz < 0)
+		sz = sqfs_inode_size(base, le32_to_cpu(block_size),
+				     table_size - offset);
+		if (sz <= 0 || (size_t)sz > table_size - offset)
 			return NULL;
 
 		offset += sz;
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] fs/squashfs: bound fragment offset/size in sqfs_read_nest()
  2026-06-12  7:54 [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Piyush Paliwal
  2026-06-12  7:54 ` [PATCH 1/2] fs/squashfs: bound the inode table walk in sqfs_find_inode() Piyush Paliwal
@ 2026-06-12  7:54 ` Piyush Paliwal
  2026-06-25  8:08 ` [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Richard GENOUD
  2 siblings, 0 replies; 4+ messages in thread
From: Piyush Paliwal @ 2026-06-12  7:54 UTC (permalink / raw)
  To: u-boot
  Cc: joaomarcos.costa, richard.genoud, miquel.raynal, thomas.petazzoni,
	trini, eric.kilmer, Piyush Paliwal, stable

When reading a fragment-backed file, sqfs_read_nest() copies the file
data out of the fragment block with:

  memcpy(buf + *actread, &fragment_block[finfo.offset],
         finfo.size - *actread);

finfo.offset (the fragment's byte offset) and finfo.size come straight
from the on-disk inode and are never validated against the fragment
block length. Unlike the data-block loop above it, this path does not
clamp the source span, so a crafted inode makes the memcpy read past the
fragment buffer -> out-of-bounds heap read. The leaked bytes are copied
into the user-visible load buffer (information disclosure) or fault.
This affects both the compressed (dest_len bytes) and the uncompressed
(table_size bytes) fragment cases.

Validate finfo.offset and the copy length against the available fragment
data before each memcpy and reject malformed inodes.

Found by fuzzing the sandbox (CONFIG_ASAN) sqfsload with mutated images:
before, SEGV in sqfs_read_nest() at the fragment memcpy; after, malformed
images are rejected and valid fragmented files still load correctly.

Fixes: 0008d8086649 ("fs/squashfs: fix reading of fragmented files")
Cc: stable@vger.kernel.org
Signed-off-by: Piyush Paliwal <piyushthepal@gmail.com>
---
 fs/squashfs/sqfs.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
index 3548b5e07e2..6e7723669d4 100644
--- a/fs/squashfs/sqfs.c
+++ b/fs/squashfs/sqfs.c
@@ -1630,6 +1630,19 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
 			goto out;
 		}
 
+		/*
+		 * finfo.offset and finfo.size come from the on-disk inode and
+		 * must not let the copy read past the decompressed fragment
+		 * block (dest_len bytes).
+		 */
+		if (finfo.size < (size_t)*actread ||
+		    finfo.offset > dest_len ||
+		    finfo.size - *actread > dest_len - finfo.offset) {
+			free(fragment_block);
+			ret = -EINVAL;
+			goto out;
+		}
+
 		memcpy(buf + *actread, &fragment_block[finfo.offset], finfo.size - *actread);
 		*actread = finfo.size;
 
@@ -1638,6 +1651,17 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
 	} else if (finfo.frag && !finfo.comp) {
 		fragment_block = (void *)fragment + table_offset;
 
+		/*
+		 * Same check for the uncompressed fragment: the readable data
+		 * is table_size bytes starting at table_offset within fragment.
+		 */
+		if (finfo.size < (size_t)*actread ||
+		    finfo.offset > table_size ||
+		    finfo.size - *actread > table_size - finfo.offset) {
+			ret = -EINVAL;
+			goto out;
+		}
+
 		memcpy(buf + *actread, &fragment_block[finfo.offset], finfo.size - *actread);
 		*actread = finfo.size;
 	}
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images
  2026-06-12  7:54 [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Piyush Paliwal
  2026-06-12  7:54 ` [PATCH 1/2] fs/squashfs: bound the inode table walk in sqfs_find_inode() Piyush Paliwal
  2026-06-12  7:54 ` [PATCH 2/2] fs/squashfs: bound fragment offset/size in sqfs_read_nest() Piyush Paliwal
@ 2026-06-25  8:08 ` Richard GENOUD
  2 siblings, 0 replies; 4+ messages in thread
From: Richard GENOUD @ 2026-06-25  8:08 UTC (permalink / raw)
  To: Piyush Paliwal, u-boot
  Cc: joaomarcos.costa, miquel.raynal, thomas.petazzoni, trini,
	eric.kilmer

Hi Piyush,

Le 12/06/2026 à 09:54, Piyush Paliwal a écrit :
> Two independent out-of-bounds reads in the SquashFS driver, both reachable
> by pointing U-Boot at an attacker-supplied image (e.g. ls/load on a crafted
> USB/SD/netboot rootfs). Either one crashes the bootloader (DoS); patch 2 can
> also copy adjacent heap memory into the loaded file (information disclosure).
> 
>    1/2 sqfs_find_inode()/sqfs_inode_size() walk the decompressed inode table
>        using on-disk sizes with no check that the cursor stays inside the
>        buffer -> wild read / SEGV, e.g. from a simple "ls".
>    2/2 sqfs_read_nest() uses the on-disk fragment offset as an unbounded
>        source index into the fragment block -> out-of-bounds heap read when
>        loading a fragment-backed file.
> 
> Both were found by fuzzing the sandbox build (CONFIG_ASAN) of sqfsls/sqfsload
> with mutated images. With the fixes, the crashing inputs are rejected
> cleanly, 2000 fuzz iterations produce no further crashes, and the valid-image
> path is unchanged.
> 
> These are distinct from the 2024 SquashFS CVE cluster (CVE-2024-57254..57259,
> fixed in 2025.01-rc1) and from the sqfs_frag_lookup() fix (e365a269df5): the
> earlier work added NULL checks at the callers and fixed the symlink-size and
> fragment-table paths, but left these inode-table-walk and fragment-data
> paths unbounded.
> 
> The two patches are independent and can be applied in either order.
> 
> Piyush Paliwal (2):
>    fs/squashfs: bound the inode table walk in sqfs_find_inode()
>    fs/squashfs: bound fragment offset/size in sqfs_read_nest()
> 
>   fs/squashfs/sqfs.c            |  50 ++++++++++++----
>   fs/squashfs/sqfs_filesystem.h |   6 +-
>   fs/squashfs/sqfs_inode.c      | 106 +++++++++++++++++++++++++++++-----
>   3 files changed, 134 insertions(+), 28 deletions(-)
> 
Those patches look good.

NB: a patch correcting the same issue as patch 1 was sent 12 hours 
earlier by Hem Parekh, but it fixes less problems than this one.

Reviewed-by: Richard Genoud <richard.genoud@bootlin.com>

Thanks!

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-25  8:08 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12  7:54 [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Piyush Paliwal
2026-06-12  7:54 ` [PATCH 1/2] fs/squashfs: bound the inode table walk in sqfs_find_inode() Piyush Paliwal
2026-06-12  7:54 ` [PATCH 2/2] fs/squashfs: bound fragment offset/size in sqfs_read_nest() Piyush Paliwal
2026-06-25  8:08 ` [PATCH 0/2] fs/squashfs: fix two out-of-bounds reads on crafted images Richard GENOUD

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.