Skip to content

Commit 2eb6f3a

Browse files
committed
Fix crash on reading a here-doc within a comsub within a here-doc
Minimal reproducer: TEST_LINE="Test line" cat <<EOF $( cat <<EOF2 ${TEST_LINE} EOF2 ) EOF This script crashes on (at least) Alpine Linux with musl libc. I have not been able to get it to crash on any other system, even with ASan or valgrind, but still, this indicates a problem. The relevant part of the gdb(1) stack trace on that system is: #0 memcpy () at src/string/aarch64/memcpy.S:145 #1 0x0000aaaaaabd4d7c in sfwrite (f=0xaaaaaac813d8 <_Stak_data>, buf=0xfffff7eb127b, n=225211) at /usr/local/src/ksh/src/lib/libast/sfio/sfwrite.c:130 #2 0x0000aaaaaaae7f28 in lex_advance (iop=0xfffff7fe0600, buff=0xfffff7eb127b <error: Cannot access memory at address 0xfffff7eb127b>, size=225211, context=0xaaaaaac8aae0) at /usr/local/src/ksh/src/cmd/ksh93/sh/lex.c:161 #3 0x0000aaaaaaad10e8 in fcfill () at /usr/local/src/ksh/src/cmd/ksh93/sh/fcin.c:98 #4 0x0000aaaaaaaf17a0 in sh_machere (infile=0xfffff7fe0600, outfile=0xfffff7fe3140, string=0xfffff7fe3c51 "EOF") at /usr/local/src/ksh/src/cmd/ksh93/sh/macro.c:319 #5 0x0000aaaaaaadf64c in io_heredoc (iop=0xfffff7fe3c00, name=0xfffff7fe3c51 "EOF", traceon=1) at /usr/local/src/ksh/src/cmd/ksh93/sh/io.c:1632 #6 0x0000aaaaaaade36c in sh_redirect (iop=0xfffff7fe3c00, flag=0) at /usr/local/src/ksh/src/cmd/ksh93/sh/io.c:1245 #7 0x0000aaaaaab27898 in sh_exec (t=0xfffff7fe3bc0, flags=4) at /usr/local/src/ksh/src/cmd/ksh93/sh/xec.c:1176 So, as we can see in #2, the problem is an invalid pointer. It becomes invalid in lex_advance() in lex.c on line 152, which is: buff = lp->lexd.first; So it looks like the lp->lexd.first pointer is somehow invalidated. Analysis: The execution of both here-documents and command substitutions involves parsing and lexical analysis at runtime. A command substitution combined with nested execution of here- documents causes lp->lexd.first to be changed, without being restored when the nested execution returns. So, this suggests that the fix would be to save that pointer before calling comsubst() from sh_machere() to execute a command substitution from within a here-document, and restore it after. However, restoring it causes another, very similar crash when running the regression tests from the modernish shell library. So we can't rely on that pointer being valid either way. src/cmd/ksh93/sh/macro.c: sh_machere(): - When executing a command substitution or arithmetic expression from a here-document, reset lp->lexd.first to NULL to avoid a potentially invalid address being used in lex_advance(). The lex.c code already handles the case of it being NULL. Thanks to @stefanbidi for the bug report. Resolves: #823
1 parent 1a94bc5 commit 2eb6f3a

File tree

4 files changed

+37
-1
lines changed

4 files changed

+37
-1
lines changed

NEWS

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ This documents significant changes in the 1.0 branch of ksh 93u+m.
22
For full details, see the git log at: https://github.com/ksh93/ksh/tree/1.0
33
Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.
44

5+
2025-03-24:
6+
7+
- Fixed a crash that occurred in specific circumstances when processing a
8+
here-documnet within a command subtitution within another here-document.
9+
510
2025-03-21:
611

712
- Fixed a corner case bug in the "$*" expansion: spurious backslashes were

src/cmd/ksh93/include/version.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <ast_release.h>
1919
#include "git.h"
2020

21-
#define SH_RELEASE_DATE "2025-03-22" /* must be in this format for $((.sh.version)) */
21+
#define SH_RELEASE_DATE "2025-03-24" /* must be in this format for $((.sh.version)) */
2222
/*
2323
* This comment keeps SH_RELEASE_DATE a few lines away from SH_RELEASE_SVER to avoid
2424
* merge conflicts when cherry-picking dev branch commits onto a release branch.

src/cmd/ksh93/sh/macro.c

+5
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,12 @@ void sh_machere(Sfio_t *infile, Sfio_t *outfile, char *string)
381381
break;
382382
}
383383
case S_PAR:
384+
/* Execute a command substitution or arithmetic expression from a here-document */
384385
comsubst(mp,NULL,1);
386+
/* Because comsubs and here-documents both do lexical analysis at runtime, lp->lexd.first
387+
* may become invalid if these are nested, which may crash lex_advance(), triggered by
388+
* fcfill() below. To avoid that, reset it (lex.c handles this correctly). */
389+
lp->lexd.first = NULL;
385390
break;
386391
case S_EOF:
387392
if((c=fcfill()) > 0)

src/cmd/ksh93/tests/heredoc.sh

+26
Original file line numberDiff line numberDiff line change
@@ -554,5 +554,31 @@ _EOF"; } 2>&1 )
554554
"(expected status 0, '$exp';" \
555555
"got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))"
556556
557+
# ======
558+
# potential crash when here-documents and command substitutions are nested
559+
# https://github.com/ksh93/ksh/issues/823
560+
got=$(set +x; { "$SHELL" -c 'TEST_LINE1="Test line 1"
561+
TEST_LINE2="Test line 2"
562+
function1 () {
563+
cat << EOF
564+
${TEST_LINE1}
565+
EOF
566+
}
567+
function2 () {
568+
cat << EOF
569+
${TEST_LINE2}
570+
EOF
571+
}
572+
function3 () {
573+
cat << EOF
574+
$(function1)
575+
EOF
576+
function2
577+
}
578+
function3'; } 2>&1)
579+
exp=$'\t\tTest line 1\n\tTest line 2'
580+
[[ $got == "$exp" ]] || err_exit "processing a here-document from a command substitution in a here-document" \
581+
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
582+
557583
# ======
558584
exit $((Errors<125?Errors:125))

0 commit comments

Comments
 (0)