Skip to content

Commit dfb699b

Browse files
committed
Fix two more PS2/SIGINT crashing bugs (re: 3023d53)
*** Crash 1: *** ksh crashed if the PS1 prompt contains one or more command substitutions and you enter a multi-line command substitution on the command line, then interrupt while on the PS2 prompt. $ ENV=/./dev/null /usr/local/bin/ksh -o emacs $ PS1='$(echo foo) $(echo bar) $(echo baz) ! % ' foo bar baz 16999 % echo $( > true <-- here, press Ctrl+C instead of Return Memory fault The crash occurred due to a corrupted lexer state while trying to display the PS1 prompt. Analysis: My fix for the crashing bug with Ctrl+C in commit 3023d53 is incorrect and only worked accidentally. sh_fault() is not the right place to reset the lexer state because, when we press Ctrl+C on a PS2 prompt, ksh had been waiting for input to finish lexing a multi-line command, so sh_lex() and other lexer functions are on the function call stack and will be returned to. src/cmd/ksh93/sh/fault.c: sh_fault(): - Remove incorrect SIGINT fix. src/cmd/ksh93/sh/io.c: io_prompt(): - Reset the lexer state immediately before printing every PS1 prompt. Even in situations where this is redundant it should be perfectly safe, the overhead is negligible, and it resolves this crash. It may pre-empt other problems as well. *** Crash 2: *** If an INT trap is set, and you start entering a multi-line command substitution, then press Ctrl+C on the PS2 prompt to trigger the crash, the lexer state is corrupted because the lexer is invoked to eval the trap action. A crash then occurs on entering the final ')' of the command substitution. $ trap 'echo TRAPPED' INT $ echo $( > trueTRAPPED <-- press Ctrl+C to output "TRAPPED" > ) Memory fault Technically, as SIGINT is trapped, it should not interrupt, so ksh should execute the trap, then continue with the PS2 prompt to let the user finish inputting the command. But I have been unsuccessful in many different attempts to make this work properly. I managed to get multi-line command substitutions to lex correctly by saving and restoring the lexer state, but command substitutions were still corrupted at the parser and/or execution level and I have not managed to trace the cause of that. My testing showed that all other shells interrupt the PS2 prompt and return to PS1 when the user presses Ctrl+C, even if SIGINT is trapped. I think that is a reasonable alternative, and it is something I managed to make work. src/cmd/ksh93/sh/fault.c: sh_chktrap(): - Immediately after invoking sh_trap() to run a trap action, check if we're in a PS2 prompt (sh.nextprompt == 2). If so, assume the lexer state is now overwritten. Closing the fcin stream with fcclose() seems to reliably force the lexer to stop doing anything else. Then we can just reset the prompt to PS1 and invoke sh_exit() to start new command line, which will now reset the lexer state as per above.
1 parent f0e5460 commit dfb699b

5 files changed

Lines changed: 19 additions & 4 deletions

File tree

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ For full details, see the git log at: https://github.com/ksh93/ksh
33

44
Any uppercase BUG_* names are modernish shell bug IDs.
55

6+
2021-12-11:
7+
8+
- Fixed two more crashing bugs that occurred if ksh received a signal (such
9+
as SIGINT due to Ctrl+C) while the user is entering a multi-line command
10+
substitution in an interactive shell.
11+
612
2021-12-09:
713

814
- Increased the general robustness of discipline function handling, fixing

src/cmd/ksh93/Mamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,7 @@ make install
924924
prev include/edit.h implicit
925925
prev include/history.h implicit
926926
prev include/shnodes.h implicit
927+
prev include/shlex.h implicit
927928
prev include/jobs.h implicit
928929
prev include/io.h implicit
929930
prev include/path.h implicit

src/cmd/ksh93/include/version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
2323
#define SH_RELEASE_SVER "1.1.0-alpha" /* semantic version number: https://semver.org */
24-
#define SH_RELEASE_DATE "2021-12-09" /* must be in this format for $((.sh.version)) */
24+
#define SH_RELEASE_DATE "2021-12-11" /* must be in this format for $((.sh.version)) */
2525
#define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK
2626

2727
/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */

src/cmd/ksh93/sh/fault.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,6 @@ void sh_fault(register int sig)
6868
register char *trap;
6969
register struct checkpt *pp = (struct checkpt*)shp->jmplist;
7070
int action=0;
71-
/* reset lexer state on Ctrl+C */
72-
if(sh_isstate(SH_INTERACTIVE) && sig==SIGINT)
73-
sh_lexopen(sh.lex_context, &sh, 0);
7471
/* reset handler */
7572
if(!(sig&SH_TRAP))
7673
signal(sig, sh_fault);
@@ -437,6 +434,15 @@ void sh_chktrap(Shell_t* shp)
437434
cursig = sig;
438435
sh_trap(trap,0);
439436
cursig = -1;
437+
/* If we're in a PS2 prompt, then we just parsed and executed a trap in the middle of parsing
438+
* another command, so the lexer state is overwritten. Escape to avoid crashing the lexer. */
439+
if(sh.nextprompt == 2)
440+
{
441+
fcclose(); /* force lexer to abort partial command */
442+
sh.nextprompt = 1; /* next display prompt is PS1 */
443+
sh.lastsig = sig; /* make sh_exit() set $? to signal exit status */
444+
sh_exit(SH_EXITSIG); /* start a new command line */
445+
}
440446
}
441447
}
442448
}

src/cmd/ksh93/sh/io.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "path.h"
3838
#include "io.h"
3939
#include "jobs.h"
40+
#include "shlex.h"
4041
#include "shnodes.h"
4142
#include "history.h"
4243
#include "edit.h"
@@ -2154,6 +2155,7 @@ static int io_prompt(Shell_t *shp,Sfio_t *iop,register int flag)
21542155
case 1:
21552156
{
21562157
register int c;
2158+
sh_lexopen(sh.lex_context, &sh, 0); /* reset lexer state */
21572159
#if defined(TIOCLBIC) && defined(LFLUSHO)
21582160
if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
21592161
{

0 commit comments

Comments
 (0)