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

View file

@ -68,9 +68,6 @@ void sh_fault(register int sig)
register char *trap;
register struct checkpt *pp = (struct checkpt*)shp->jmplist;
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 */
if(!(sig&SH_TRAP))
signal(sig, sh_fault);
@ -437,6 +434,15 @@ void sh_chktrap(Shell_t* shp)
cursig = sig;
sh_trap(trap,0);
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 */
}
}
}
}