[PATCH v3] ash: fix out-of-bounds read in ifsbreakup()

Denys Vlasenko vda.linux at googlemail.com
Mon Jul 6 08:29:44 UTC 2026


Applied, thank you.

Although the reproducer:

M='AAAAAAAAAAAAAAAAA'
q00(){
<<000;echo
${D?$M$M$M$M$M$M}
000
}
q00
echo Done:$?

does not reproduce the bug for me (works correctly even before the patch).
It does reproduce the problem when run by dash - prints garbage:

$ dash REPRO
zz: 3: D: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
��V�V �V (�V 8�V H�V
Done:0

Were you able to reproduce the problem with busybox?
I'd like to add it to the testsuite.

On Thu, Jun 18, 2026 at 10:05 AM Sanghyun Park via busybox
<busybox at busybox.net> wrote:
>
> ifsfree() does not only release allocated ifsregion nodes; it also clears
> the global IFS region state used by ifsbreakup(). If argstr() raises an
> error while expanding an argument, ash longjmps out of expandarg() before
> that cleanup runs, leaving stale IFS split offsets behind.
>
> A later expansion can reuse the stack for a shorter string. ifsbreakup()
> then sees the stale IFS state, trusts the old offsets, and can walk past
> the current stack block before dereferencing p.
>
> Follow dash's root-cause fix: when an expansion-related handler catches
> EXERROR and continues, restore the handler and call ifsfree(). Apply
> the cleanup to redirectsafe(), expandstr(), and evaltree().
>
> Signed-off-by: Sanghyun Park <sanghyun.park.cnu at gmail.com>
> ---
> v3:
> - Replace the v2 ifsbreakup() bounds check with dash-style ifsfree() cleanup.
> - Cover redirectsafe(), expandstr(), and evaltree().
> v2: https://lists.busybox.net/pipermail/busybox/2026-June/092357.html
> v1: https://lists.busybox.net/pipermail/busybox/2026-June/092353.html
>
>  shell/ash.c | 24 +++++++++++++++---------
>  1 file changed, 15 insertions(+), 9 deletions(-)
>
> diff --git a/shell/ash.c b/shell/ash.c
> index fb887f3..b8ff67b 100644
> --- a/shell/ash.c
> +++ b/shell/ash.c
> @@ -5574,6 +5574,7 @@ write2pipe(int pip[2], const char *p, size_t len)
>
>  /* openhere needs this forward reference */
>  static void expandhere(union node *arg);
> +static void ifsfree(void);
>  static int
>  openhere(union node *redir)
>  {
> @@ -5998,6 +5999,17 @@ redirect(union node *redir, int flags)
>         //      preverrout_fd = copied_fd2;
>  }
>
> +static void
> +restore_handler_expandarg(struct jmploc *savehandler, int err)
> +{
> +       exception_handler = savehandler;
> +       if (err) {
> +               if (exception_type != EXERROR)
> +                       longjmp(exception_handler->loc, 1);
> +               ifsfree();
> +       }
> +}
> +
>  static int
>  redirectsafe(union node *redir, int flags)
>  {
> @@ -6013,9 +6025,7 @@ redirectsafe(union node *redir, int flags)
>                 exception_handler = &jmploc;
>                 redirect(redir, flags);
>         }
> -       exception_handler = savehandler;
> -       if (err && exception_type != EXERROR)
> -               longjmp(exception_handler->loc, 1);
> +       restore_handler_expandarg(savehandler, err);
>         RESTORE_INT(saveint);
>         return err;
>  }
> @@ -9792,9 +9802,7 @@ evaltree(union node *n, int flags)
>                         trap_depth--;
>                         in_trap_ERR = 0;
>
> -                       exception_handler = savehandler;
> -                       if (err && exception_type != EXERROR)
> -                               longjmp(exception_handler->loc, 1);
> +                       restore_handler_expandarg(savehandler, err);
>
>                         exitstatus = savestatus;
>                 }
> @@ -14009,9 +14017,7 @@ expandstr(const char *ps, int syntax_type)
>         result = stackblock();
>
>  out:
> -       exception_handler = savehandler;
> -       if (err && exception_type != EXERROR)
> -               longjmp(exception_handler->loc, 1);
> +       restore_handler_expandarg(savehandler, err);
>
>         doprompt = saveprompt;
>         /* Try: PS1='`xxx(`' */
> --
> 2.48.1
> _______________________________________________
> busybox mailing list
> busybox at busybox.net
> https://lists.busybox.net/mailman/listinfo/busybox


More information about the busybox mailing list