Bug 12731

Summary: inetd SIGHUP breaks SIGCHLD handling
Product: Base System Reporter: Sheldon Hearn <sheldonh>
Component: binAssignee: Sheldon Hearn <sheldonh>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.0-CURRENT   
Hardware: Any   
OS: Any   

Description Sheldon Hearn 1999-07-21 10:30:01 UTC
	When a wrapping inetd adds a new service during a SIGHUP-induced
	config(), and when the rule pertaining to that service in
	/etc/hosts.allow uses the hosts_options(3) spawn option, inetd
	is unable to run the associated daemon for that service.

Fix: 

Not known at this time.

	Although the failure that causes the syslog message happens in
	flag_signal() at line 769 of inetd.c, I'm not sure why the
	pipe(2) call fails. Obviously, main() and config() are the two
	suspects to look at. ;-)

	I'll look at it when I have a moment, unless David Malone and
	the boys beat me to it. ;-)
How-To-Repeat: 
	In /etc/inetd.conf:
	# telnet service commented out:
	#telnet  stream  tcp  nowait  root  /usr/libexec/telnetd  telnet

	In /etc/hosts.allow:
	telnetd: ALL : \
	    spawn (/bin/echo "Something or other") & \
	    : ALLOW

	Make sure no inetd daemon is running and do:
	# inetd -w
	# telnet localhost
	Trying 127.0.0.1...
	telnet: Unable to connect to remote host: Connection refused

	Now, change /etc/inetd.conf:
	# bind to telnet port:
	telnet  stream  tcp  nowait  root  /usr/libexec/telnetd  telnet

	Do:
	# kill -HUP `cat /var/run/inetd.pid`
	# telnet localhost
	Trying 127.0.0.1...
	Connected to localhost.
	Escape character is '^]'.
	Connection closed by foreign host.
	
	Notice in /var/log/messages:
	Jul 21 11:11:04 axl inetd[18062]: write: Bad file descriptor
Comment 1 Sheldon Hearn freebsd_committer freebsd_triage 1999-07-21 10:40:11 UTC
Responsible Changed
From-To: freebsd-bugs->sheldonh

I'll take this one, although private mail indicates that David's already 
onto this one. :-) 

These Irish, I tell you! 

Comment 2 Sheldon Hearn 1999-07-21 12:55:32 UTC
Through the SIGCHLD handler flag_reapchild(), reapchild() is being
triggered when the spawn option's /bin/sh exits during or after we call
hosts_access(). This is bad news, since reapchild() is designed to clean
up sep->se_pids and sep->se_numchild.

The SIGCHLD may arrive any time during or after the hosts_access()
call. I can't see why we're interested in it at that stage, though.

Ciao,
Sheldon.

Index: inetd.c
===================================================================
RCS file: /home/ncvs/src/usr.sbin/inetd/inetd.c,v
retrieving revision 1.61
diff -u -d -r1.61 inetd.c
--- inetd.c	1999/07/15 17:01:43	1.61
+++ inetd.c	1999/07/21 11:47:08
@@ -351,7 +351,7 @@
 	struct servtab *sep;
 	struct passwd *pwd;
 	struct group *grp;
-	struct sigaction sa, sapipe;
+	struct sigaction sa, sachld, sapipe;
 	int tmpint, ch, dofork;
 	pid_t pid;
 	char buf[50];
@@ -462,7 +462,7 @@
 	sa.sa_handler = flag_config;
 	sigaction(SIGHUP, &sa, (struct sigaction *)0);
 	sa.sa_handler = flag_reapchild;
-	sigaction(SIGCHLD, &sa, (struct sigaction *)0);
+	sigaction(SIGCHLD, &sa, &sachld);
 	sa.sa_handler = SIG_IGN;
 	sigaction(SIGPIPE, &sa, &sapipe);
 
@@ -643,6 +643,18 @@
 				fromhost(&req);
 				deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY;
 				allow_severity = LIBWRAP_ALLOW_FACILITY|LIBWRAP_ALLOW_SEVERITY;
