[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