Skip to content

Commit 6fa70b5

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. But, to avoid other potential or unforeseen problems, it is probably best to save and restore the entire lexer state struct, not just that one 'first' pointer. src/cmd/ksh93/sh/macro.c: sh_machere(): - When executing a command substitution or arithmetic expression from a here-document, save and restore the lexer state. Thanks to @stefanbidi for the bug report. Resolves: #823
1 parent ea42e0f commit 6fa70b5

File tree

4 files changed

+41
-1
lines changed

4 files changed

+41
-1
lines changed

NEWS

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

5+
2025-03-23:
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-23" /* 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

+9
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,17 @@ void sh_machere(Sfio_t *infile, Sfio_t *outfile, char *string)
381381
break;
382382
}
383383
case S_PAR:
384+
{
385+
/*
386+
* Execute a command substitution or arithmetic expression from a here-document. As both
387+
* these and here-documents involve lexical analysis at runtime, save and restore the
388+
* lexer state to avoid hard-to-trace invalid pointers down the line in case of nesting.
389+
*/
390+
Lex_t sav = *lp;
384391
comsubst(mp,NULL,1);
392+
*lp = sav;
385393
break;
394+
}
386395
case S_EOF:
387396
if((c=fcfill()) > 0)
388397
goto again;

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)