diff options
| author | Tom Rini <[email protected]> | 2026-07-25 07:55:35 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2026-07-25 07:55:35 -0600 |
| commit | b635d43bca429500cb8ef20aa151cb5773b9a8a5 (patch) | |
| tree | ba7cad4ef1b7c4b10e2f720a13b2d672ee3fb7b3 | |
| parent | d99ad807509dcb7197a33261a269b5144e5e4acc (diff) | |
| parent | 171b604888537dd7112ffddaa13abb16932eabd0 (diff) | |
Merge patch series "fs/squashfs: fix symlink load failure on large images"
Allan ELKAIM <[email protected]> says:
sqfsload fails to load a file through a symlink when the squashfs
image contains a large number of inodes (e.g. a rootfs that includes
the tzdata timezone database).
Root cause: sqfs_read_nest() resolves the symlink by calling itself
recursively without first freeing the parent directory's inode and
directory table buffers. This causes a temporary double allocation
that can exhaust the U-Boot heap. When malloc() subsequently fails
inside sqfs_read_directory_table(), the error goes undetected and
sqfs_search_dir() is called with a NULL pos_list pointer, leading to:
Error: invalid inode reference to directory table.
Failed to load '/boot/Image'
Patch 1 fixes the structural problem (temporary double allocation)
and plugs the silent NULL pointer path in sqfs_read_directory_table().
Patch 2 adds the missing return-value checks on sqfs_dir_offset() that
turn any residual lookup failure into a clean error propagation.
Patch 3 (reworked in v3 following Richard Genoud's review) fixes
pre-existing leaks of dirs->entry on the error paths of
sqfs_search_dir(), by centralizing the cleanup at the 'out' label.
All patches are independent and can be reviewed separately.
The bug was first observed on U-Boot v2024.01 and is still present
on v2026.04. The patches have been tested on a Raspberry Pi CM4
running U-Boot v2026.04 (Yocto Scarthgap 5.0.17) with a 325 MB
squashfs rootfs containing 22 517 inodes. The symlink
/boot/Image -> Image-6.6.63-v8 now resolves successfully.
This series addresses the bug reported at:
https://lists.u-boot-project.org/pipermail/u-boot/2026-May/618533.html
Link: https://lore.kernel.org/r/[email protected]
| -rw-r--r-- | fs/squashfs/sqfs.c | 46 |
1 files changed, 38 insertions, 8 deletions
diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c index 0768fc4a7b2..55fbe1bcc1a 100644 --- a/fs/squashfs/sqfs.c +++ b/fs/squashfs/sqfs.c @@ -496,6 +496,8 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, /* get directory offset in directory table */ offset = sqfs_dir_offset(table, m_list, m_count); + if (offset < 0) + return offset; dirs->table = &dirs->dir_table[offset]; /* Setup directory header */ @@ -545,8 +547,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, /* Get reference to inode in the inode table */ table = sqfs_find_inode(dirs->inode_table, new_inode_number, sblk->inodes, sblk->block_size); - if (!table) - return -EINVAL; + if (!table) { + ret = -EINVAL; + goto out; + } dir = (struct squashfs_dir_inode *)table; /* Check for symbolic link and inode type sanity */ @@ -615,8 +619,6 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, goto out; } else if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) { printf("** Cannot find directory. **\n"); - free(dirs->entry); - dirs->entry = NULL; ret = -EINVAL; goto out; } @@ -627,6 +629,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, /* Get dir. offset into the directory table */ offset = sqfs_dir_offset(table, m_list, m_count); + if (offset < 0) { + ret = offset; + goto out; + } dirs->table = &dirs->dir_table[offset]; /* Copy directory header */ @@ -636,8 +642,6 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, /* Check for empty directory */ if (sqfs_is_empty_dir(table)) { printf("Empty directory.\n"); - free(dirs->entry); - dirs->entry = NULL; ret = SQFS_EMPTY_DIR; goto out; } @@ -651,6 +655,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, } offset = sqfs_dir_offset(table, m_list, m_count); + if (offset < 0) { + ret = offset; + goto out; + } dirs->table = &dirs->dir_table[offset]; if (get_unaligned_le16(&dir->inode_type) == SQFS_DIR_TYPE) @@ -659,6 +667,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, memcpy(&dirs->i_ldir, ldir, sizeof(*ldir)); out: + if (ret < 0) { + free(dirs->entry); + dirs->entry = NULL; + } free(res); free(rem); free(path); @@ -853,12 +865,16 @@ static int sqfs_read_directory_table(unsigned char **dir_table, u32 **pos_list) goto out; *dir_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE); - if (!*dir_table) + if (!*dir_table) { + metablks_count = -1; goto out; + } *pos_list = malloc(metablks_count * sizeof(u32)); - if (!*pos_list) + if (!*pos_list) { + metablks_count = -1; goto out; + } ret = sqfs_get_metablk_pos(*pos_list, dtb, table_offset, metablks_count); @@ -1473,6 +1489,15 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset, symlink = (struct squashfs_symlink_inode *)ipos; resolved = sqfs_resolve_symlink(symlink, filename); + /* + * Free the parent directory resources before recursing so that + * the recursive call can allocate its own inode and directory + * tables without exhausting the heap. + */ + free(dirs->entry); + dirs->entry = NULL; + sqfs_closedir(dirsp); + dirsp = NULL; ret = sqfs_read_nest(resolved, buf, offset, len, actread); free(resolved); goto out; @@ -1731,6 +1756,11 @@ static int sqfs_size_nest(const char *filename, loff_t *size) symlink = (struct squashfs_symlink_inode *)ipos; resolved = sqfs_resolve_symlink(symlink, filename); + /* + * Free the parent directory resources before recursing. + */ + sqfs_closedir(dirsp); + dirsp = NULL; ret = sqfs_size(resolved, size); free(resolved); break; |
