On Mon, 2021-08-09 at 14:15 +0200, Martijn van Duren wrote:
> On Mon, 2021-08-09 at 13:19 +0200, Ingo Schwarze wrote:
> > Hi,
> >
> > as mentioned earlier, deraadt@ reported that sftp(1) ignores Ctrl-C.
> > Fixing that without longjmp(3) requires making editline(3) better
> > behaved.
> >
> > Currently, when read(2) from the terminal gets interrupted by a
> > signal, editline(3) ignores the (first) signal and unconditionally
> > calls read(2) a second time. That seems wrong: if the user hits
> > Ctrl-C, it is sane to assume that they meant it, not that they
> > want it ignored.
> >
> > The following patch causes el_gets(3) and el_wgets(3) to return
> > failure when read(2)ing from the terminal is interrupted by a
> > signal other than SIGCONT or SIGWINCH. That allows the calling
> > program to decide what to do, usually either exit the program or
> > provide a fresh prompt to the user.
> >
> > If i know how to grep(1), the following programs in the base system
> > use -ledit:
> >
> > * bc(1)
> > It behaves well with the patch: Ctrl-C discards the current
> > input line and starts a new input line.
> > The reason why this already works even without the patch
> > is that bc(1) does very scary stuff inside the signal handler:
> > pass a file-global EditLine pointer on the heap to el_line(3)
> > and access fields inside the returned struct. Needless to
> > say that no signal handler should do such things...
> >
> > * cdio(1)
> > Behaviour is acceptable and unchanged with the patch:
> > Ctrl-C exits cdio(1).
> >
> > * ftp(1)
> > It behaves well with the patch: Ctrl-C discards the current
> > input line and provides a fresh prompt.
> > The reason why it already works without the patch is that ftp(1)
> > uses setjmp(3)/longjmp(3) to forcefully grab back control
> > from el_gets(3) without waiting for it to return.
> >
> > * lldb(1)
> > It misbehaves with or without the patch and ignores Ctrl-C.
> > I freely admit that i don't feel too enthusiastic about
> > debugging that beast.
> >
> > * sftp(1)
> > Behaviour is improved with the patch: Ctrl-C now exits sftp(1).
> > If desired, i can supply a very simple follow-up patch to sftp.c
> > to instead behave like ftp(1) and bc(1), i.e. discard the
> > current input line and provide a fresh prompt.
> > Neither doing undue work in the signal handler nor longjmp(3)
> > will be required for that (if this patch gets committed).
> >
> > * bgplgsh(8), fsdb(8)
> > I have no idea how to test those. Does anyone think that testing
> > either of them would be required?
> >
> > Regarding the patch below, note that differentiating EINTR behaviour
> > by signal number is not needed because the calling code in read_char()
> > already handles SIGCONT and SIGWINCH, so those two never arrive in
> > read__fixio() in the first place.
> >
> > Also note that deraadt@ pointed out in private mail to me that the
> > fact that read__fixio() clears FIONBIO is probably a symptom of
> > botched editline(3) API design. That might be worth fixing, too,
> > as far as that is feasible, but it is unrelated to the sftp(1)
> > Ctrl-C issue; let's address one topic at a time.
> >
> > Any feedback is welcome.
> >
> > Yours,
> > Ingo
> >
> > P.S.
> > I decided to Cc: tech@ again because this patch might affect
> > even people who are not specifically interested in editline(3),
> > and i have no intention to cause unpleasant surprises.
> >
> If we're stripping down fixio to a single function, why not inline the
> whole thing?
>
> Also, as far as my brain explains things to me, it could theoretically
> be possible that the signal handler kicks in right after we return a -1
> from read(2), making it possible to get something like an EIO and
> entering the sig switch statement. Assuming that a signal handler
> doesn't clobber our errno (which libedit doesn't do, but who knows what
> bad code is out there) we should probably only check the signal type
> when we know we have an EINTR.
>
> Finally, I don't understand why we only have a single retry on EAGAIN?
> If the application keeps resetting the FIONBIO in such a furious way
> that it happens more then once in a single call (no idea how that could
> happen) it might be an uphill battle, but we just burn away cpu cycles.
> It is not and should probably not be treated like something fatal.
>
> Diff below does this. (minus 27 LoC)
>
> The following ports use libedit (there might be a better way of finding
> them, but this works):
> $ find . -name Makefile | xargs grep -F '.include <bsd.port.mk>' | \
> > cut -d: -f1 | while read file; do \
> > (cd $(dirname $file); make show=WANTLIB | \
> > grep -q '\<edit\>' && echo $(dirname $file)); \>
> > done 2> /dev/null
> ./devel/clang-tools-extra
> ./devel/llvm
> ./mail/dcc
> ./mail/nmh
> ./emulators/gsplus
> ./emulators/nono
> ./lang/brainfuck
> ./lang/eltclsh
> ./lang/lua/5.1
> ./lang/lua/5.2
> ./lang/lua/5.3
> ./lang/swi-prolog
> ./math/gbc
> ./net/dnsdist
> ./net/honeyd
> ./net/icinga/core2
> ./net/knot
> ./net/ntp
> ./security/kc
> ./security/pivy
> ./shells/dash
> ./shells/nsh
> ./sysutils/ipmitool
>
> So if we decide which of our interpretations should take precedence it
> might be a good idea to put it into snaps for a while.
>
> martijn@
>
We can make it even smaller by making use of this little known construct
named while.
Index: read.c
===================================================================
RCS file: /cvs/src/lib/libedit/read.c,v
retrieving revision 1.45
diff -u -p -r1.45 read.c
--- read.c 9 Aug 2021 09:11:26 -0000 1.45
+++ read.c 9 Aug 2021 12:21:55 -0000
@@ -66,7 +66,6 @@ struct el_read_t {
int read_errno;
};
-static int read__fixio(int, int);
static int read_char(EditLine *, wchar_t *);
static int read_getcmd(EditLine *, el_action_t *, wchar_t *);
static void read_clearmacros(struct macros *);
@@ -132,29 +131,6 @@ el_read_getfn(struct el_read_t *el_read)
}
-/* read__fixio():
- * Try to recover from a read error
- */
-static int
-read__fixio(int fd, int e)
-{
- int zero = 0;
-
- switch (e) {
- case EAGAIN:
- if (ioctl(fd, FIONBIO, &zero) == -1)
- return -1;
- return 0;
-
- case EINTR:
- return 0;
-
- default:
- return -1;
- }
-}
-
-
/* el_push():
* Push a macro
*/
@@ -234,33 +210,29 @@ static int
read_char(EditLine *el, wchar_t *cp)
{
ssize_t num_read;
- int tried = 0;
char cbuf[MB_LEN_MAX];
int cbp = 0;
- int save_errno = errno;
+ int zero = 0;
- again:
el->el_signal->sig_no = 0;
while ((num_read = read(el->el_infd, cbuf + cbp, 1)) == -1) {
- int e = errno;
- switch (el->el_signal->sig_no) {
- case SIGCONT:
- el_set(el, EL_REFRESH);
- /*FALLTHROUGH*/
- case SIGWINCH:
- sig_set(el);
- goto again;
- default:
- break;
- }
- if (!tried && read__fixio(el->el_infd, e) == 0) {
- errno = save_errno;
- tried = 1;
- } else {
- errno = e;
- *cp = L'\0';
- return -1;
+ if (errno == EINTR) {
+ switch (el->el_signal->sig_no) {
+ case SIGCONT:
+ el_set(el, EL_REFRESH);
+ /*FALLTHROUGH*/
+ case SIGWINCH:
+ sig_set(el);
+ continue;
+ default:
+ break;
+ }
+ } else if (errno == EAGAIN) {
+ if (ioctl(el->el_infd, FIONBIO, &zero) != -1)
+ continue;
}
+ *cp = L'\0';
+ return -1;
}
/* Test for EOF */