Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 05 Aug 2026 20:45:00 +0000
From:      Jilles Tjoelker <jilles@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org
Cc:        Kristofer Peterson <kris@tranception.com>
Subject:   git: 0fb06da11083 - stable/14 - bin/sh: Fix history long line truncation/corruption
Message-ID:  <6a73a0cc.3f870.762b77e@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch stable/14 has been updated by jilles:

URL: https://cgit.FreeBSD.org/src/commit/?id=0fb06da11083bed1ca1cab0332183492e5b665ec

commit 0fb06da11083bed1ca1cab0332183492e5b665ec
Author:     Kristofer Peterson <kris@tranception.com>
AuthorDate: 2026-02-16 15:53:47 +0000
Commit:     Jilles Tjoelker <jilles@FreeBSD.org>
CommitDate: 2026-08-05 20:44:03 +0000

    bin/sh: Fix history long line truncation/corruption
    
    When reading from standard input with editline history enabled, increase
    buffer size to accomodate long lines so that history is recorded
    correctly. Cleanup el_gets() handling avoiding potentially dangerous
    retention of pointers to editline buffers across calls.  Ensure struct
    parsefile objects are properly zero initialised when created. Remove
    push argument from setinputstring() and simplify logic as it was always
    called with a value of one and as was written was potentially dangerous
    if ever called with a value of zero.
    
    This commit does not fix long lines when history is enabled but editing
    is not (e.g. if there is no terminal).
    
    MFC after:      3 weeks
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2028
    Signed-off-by: Kristofer Peterson <kris@tranception.com>
    (cherry picked from commit 95e4fce8f0c4fc6bf828288b1d63faf0f1300198)
---
 bin/sh/eval.c   |   2 +-
 bin/sh/input.c  | 146 ++++++++++++++++++++++++++++----------------------------
 bin/sh/input.h  |   2 +-
 bin/sh/parser.c |   4 +-
 4 files changed, 76 insertions(+), 78 deletions(-)

diff --git a/bin/sh/eval.c b/bin/sh/eval.c
index 778aa60c52f1..dd921f60334f 100644
--- a/bin/sh/eval.c
+++ b/bin/sh/eval.c
@@ -157,7 +157,7 @@ evalstring(const char *s, int flags)
 	flags &= ~EV_EXIT;
 	any = 0;
 	setstackmark(&smark);
