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

Fix crash when suspending a blocked write to a FIFO

Reproducer (symptoms on at least macOS and FreeBSD):

$ mkfifo f
$ echo foo > f
(press Ctrl+Z)
^Zksh: f: cannot create [Interrupted system call]
Abort

The shell either aborts (dev builds) or crashes with 'Illegal
instruction' (release builds). This is consistent with
UNREACHABLE() being reached.

Backtrace:

0   libsystem_kernel.dylib    __kill + 10
1   ksh                       sh_done + 836 (fault.c:678)
2   ksh                       sh_fault + 1324
3   libsystem_platform.dylib  _sigtramp + 29
4   dyld                      ImageLoaderMachOCompressed::resolve(ImageLoader::LinkContext const&, char const*, unsigned ch
5   libsystem_c.dylib         abort + 127
6   ksh                       sh_redirect + 3576 (io.c:1356)
7   ksh                       sh_exec + 7231 (xec.c:1308)
8   ksh                       exfile + 3247 (main.c:607)
9   ksh                       sh_main + 3551 (main.c:368)
10  ksh                       main + 38 (pmain.c:45)
11  libdyld.dylib             start + 1

This means that UNREACHABLE() is actually reached here:

ksh/src/cmd/ksh93/sh/io.c
1351: if((fd=sh_open(tname?tname:fname,o_mode,RW_ALL)) <0)
1352: {
1353: 	errormsg(SH_DICT,ERROR_system(1),((o_mode&O_CREAT)?e_create:e_open),fname);
1354: 	UNREACHABLE();
1355: }

The cause is that, in the following section of code in sh_fault():

ksh/src/cmd/ksh93/sh/fault.c
183: #ifdef SIGTSTP
184: 		if(sig==SIGTSTP)
185: 		{
186: 			sh.trapnote |= SH_SIGTSTP;
187: 			if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK))
188: 			{
189: 				sigrelease(sig);
190: 				sh_exit(SH_EXITSIG);
191: 				return;
192: 			}
193: 		}
194: #endif /* SIGTSTP */

...sh_exit() is not getting called and the function will not return
because the SH_STOPOK bit is not set while the shell is blocked
waiting to write to a FIFO.

Even if sh_exit() did get called, that would not fix it, because
that function also checks for the SH_STOPOK bit and returns without
doing a longjmp if the signal is SIGTSTP and the SH_STOPOK bit is
not set. That is direct the reason why UNREACHABLE() was raeched:
errormsg() does call sh_exit() but sh_exit() then does not longjmp.

src/cmd/ksh93/sh/fault.c: sh_fault():
- To avoid the crash, we simply need to return from sh_fault() if
  SH_STOPOK is off, so that the code path does not continue, no
  error message is given on Ctrl+Z, UNREACHABLE() is not reached,
  and the shell resumes waiting on trying to write to the FIFO.
  The sh.trapnote flag should not be set if we're not going to
  process the signal. This makes ksh behave like all other shells.

Resolves: https://github.com/ksh93/ksh/issues/464
This commit is contained in:
Martijn Dekker 2022-02-17 20:07:56 +00:00
parent 11177d448d
commit b09ce2fa02
3 changed files with 28 additions and 4 deletions

View file

@ -181,15 +181,15 @@ void sh_fault(register int sig)
sh.lastsig = sig;
flag = SH_SIGSET;
#ifdef SIGTSTP
if(sig==SIGTSTP)
if(sig==SIGTSTP && pp->mode==SH_JMPCMD)
{
sh.trapnote |= SH_SIGTSTP;
if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK))
if(sh_isstate(SH_STOPOK))
{
sh.trapnote |= SH_SIGTSTP;
sigrelease(sig);
sh_exit(SH_EXITSIG);
return;
}
return;
}
#endif /* SIGTSTP */
}