+				/*
+				 * Somewhere during or after hosts_access(), a shell from
+				 * hosts_options(5) spawn option will generate a SIGCHLD, which
+				 * must be ignored.
+				 */
+#ifdef SANITY_CHECK
+				if (!dofork)
+					syslog(LOG_ERR,
+					    "can't ignore SIGCHLD in listening process");
+				else
+#endif /* SANITY_CHECK */
+				sigaction(SIGCHLD, &sachld, (struct sigaction *)0);
 				denied = !hosts_access(&req);
 				if (denied) {
 				    syslog(deny_severity,
Comment 3 dwmalone 1999-07-21 13:23:52 UTC
> The SIGCHLD may arrive any time during or after the hosts_access()
> call. I can't see why we're interested in it at that stage, though.

I wonder if we should reset all the signal handlers after the fork,
afterall - we're no longer interested in HUPs, CHLDs and ALRMs at
that stage.

We should also take care to update maxsock correctly, or we should
make most of the descriptors in inetd close-on-exec - at the moment
we sometimes accidently pass signalpipe[1] to children which could
have some bad effects!

	David.

--- inetd.c	1999/07/15 17:01:43	1.61
+++ inetd.c	1999/07/21 12:20:46
@@ -351,7 +351,7 @@
 	struct servtab *sep;
 	struct passwd *pwd;
 	struct group *grp;
-	struct sigaction sa, sapipe;
+	struct sigaction sa, sapipe, sahup, saalrm, sachld;
 	int tmpint, ch, dofork;
 	pid_t pid;
 	char buf[50];
@@ -457,12 +457,12 @@
 	sigaddset(&sa.sa_mask, SIGCHLD);
 	sigaddset(&sa.sa_mask, SIGHUP);
 	sa.sa_handler = flag_retry;
-	sigaction(SIGALRM, &sa, (struct sigaction *)0);
+	sigaction(SIGALRM, &sa, &saalrm);
 	config();
 	sa.sa_handler = flag_config;
-	sigaction(SIGHUP, &sa, (struct sigaction *)0);
+	sigaction(SIGHUP, &sa, &sahup);
 	sa.sa_handler = flag_reapchild;
-	sigaction(SIGCHLD, &sa, (struct sigaction *)0);
+	sigaction(SIGCHLD, &sa, &sachld);
 	sa.sa_handler = SIG_IGN;
 	sigaction(SIGPIPE, &sa, &sapipe);
 
@@ -484,6 +484,8 @@
 	nsock++;
 	if (signalpipe[0] > maxsock)
 	    maxsock = signalpipe[0];
+	if (signalpipe[1] > maxsock)
+	    maxsock = signalpipe[1];
 
 	for (;;) {
 	    int n, ctrl;
@@ -623,6 +625,11 @@
 				for (tmpint = maxsock; tmpint > 2; tmpint--)
 					if (tmpint != ctrl)
 						(void) close(tmpint);
+				alarm(0);
+				sigaction(SIGALRM, &saalrm);
+				sigaction(SIGCHLD, &sachld);
+				sigaction(SIGHUP, &sahup);
+				/* SIGPIPE reset before exec */
 			    }
 			    /*
 			     * Call tcpmux to find the real service to exec.
Comment 4 Sheldon Hearn 1999-07-21 14:19:46 UTC
On Wed, 21 Jul 1999 13:23:52 +0100, David Malone wrote:

> I wonder if we should reset all the signal handlers after the fork,
> afterall - we're no longer interested in HUPs, CHLDs and ALRMs at
> that stage.

Good.

I was talking crap before when I said that we may receive SIGCHLD any
time after hosts_access(). After chatting to a local guru, I've been
made to see that this is impossible.

So when we unload our SIGCHLD handler, we're actually working around a
bug in hosts_access(3), which should save the existing SIGCHLD handler,
use the default handler (SIG_DFL) and then restore the saved handler.

It's only worth noting because someone needs to chat to Wietse about
this.

> +				sigaction(SIGALRM, &saalrm);
> +				sigaction(SIGCHLD, &sachld);
> +				sigaction(SIGHUP, &sahup);

I don't think that the third argument to sigaction is optional, but
that's trivial. :-)

Ciao,
Sheldon.
Comment 5 dwmalone 1999-07-21 14:24:13 UTC
> So when we unload our SIGCHLD handler, we're actually working around a
> bug in hosts_access(3), which should save the existing SIGCHLD handler,
> use the default handler (SIG_DFL) and then restore the saved handler.
> 
> It's only worth noting because someone needs to chat to Wietse about
> this.

Indeed - there is already a little signal code in the wrapper code,
so it stands some chance of getting fixed.

> > +				sigaction(SIGALRM, &saalrm);
> > +				sigaction(SIGCHLD, &sachld);
> > +				sigaction(SIGHUP, &sahup);
> 
> I don't think that the third argument to sigaction is optional, but
> that's trivial. :-)

Drat - I ment to say the patch was untested ;-)

I'm going to try to write a test service for inetd which can spot stuff
like leaked file discriptors.

	David.
Comment 6 Sheldon Hearn 1999-07-21 14:29:25 UTC
On Wed, 21 Jul 1999 14:24:13 +0100, David Malone wrote:

> I'm going to try to write a test service for inetd which can spot
> stuff like leaked file discriptors.

I'd prefer to fix the signal handling and leaking descriptors in two
different commits. I've already learned the hard way that fixing
multiple problems with a single commit can cause serious head-aches if
you want to MFC just one of the fixes. :-(

Ciao,
Sheldon.
Comment 7 des 1999-07-21 14:38:48 UTC
Sheldon Hearn <sheldonh@uunet.co.za> writes:
> 	Notice in /var/log/messages:
> 	Jul 21 11:11:04 axl inetd[18062]: write: Bad file descriptor

Sounds like something incorrectly closes the signal pipe.

DES
-- 
Dag-Erling Smorgrav - des@flood.ping.uio.no
Comment 8 Sheldon Hearn 1999-07-21 14:52:50 UTC
On 21 Jul 1999 15:38:48 +0200, Dag-Erling Smorgrav wrote:

> Sounds like something incorrectly closes the signal pipe.

Actually, what I need to understand is _why_ we need the signal pipe.
The child doesn't care about the parent's HUPs, ALRMs, CHLDs (!) and PIPEs,
and I'm not sure why the parent would need to know about any of these
received by one of its children.

Certainly, just unloading our SIGCHLD handler appears to resolve the
hosts_options(5) spawn problem. So what's with this signal pipe?

Ciao,
Sheldon.
Comment 9 des 1999-07-21 15:37:49 UTC
Sheldon Hearn <sheldonh@uunet.co.za> writes:
> Certainly, just unloading our SIGCHLD handler appears to resolve the
> hosts_options(5) spawn problem. So what's with this signal pipe?

The parent needs it. We can of course close it when we fork, but we
should reset all signal handlers first.

DES
-- 
Dag-Erling Smorgrav - des@flood.ping.uio.no
Comment 10 Sheldon Hearn 1999-07-21 16:34:14 UTC
Hi DES,

Are you happy with the following working version of David's patch? It
only addresses the work-around for the unwanted SIGCHLD from a ``spawn''
within hosts_access(3). I'd prefer to have the signalpipe handling fix
in as a separate commit.

Ciao,
Sheldon.

PS: Yes, we also reset the SIGALRM and SIGHUP handlers, but my feeling
    is that child inetd processes should die when they receive these
    signals. We don't want a child inetd rereading its config and if it
    gets a SIGALRM, it certainly isn't because it requested one.

Index: inetd.c
===================================================================
RCS file: /home/ncvs/src/usr.sbin/inetd/inetd.c,v
retrieving revision 1.62
diff -u -d -r1.62 inetd.c
--- inetd.c	1999/07/21 12:19:24	1.62
+++ inetd.c	1999/07/21 15:28:47
@@ -351,7 +351,7 @@
 	struct servtab *sep;
 	struct passwd *pwd;
 	struct group *grp;
-	struct sigaction sa, sapipe;
+	struct sigaction sa, saalrm, sachld, sahup, sapipe;
 	int tmpint, ch, dofork;
 	pid_t pid;
 	char buf[50];
@@ -457,12 +457,12 @@
 	sigaddset(&sa.sa_mask, SIGCHLD);
 	sigaddset(&sa.sa_mask, SIGHUP);
 	sa.sa_handler = flag_retry;
-	sigaction(SIGALRM, &sa, (struct sigaction *)0);
+	sigaction(SIGALRM, &sa, &saalrm);
 	config();
 	sa.sa_handler = flag_config;
-	sigaction(SIGHUP, &sa, (struct sigaction *)0);
+	sigaction(SIGHUP, &sa, &sahup);
 	sa.sa_handler = flag_reapchild;
-	sigaction(SIGCHLD, &sa, (struct sigaction *)0);
+	sigaction(SIGCHLD, &sa, &sachld);
 	sa.sa_handler = SIG_IGN;
 	sigaction(SIGPIPE, &sa, &sapipe);
 
@@ -624,6 +624,10 @@
 				for (tmpint = maxsock; tmpint > 2; tmpint--)
 					if (tmpint != ctrl)
 						(void) close(tmpint);
+				sigaction(SIGALRM, &saalrm, (struct sigaction *)0);
+				sigaction(SIGCHLD, &sachld, (struct sigaction *)0);
+				sigaction(SIGHUP, &sahup, (struct sigaction *)0);
+				/* SIGPIPE reset before exec */
 			    }
 			    /*
 			     * Call tcpmux to find the real service to exec.
Comment 11 des 1999-07-21 16:37:24 UTC
Sheldon Hearn <sheldonh@uunet.co.za> writes:
> Are you happy with the following working version of David's patch?

Hmm, looks OK, though I didn't look too close. From what I can see, it
simply stores the original signal handler state and restores it after
a fork?

DES
-- 
Dag-Erling Smorgrav - des@flood.ping.uio.no
Comment 12 Sheldon Hearn 1999-07-21 16:39:24 UTC
On 21 Jul 1999 17:37:24 +0200, Dag-Erling Smorgrav wrote:

> Hmm, looks OK, though I didn't look too close. From what I can see, it
> simply stores the original signal handler state and restores it after
> a fork?

'Zactly. :-)

Ciao,
Sheldon.
Comment 13 Sheldon Hearn 1999-07-21 23:04:42 UTC
Hi DES,

Would you care to comment on David's speculation regarding the purpose
of nsock?

Thanks,
Sheldon.

------- Forwarded Message
Message-ID:  <199907212259.aa16005@salmon.maths.tcd.ie>
Date: Wed, 21 Jul 1999 22:59:49 +0100
From: David Malone <dwmalone@maths.tcd.ie>
To: Sheldon Hearn <sheldonh@uunet.co.za>
Subject: Re: Tester Wanted: PR 12731 (inetd bug) How-To-Repeat 

[...]
> And can you explain nsock's relationship to maxsock for me? I'm not sure
> why we bump it after FD_SET(signalpipe[0], &allsock). Is this right:
> 
> 	maxsock	=	The highest-numbered descriptor
> 	nsock =		The number of descriptors

maxsock seems to have two functions:

	1) maxsock + 1 is the first arg to select, so it must be
		atleast as big as any fd we want to read from.

	2) the highest numbered file discriptor to try and close
		after a fork.

These used to be the same before the signal pipe stuff got added.

As far as I can tell nsock now does nothing useful. It used to be
used to count the number of active sockets, so when they were all
disabeled the code could do a sigpause (presumeably you can't do
a select on no file discriptors, so they had to do this to stop
inetd chewing CPU time). Since it now always has the sigpipe to
select on, nsock doesn't seem to do anything useful.

The only code that uses it just exits with an error if it ever
hits 0 - which it doesn't 'cos we bump it when we open the pipe.
I'd suggest that we cut it out completly.

> I haven't looked at this very carefully and I'm also a little tired. ;-)

I'll proabaly stay at home and do some mathematics tomorrow, rather
than fiddling with computers ;-) However, I'll try to fix the
leaking discriptor, remove nsock and do some tests. If you want to
commit something you could just update maxsock - I believe that
should fix the problem.

Inetd is begining to look like it has been patched too many times
without a general cleanup to make the code more coherent. It should
really be split into functions like NetBSD's I guess. Would there
be an interest in this for -current?

	David.


------- End of Forwarded Message
Comment 14 des 1999-07-22 09:51:35 UTC
Sheldon Hearn <sheldonh@uunet.co.za> writes:
> The only code that uses it just exits with an error if it ever
> hits 0 - which it doesn't 'cos we bump it when we open the pipe.

I know, I wrote some of that code :) See the discussions around the
SIGHUP / zombie bug in May. I left the code in there to make sure I
hadn't made any incorrect assumptions.

> I'd suggest that we cut it out completly.

Agreed.

> Inetd is begining to look like it has been patched too many times
> without a general cleanup to make the code more coherent. It should
> really be split into functions like NetBSD's I guess. Would there
> be an interest in this for -current?

Of course.

DES
-- 
Dag-Erling Smorgrav - des@flood.ping.uio.no
Comment 15 Sheldon Hearn freebsd_committer freebsd_triage 1999-07-22 17:16:45 UTC
State Changed
From-To: open->closed

Both problems (SIGCHLD handling and descriptor leak) fixed in CURRENT 
(rev 1.67 inetd.c) and MFC'd to STABLE (rev 1.46.2.7  inetd.c). Thanks!