-	setinputstring(s, 1);
+	setinputstring(s);
 	while ((n = parsecmd(0)) != NEOF) {
 		if (n != NULL && !nflag) {
 			if (flags_exit && preadateof())
diff --git a/bin/sh/input.c b/bin/sh/input.c
index 2ba4c7dd9e00..607bf5df4830 100644
--- a/bin/sh/input.c
+++ b/bin/sh/input.c
@@ -85,6 +85,7 @@ struct parsefile {
 	int lleft;		/* number of lines left in this buffer */
 	const char *nextc;	/* next char in buffer */
 	char *buf;		/* input buffer */
+	size_t bufsize;		/* input buffer size */
 	struct strpush *strpush; /* for pushing strings at this level */
 	struct strpush basestrpush; /* so pushing one is fast */
 };
@@ -97,7 +98,8 @@ const char *parsenextc;		/* copy of parsefile->nextc */
 static char basebuf[BUFSIZ + 1];/* buffer for top level input file */
 static struct parsefile basepf = {	/* top level input file */
 	.nextc = basebuf,
-	.buf = basebuf
+	.buf = basebuf,
+	.bufsize = sizeof(basebuf),
 };
 static struct parsefile *parsefile = &basepf;	/* current input file */
 int whichprompt;		/* 1 == PS1, 2 == PS2 */
@@ -131,52 +133,61 @@ static int
 preadfd(void)
 {
 	int nr;
-	parsenextc = parsefile->buf;
 
 retry:
 #ifndef NO_HISTORY
 	if (parsefile->fd == 0 && el) {
-		static const char *rl_cp;
-		static int el_len;
-
-		if (rl_cp == NULL) {
-			el_resize(el);
-			rl_cp = el_gets(el, &el_len);
-		}
-		if (rl_cp == NULL)
-			nr = el_len == 0 ? 0 : -1;
-		else {
-			nr = el_len;
-			if (nr > BUFSIZ)
-				nr = BUFSIZ;
-			memcpy(parsefile->buf, rl_cp, nr);
-			if (nr != el_len) {
-				el_len -= nr;
-				rl_cp += nr;
-			} else
-				rl_cp = NULL;
+		const char *line;
+
+		el_resize(el);
+		line = el_gets(el, &nr);
+		if (nr > 0 && parsefile->bufsize < (size_t)nr + 1) {
+			size_t bufsize;
+
+			INTOFF;
+			if (parsefile->buf != basebuf) {
+				ckfree(parsefile->buf);
+				parsefile->buf = NULL;
+				parsefile->bufsize = 0;
+			}
+			bufsize = (size_t)nr + BUFSIZ + 1;
+			bufsize -= bufsize % BUFSIZ;
+			parsefile->buf = ckmalloc(bufsize);
+			parsefile->bufsize = bufsize;
+			INTON;
 		}
+		if (nr > 0 && line != NULL)
+			memcpy(parsefile->buf, line, nr);
+		else
+			nr = nr ? -1 : 0;
 	} else
 #endif
-		nr = read(parsefile->fd, parsefile->buf, BUFSIZ);
-
-	if (nr <= 0) {
-                if (nr < 0) {
-                        if (errno == EINTR)
-                                goto retry;
-                        if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
-                                int flags = fcntl(0, F_GETFL, 0);
-                                if (flags >= 0 && flags & O_NONBLOCK) {
-                                        flags &=~ O_NONBLOCK;
-                                        if (fcntl(0, F_SETFL, flags) >= 0) {
-						out2fmt_flush("sh: turning off NDELAY mode\n");
-                                                goto retry;
-                                        }
-                                }
-                        }
+	nr = read(parsefile->fd, parsefile->buf, parsefile->bufsize - 1);
+
+	if (nr < 0)
+		switch (errno) {
+			int flags;
+
+		case EINTR:
+			goto retry;
+		case EWOULDBLOCK:
+			if (parsefile->fd != 0)
+				break;
+			if ((flags = fcntl(0, F_GETFL, 0)) < 0)
+				break;
+			if (!(flags & O_NONBLOCK))
+				break;
+			if (fcntl(0, F_SETFL, flags & ~O_NONBLOCK) < 0)
+				break;
+			out2fmt_flush("sh: turning off NDELAY mode\n");
+			goto retry;
                 }
-                nr = -1;
-	}
+	else if (nr > 0)
+		parsefile->buf[nr] = '\0';
+	else
+		nr = -1;
+
+	parsenextc = parsefile->buf;
 	return nr;
 }
 
@@ -193,7 +204,8 @@ retry:
 int
 preadbuffer(void)
 {
-	char *p, *q, *r, *end;
+	const char *end;
+	char *q, *r;
 	char savec;
 
 	while (parsefile->strpush) {
@@ -212,31 +224,22 @@ preadbuffer(void)
 		return PEOF;
 
 again:
-	if (parselleft <= 0) {
-		if ((parselleft = preadfd()) == -1) {
-			parselleft = parsenleft = EOF_NLEFT;
-			return PEOF;
-		}
+	if (parselleft <= 0 && (parselleft = preadfd()) == -1) {
+		parselleft = parsenleft = EOF_NLEFT;
+		return (PEOF);
 	}
-
-	p = parsefile->buf + (parsenextc - parsefile->buf);
-	end = p + parselleft;
-	*end = '\0';
-	q = strchrnul(p, '\n');
-	if (q != end && *q == '\0') {
+	end = parsenextc + parselleft;
+	q = strchrnul(parsenextc, '\n');
+	if (*q == '\0' && q != end) {
 		/* delete nul characters */
-		for (r = q; q != end; q++) {
+		for (r = q++; q != end; q++)
 			if (*q != '\0')
 				*r++ = *q;
-		}
-		parselleft -= end - r;
-		if (parselleft == 0)
-			goto again;
-		end = p + parselleft;
-		*end = '\0';
-		q = strchrnul(p, '\n');
+		*r = '\0';
+		parselleft = r - parsenextc;
+		goto again;
 	}
-	if (q == end) {
+	if (*q == '\0') {
 		parsenleft = parselleft;
 		parselleft = 0;
 	} else /* *q == '\n' */ {
@@ -311,7 +314,7 @@ pushstring(const char *s, int len, struct alias *ap)
 	INTOFF;
 /*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/
 	if (parsefile->strpush) {
-		sp = ckmalloc(sizeof (struct strpush));
+		sp = ckmalloc(sizeof(struct strpush));
 		sp->prev = parsefile->strpush;
 		parsefile->strpush = sp;
 	} else
@@ -395,15 +398,15 @@ setinputfile(const char *fname, int push, int verify)
 void
 setinputfd(int fd, int push)
 {
-	if (push) {
+	if (push)
 		pushfile();
-		parsefile->buf = ckmalloc(BUFSIZ + 1);
-	}
 	if (parsefile->fd > 0)
 		close(parsefile->fd);
 	parsefile->fd = fd;
-	if (parsefile->buf == NULL)
+	if (parsefile->buf == NULL) {
 		parsefile->buf = ckmalloc(BUFSIZ + 1);
+		parsefile->bufsize = BUFSIZ + 1;
+	}
 	parselleft = parsenleft = 0;
 	plinno = 1;
 }
@@ -414,14 +417,12 @@ setinputfd(int fd, int push)
  */
 
 void
-setinputstring(const char *string, int push)
+setinputstring(const char *string)
 {
 	INTOFF;
-	if (push)
-		pushfile();
+	pushfile();
 	parsenextc = string;
 	parselleft = parsenleft = strlen(string);
-	parsefile->buf = NULL;
 	plinno = 1;
 	INTON;
 }
@@ -438,15 +439,12 @@ pushfile(void)
 {
 	struct parsefile *pf;
 
+	pf = (struct parsefile *)ckmalloc(sizeof(struct parsefile));
+	*pf = (struct parsefile){ .prev = parsefile, .fd = -1 };
 	parsefile->nleft = parsenleft;
 	parsefile->lleft = parselleft;
 	parsefile->nextc = parsenextc;
 	parsefile->linno = plinno;
-	pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
-	pf->prev = parsefile;
-	pf->fd = -1;
-	pf->strpush = NULL;
-	pf->basestrpush.prev = NULL;
 	parsefile = pf;
 }
 
diff --git a/bin/sh/input.h b/bin/sh/input.h
index b46e2c021e70..e0abdc0cc337 100644
--- a/bin/sh/input.h
+++ b/bin/sh/input.h
@@ -54,7 +54,7 @@ void pungetc(void);
 void pushstring(const char *, int, struct alias *);
 void setinputfile(const char *, int, int);
 void setinputfd(int, int);
-void setinputstring(const char *, int);
+void setinputstring(const char *);
 void popfile(void);
 struct parsefile *getcurrentfile(void);
 void popfilesupto(struct parsefile *);
diff --git a/bin/sh/parser.c b/bin/sh/parser.c
index 044233da5d73..fa3650494da7 100644
--- a/bin/sh/parser.c
+++ b/bin/sh/parser.c
@@ -1170,7 +1170,7 @@ parsebackq(char *out, struct nodelist **pbqlist,
 		INTOFF;
 		ostr = ckmalloc(olen);
 		memcpy(ostr, stackblock(), olen);
-		setinputstring(ostr, 1);
+		setinputstring(ostr);
 		INTON;
         }
 	nlpp = pbqlist;
@@ -2202,7 +2202,7 @@ expandstr(const char *ps)
 	if (!setjmp(jmploc.loc)) {
 		handler = &jmploc;
 		parser_temp = NULL;
-		setinputstring(ps, 1);
+		setinputstring(ps);
 		doprompt = 0;
 		readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0);
 		if (backquotelist != NULL)


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a73a0cc.3f870.762b77e>