1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

Fix two more PS2/SIGINT crashing bugs (re: 3023d53b)

*** 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
3023d53b 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.
This commit is contained in:
Martijn Dekker 2021-12-11 04:27:54 +01:00
parent 0180a65bbf
commit 65feb9641a
5 changed files with 19 additions and 4 deletions

6
NEWS
View file

@ -3,6 +3,12 @@ For full details, see the git log at: https://github.com/ksh93/ksh
Any uppercase BUG_* names are modernish shell bug IDs. Any uppercase BUG_* names are modernish shell bug IDs.
2021-12-11:
- Fixed two more crashing bugs that occurred if ksh received a signal (such
as SIGINT due to Ctrl+C) while the user is entering a multi-line command
substitution in an interactive shell.
2021-12-09: 2021-12-09:
- Increased the general robustness of discipline function handling, fixing - Increased the general robustness of discipline function handling, fixing

View file

@ -924,6 +924,7 @@ make install
prev include/edit.h implicit prev include/edit.h implicit
prev include/history.h implicit prev include/history.h implicit
prev include/shnodes.h implicit prev include/shnodes.h implicit
prev include/shlex.h implicit
prev include/jobs.h implicit prev include/jobs.h implicit
prev include/io.h implicit prev include/io.h implicit
prev include/path.h implicit prev include/path.h implicit

View file

@ -21,7 +21,7 @@
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
#define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */
#define SH_RELEASE_DATE "2021-12-09" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_DATE "2021-12-11" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK #define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK
/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */

View file

@ -68,9 +68,6 @@ void sh_fault(register int sig)
register char *trap; register char *trap;
register struct checkpt *pp = (struct checkpt*)shp->jmplist; register struct checkpt *pp = (struct checkpt*)shp->jmplist;
int action=0; int action=0;
/* reset lexer state on Ctrl+C */
if(sh_isstate(SH_INTERACTIVE) && sig==SIGINT)
sh_lexopen(sh.lex_context, &sh, 0);
/* reset handler */ /* reset handler */
if(!(sig&SH_TRAP)) if(!(sig&SH_TRAP))
signal(sig, sh_fault); signal(sig, sh_fault);
@ -437,6 +434,15 @@ void sh_chktrap(Shell_t* shp)
cursig = sig; cursig = sig;
sh_trap(trap,0); sh_trap(trap,0);
cursig = -1; cursig = -1;
/* If we're in a PS2 prompt, then we just parsed and executed a trap in the middle of parsing
* another command, so the lexer state is overwritten. Escape to avoid crashing the lexer. */
if(sh.nextprompt == 2)
{
fcclose(); /* force lexer to abort partial command */
sh.nextprompt = 1; /* next display prompt is PS1 */
sh.lastsig = sig; /* make sh_exit() set $? to signal exit status */
sh_exit(SH_EXITSIG); /* start a new command line */
}
} }
} }
} }

View file

@ -37,6 +37,7 @@
#include "path.h" #include "path.h"
#include "io.h" #include "io.h"
#include "jobs.h" #include "jobs.h"
#include "shlex.h"
#include "shnodes.h" #include "shnodes.h"
#include "history.h" #include "history.h"
#include "edit.h" #include "edit.h"
@ -2154,6 +2155,7 @@ static int io_prompt(Shell_t *shp,Sfio_t *iop,register int flag)
case 1: case 1:
{ {
register int c; register int c;
sh_lexopen(sh.lex_context, &sh, 0); /* reset lexer state */
#if defined(TIOCLBIC) && defined(LFLUSHO) #if defined(TIOCLBIC) && defined(LFLUSHO)
if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS)) if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
{ {