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

Sanghyun Park via busybox <[email protected]> Tue, 16 Jun 2026 23:43:40 +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.

Limit each saved IFS-region scan to the current stack block, skip stale
regions that are now outside that bound, and make the final empty-string
check use the same bound.

Signed-off-by: Sanghyun Park <[email protected]>
---
v2:
- Drop the unsupported CTLESC-at-region-end explanation and guards.
- Explain the stale IFS region state left after longjmp from argstr().
- Keep the fix scoped to bounding stale saved regions to the current
  stack block.
v1: https://lists.busybox.net/pipermail/busybox/2026-June/092353.html

 shell/ash.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/shell/ash.c b/shell/ash.c
index fb887f3..366559a 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6412,13 +6412,16 @@ ifsbreakup(char *string, struct arglist *arglist)
 	struct ifsregion *ifsp;
 	struct strlist *sp;
 	char *start;
+	char *end;
 	char *p;
 	char *q;
+	char *stack_end;
 	const char *ifs, *realifs;
 	int ifsspc;
 	int nulonly;
 
 	start = string;
+	stack_end = (char *)stackblock() + stackblocksize();
 	if (ifslastp != NULL) {
 		ifsspc = 0;
 		nulonly = 0;
@@ -6428,11 +6431,18 @@ ifsbreakup(char *string, struct arglist *arglist)
 			int afternul;
 
 			p = string + ifsp->begoff;
+			end = string + ifsp->endoff;
+			if (end > stack_end)
+				end = stack_end;
+			if (p >= end) {
+				ifsp = ifsp->next;
+				continue;
+			}
 			afternul = nulonly;
 			nulonly = ifsp->nulonly;
 			ifs = nulonly ? nullstr : realifs;
 			ifsspc = 0;
-			while (p < string + ifsp->endoff) {
+			while (p < end) {
 				q = p;
 				if ((unsigned char)*p == CTLESC)
 					p++;
@@ -6456,7 +6466,7 @@ ifsbreakup(char *string, struct arglist *arglist)
 				p++;
 				if (!nulonly) {
 					for (;;) {
-						if (p >= string + ifsp->endoff) {
+						if (p >= end) {
 							break;
 						}
 						q = p;
@@ -6486,7 +6496,7 @@ ifsbreakup(char *string, struct arglist *arglist)
 			goto add;
 	}
 
-	if (!*start)
+	if (start >= stack_end || !*start)
 		return;
 
  add:
-- 
2.48.1