Bug 30985

Summary: incorrect signal handling in snpread()
Product: Base System Reporter: Valentin Nechayev <netch>
Component: kernAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.4-RELEASE   
Hardware: Any   
OS: Any   

Description Valentin Nechayev 2001-10-02 14:00:01 UTC
sleeping in snpread() waiting for data is interruptible (PCATCH flag),
but does not check for tsleep() status which can say that signal came.
As a result, system hangs in "forever" cycle.

Discovered by: Vladimir Jakovenko <vovik@lucky.net>

Fix: Following simple fix provides checking for tsleep() return code
and EINTR returning when signal is caught. It is applicable for
all late 4.* systems.
It also increases IPL around the data wait cycle. As data can be put
to snoop device during interrupt (am I wrong?), it is desirable.
If hard-interrupt and soft-interrupt handling code can't put data to
snoop device, this IPL increasing isn't needed.
I didn't increase IPL around uiomove() due to some deja-vu.

But it should be noted that there are some principal architectural
issues in this approach. As snp device is designed for use when
select/poll is used to wait and ioctl(FIONREAD) is desired to get
tty status, it may be desirable to avoid blocking reads of snp device
totally. I can't suppose what approach is more right.

==={{{


/netch--MYX0T90r85IcyNMoQG6aPBSFyUrAHUBrD83qNGvZob5z9YAg
Content-Type: text/plain; name="file.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="file.diff"

--- tty_snoop.c.0	Thu Nov 18 08:39:47 1999
+++ tty_snoop.c	Tue Oct  2 15:23:54 2001
@@ -143,18 +143,26 @@
 	if (snp->snp_tty == NULL)
 		return (EIO);
 
+	s = spltty();
 	snp->snp_flags &= ~SNOOP_RWAIT;
 
 	do {
 		if (snp->snp_len == 0) {
-			if (flag & IO_NDELAY)
+			if (flag & IO_NDELAY) {
+				splx(s);
 				return (EWOULDBLOCK);
+			}
 			snp->snp_flags |= SNOOP_RWAIT;
-			tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread", 0);
+			error = tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread", 0);
+			if (error == EINTR || error == ERESTART) {
+				splx(s);
+				return EINTR;
+			}
 		}
 	} while (snp->snp_len == 0);
 
 	n = snp->snp_len;
+	splx(s);
 
 	while (snp->snp_len > 0 && uio->uio_resid > 0 && error == 0) {
 		len = MIN(uio->uio_resid, snp->snp_len);
===}}}
How-To-Repeat: 
Write any userland program which does read() on snp(4) device in
blocking mode. Run it and interrupt it from its terminal with intr character
(Ctrl-C in standard case). Local (virtual) console, e.g. ttyv1, may be
required.
Standard watch(8) does not show this behavoir in 99.99...% cases because
it waits in select(), not in read(). But it also can fall to this case
in rare case when signal occurs during snpread() (am I wrong?)
Comment 1 Valentin Nechayev 2001-10-04 11:27:51 UTC
>>Synopsis:       incorrect signal handling in snpread()

VN> --- tty_snoop.c.0	Thu Nov 18 08:39:47 1999
VN> +++ tty_snoop.c	Tue Oct  2 15:23:54 2001

For 5-current, the same bug is in src/sys/dev/snp/snp.c.


/netch
Comment 2 Dima Dorfman 2001-10-07 16:50:41 UTC
Valentin Nechayev <netch@lucky.net> wrote:
> >Fix:
> 
> Following simple fix provides checking for tsleep() return code
> and EINTR returning when signal is caught. It is applicable for
> all late 4.* systems.
> It also increases IPL around the data wait cycle. As data can be put
> to snoop device during interrupt (am I wrong?), it is desirable.

I don't think it's possible for data to enter snp during an interrupt.
At least, there are no provisions for this anywhere else in the code.
Thus, I think this is undesirable.

> But it should be noted that there are some principal architectural
> issues in this approach. As snp device is designed for use when
> select/poll is used to wait and ioctl(FIONREAD) is desired to get
> tty status, it may be desirable to avoid blocking reads of snp device
> totally. I can't suppose what approach is more right.

I think it's okay to sleep in snpread.  snp generally tries to act
like any other driver, and most allow sleeping in their read routine.

>  	do {
>  		if (snp->snp_len == 0) {
> -			if (flag & IO_NDELAY)
> +			if (flag & IO_NDELAY) {
> +				splx(s);
>  				return (EWOULDBLOCK);
> +			}
>  			snp->snp_flags |= SNOOP_RWAIT;
> -			tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread"
> , 0);
> +			error = tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread", 0);
> +			if (error == EINTR || error == ERESTART) {
> +				splx(s);
> +				return EINTR;
> +			}

Why can't we just return whatever tsleep() returns, as most (all?)
other drivers do?  Like so (untested):

Index: snp.c
===================================================================
RCS file: /ref/cvsf/src/sys/dev/snp/snp.c,v
retrieving revision 1.63
diff -u -r1.63 snp.c
--- snp.c	2001/09/12 08:37:11	1.63
+++ snp.c	2001/10/07 15:44:47
@@ -255,7 +255,10 @@
 			if (flag & IO_NDELAY)
 				return (EWOULDBLOCK);
 			snp->snp_flags |= SNOOP_RWAIT;
-			tsleep((caddr_t)snp, (PZERO + 1) | PCATCH, "snprd", 0);
+			error = tsleep((caddr_t)snp, (PZERO + 1) | PCATCH,
+			    "snprd", 0);
+			if (error != 0)
+				return (error);
 		}
 	} while (snp->snp_len == 0);
Comment 3 Valentin Nechayev 2001-10-07 19:15:36 UTC
 Sun, Oct 07, 2001 at 08:50:41, dima wrote about "Re: kern/30985: incorrect signal handling in snpread()": 

> > -			tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread"
> > , 0);
> > +			error = tsleep((caddr_t) snp, (PZERO + 1) | PCATCH, "snoopread", 0);
> > +			if (error == EINTR || error == ERESTART) {
> > +				splx(s);
> > +				return EINTR;
> > +			}
> 
> Why can't we just return whatever tsleep() returns, as most (all?)
> other drivers do?  Like so (untested):

I am not experienced kernel hacker ;) The examples I saw in kernel code,
mostly test for ERESTART and possibly EINTR and exit from routine
in case of such codes. tsleep(9) man page (in RELENG_4_4) mentions the only
another return code allowed - EWOULDBLOCK in timeout case, but snpread()
doesn't suppose timeout. If you suppose that exit on any nonzero value
is correct, you probably are right.

>  			snp->snp_flags |= SNOOP_RWAIT;
> -			tsleep((caddr_t)snp, (PZERO + 1) | PCATCH, "snprd", 0);
> +			error = tsleep((caddr_t)snp, (PZERO + 1) | PCATCH,
> +			    "snprd", 0);
> +			if (error != 0)
> +				return (error);
>  		}


/netch
Comment 4 dd freebsd_committer freebsd_triage 2001-11-24 16:00:14 UTC
State Changed
From-To: open->closed

FIxed in -current, thanks!  (And sorry for the delay.)