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

Sanghyun Park via busybox <[email protected]> Thu, 18 Jun 2026 17:04:20 +0900
Newsgroups gmane.linux.busybox
Message-ID <[email protected]>
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 <[email protected]>
---
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