Hi Richard,

Le 24/06/2026 à 17:23, Richard GENOUD a écrit :

> Having a 2nd look at the whole function, it seems that there a lot of:
> ret = -Exxx;
> goto out;
> without freeing dirs->entry

You're right, my v2 only fixed one of them. Going through the function
again, the entry is also leaked on the -ELOOP path, on all seven
allocation/tokenization failures in the symlink resolution block, and
in a more subtle case: when sqfs_readdir_nest() aborts via its
sqfs_find_inode() failure path *after* sqfs_read_entry() succeeded,
the while loop exits with a live entry that the "Cannot find
directory" path then leaks.

> It seems to me that we could keep free(dirs->entry):
> - in the while (!sqfs_readdir_nest(dirsp, &dent)) loop
> - at the end of the for (j = 0; j < token_count; j++) loop
> - and before the recursive call to sqfs_search_dir()
>
> And remove all the rest to add something like:
> [...]
> out:
> +       if (ret < 0) {
> +               free(dirs->entry);
> +               dirs->entry = NULL;
> +       }
>
> (not tested)
>
> What do you think?

I agree, this is better than fixing each site individually. I checked
that it holds on every path:

- On success (ret == 0), dirs->entry is provably already NULL: it is
  freed at the end of each token iteration and before recursing into
  a symlink target, and the root-directory early return never
  allocates it. So the conditional free at out: can never take away
  an entry a caller would use (sqfs_opendir_nest() doesn't read it
  after return anyway).

- No double free is possible: every free site resets the pointer to
  NULL, including the recursive case, where the child's out: label
  runs before the parent's.

- The three frees you list are exactly the ones that must stay: the
  first two run on success paths that the "ret < 0" guard at out:
  would skip, and the one before the recursive call is needed because
  the child's sqfs_read_entry() overwrites the pointer without
  freeing it.

- SQFS_EMPTY_DIR is -1, so the empty-directory path is covered by the
  "ret < 0" test like the current explicit free.

I've reworked patch 3 accordingly for v3 (with your Suggested-by),
dropping the now-redundant frees on the individual error paths,
including the two added by patch 2. Tested on sandbox with the
squashfs test suite ("test.py -k sqfs") plus a manual sqfsload
through a symlink on the image that originally triggered the bug
report, and the error paths (missing file, bad directory).

v3 also moves the collected tags before my Signed-off-by, per your
remark on patch 2. Thanks for the review!

Regards,
Allan


Le mer. 24 juin 2026 à 17:24, Richard GENOUD <[email protected]> a
écrit :

> Le 11/06/2026 à 11:43, Allan ELKAIM a écrit :
> > In sqfs_search_dir(), when sqfs_find_inode() fails to locate the inode
> > of a directory entry just returned by sqfs_readdir_nest(), the function
> > returns directly while dirs->entry still holds the entry allocation,
> > leaking it. The bare return also bypasses the regular error path.
> >
> > Free the entry and leave through the out label instead, consistent
> > with the other error paths in this function.
> >
> > Signed-off-by: Allan ELKAIM <[email protected]>
> > ---
> >
> > Changes in v2:
> > - New patch, fixing a pre-existing leak of the same kind as the ones
> >    addressed in patch 2 (suggested by Richard Genoud's review)
> >
> >   fs/squashfs/sqfs.c | 8 ++++++--
> >   1 file changed, 6 insertions(+), 2 deletions(-)
> >
> > diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
> > index af32d008..df988774 100644
> > --- a/fs/squashfs/sqfs.c
> > +++ b/fs/squashfs/sqfs.c
> > @@ -547,8 +547,12 @@ 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) {
> > +                     free(dirs->entry);
> > +                     dirs->entry = NULL;
> > +                     ret = -EINVAL;
> > +                     goto out;
> > +             }
> >               dir = (struct squashfs_dir_inode *)table;
> >
> >               /* Check for symbolic link and inode type sanity */
>
> Having a 2nd look at the whole function, it seems that there a lot of:
> ret = -Exxx;
> goto out;
> without freeing dirs->entry
>
> Like here:
>                         [...]
>                         if (++symlinknest == MAX_SYMLINK_NEST) {
>                                 ret = -ELOOP;
>                                 goto out;
>                         }
>
>                         sym = (struct squashfs_symlink_inode *)table;
>                         /* Get first j + 1 tokens */
>                         path = sqfs_concat_tokens(token_list, j + 1);
>                         if (!path) {
>                                 ret = -ENOMEM;
>                                 goto out;
>                         }
>                         /* Resolve for these tokens */
>                         target = sqfs_resolve_symlink(sym, path);
>                         if (!target) {
>                                 ret = -ENOMEM;
>                                 goto out;
>                         }
>                         [...]
>
> It seems to me that we could keep free(dirs->entry):
> - in the while (!sqfs_readdir_nest(dirsp, &dent)) loop
> - at the end of the for (j = 0; j < token_count; j++) loop
> - and before the recursive call to sqfs_search_dir()
>
> And remove all the rest to add something like:
> @@ -677,6 +677,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);
>
> (not tested)
>
> What do you think?
>
>
> Regards,
> Richard
>

Reply via email to