[PATCH v1 1/2] fs/squashfs: fix heap exhaustion during symlink resolution

Richard GENOUD richard.genoud at bootlin.com
Fri May 22 15:28:44 CEST 2026


Le 14/05/2026 à 20:18, Allan ELKAIM a écrit :
> When sqfs_read_nest() encounters a symlink it resolves it by calling
> itself recursively. In the unfixed code this looks like:
> 
>    // dirsp is open: inode_table + dir_table still on heap
>    resolved = sqfs_resolve_symlink(symlink, filename);
>    ret = sqfs_read_nest(resolved, ...); // recursive: allocates a new
>                                         // inode_table + dir_table pair
>    free(resolved);
>    goto out;
>    // out: sqfs_closedir(dirsp) <- parent tables freed HERE, too late
> 
> There is no permanent leak: the parent's tables are freed at the
> out: label once the recursive call returns. However, for the entire
> duration of the recursive call both the parent's inode_table +
> dir_table and the child's inode_table + dir_table are live on the
> heap simultaneously. On large squashfs images these tables can be
> significant in size, and this temporary double allocation may exhaust
> the heap budget.
> 
> A superficial workaround would be to increase CONFIG_SYS_MALLOC_LEN,
> but that wastes memory on all boards and does not address the
> structural problem. The correct fix is to change the freeing order:
> release the parent directory's resources before recursing. This way
> only one set of inode and directory tables is live at any given time,
> halving the peak heap usage during symlink resolution.
> 
> When heap exhaustion does occur and malloc returns NULL for dir_table
> or pos_list inside sqfs_read_directory_table(), the failure is
> currently silent and cascading:
> 
>    - metablks_count is not reset to -1 before the goto out, so the
>      function returns a positive block count alongside a NULL pointer.
>    - sqfs_opendir_nest() does not detect the failure (it only checks
>      metablks_count < 1) and calls sqfs_search_dir() with m_list=NULL.
>    - sqfs_dir_offset() iterates over m_list[0..n], reading from
>      addresses 0x0, 0x4, 0x8, ... None of those values match the
>      inode's start_block, so the function returns -EINVAL.
>    - The error propagates up as a load failure with no indication
>      that the root cause was heap exhaustion:
> 
>        Error: invalid inode reference to directory table.
>        Failed to load '<symlink path>'
> 
> Two fixes:
> 1. In sqfs_read_directory_table(), set metablks_count = -1 whenever
>     malloc fails after sqfs_count_metablks() returns a positive value,
>     so that the caller's "metablks_count < 1" check correctly detects
>     the failure and avoids calling sqfs_search_dir() with a NULL
>     pos_list.
> 2. In sqfs_read_nest() and sqfs_size_nest(), call sqfs_closedir() on
>     the parent dirsp before the recursive call so that the parent's
>     inode and directory tables are freed before the child allocates
>     its own. Only one set of tables is then live at any given time,
>     halving peak heap usage during symlink resolution.
> 
> Link: https://lists.denx.de/pipermail/u-boot/2026-May/618533.html
> 
This looks great!

Reviewed-by: Richard Genoud <richard.genoud at bootlin.com>
> Signed-off-by: Allan ELKAIM <allan.elkaim at gmail.com>
> ---
> 
>   fs/squashfs/sqfs.c | 22 ++++++++++++++++++++--
>   1 file changed, 20 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
> index 9cb8b4af..07e2bd82 100644
> --- a/fs/squashfs/sqfs.c
> +++ b/fs/squashfs/sqfs.c
> @@ -853,12 +853,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 +1477,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 +1744,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;

Thanks!


More information about the U-Boot mailing list