Subject: bin/13357: inetd sometimes zaps its PID file when servicing internal services
To: None <gnats-bugs@gnats.netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: netbsd-bugs
Date: 07/02/2001 14:40:44
>Number:         13357
>Category:       bin
>Synopsis:       inetd sometimes zaps its PID file when servicing internal services
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    bin-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Mon Jul 02 11:38:00 PDT 2001
>Closed-Date:
>Last-Modified:
>Originator:     Greg A. Woods
>Release:        2001/06/24
>Organization:
Planix, Inc.; Toronto, Ontario; Canada
>Environment:

System: NetBSD 1.5W
Architecture: all
Machine: all

>Description:

	Inetd will sometimes zap its PID file (accidentally) when
	servicing internal services, either always if -DLIBWRAP_INTERNAL
	is used, or just for those that cause a fork() otherwise.

	This is because the helpful little pidfile(3) function is a
	little bit too helpful for any daemon that might fork() but not
	exec().  Pidfile() uses atexit(3) to clean up the PID file when
	the process exits.  However if a caller uses fork() without
	exec() then the exiting child process won't have had its
	atexit() table "cleared" and thus this child process will call
	the function registered by pidfile(), and that funcation of
	course removes the PID file.

	The hack below works, but relies on several more or less hidden
	assumptions being true.  A better hack might be to have the
	pidfile() cleanup function check that the PID is the same now as
	it was when the file was first created (i.e. only allow the
	parent to remove the PID file).

	A more general and perhaps additional (not alternate) fix would
	be to allow a child process to de-register one or all atexit()
	functions.  Unfortunately there doesn't appear to be a standard
	way to do this (i.e. ISO C standard), and grovelling about in
	the *BSD global __atexit isn't portable.

>How-To-Repeat:

	enable the "discard" service if necessary:

	# echo "discard stream tcp nowait root internal" >> /etc/inetd.conf
	# /etc/rc.d/inetd reload

	now the test:

	# ls -l /var/run/inetd.pid
	(note it is there)
	# telnet localhost discard
	^]quit
	# ls -l /var/run/inetd.pid
	(note it is not there any more!)

>Fix:

	apply the following nasty hack to src/usr.sbin/inetd.c (note
	line numbers may be off due to other local changes)

***************
*** 620,627 ****
  		sigsetmask(0L);
  		if (pid == 0) {
  			run_service(ctrl, sep);
! 			if (dofork)
! 				exit(0);
  		}
  		if (!sep->se_wait && sep->se_socktype == SOCK_STREAM)
  			close(ctrl);
--- 616,627 ----
  		sigsetmask(0L);
  		if (pid == 0) {
  			run_service(ctrl, sep);
! 			if (dofork) {
! 				if (sep->se_bi == 0)
! 					exit(0);
! 				else
! 					_exit(0);
! 			}
  		}
  		if (!sep->se_wait && sep->se_socktype == SOCK_STREAM)
  			close(ctrl);

>Release-Note:
>Audit-Trail:
>Unformatted: