Merge patch series "fs/squashfs: fix symlink load failure on large images"
Allan ELKAIM <allan.elkaim@gmail.com> 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/20260713142420.2618339-1-allan.elkaim@gmail.com
This commit is contained in:
+38
-8
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user