[comp.bugs.4bsd] setsockopt

stevec@fornax.UUCP (Steve Cumming) (05/06/88)

Here's an interesting little glitch in the 4.3BSD socket handling code - 
specifically in the toggling of socket level options.

Just in case this has not been noticed before, here's a description,
and a fix.

What happens is that setsockopt(3) returns with EINVAL
whenever a socket level boolean option is reset.

Here's how to duplicate it:

------------------------ Cut Here ------------------------------


/* I may have forgotten a #include or two....
 
	This code comes from tftpd.c

*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>

extern	int errno;
struct	sockaddr_in sin = { AF_INET };
int	f;

main(argc, argv)
	char *argv[];
{
	register int n,f;
	struct servent *sp;


	sp = getservbyname("tftp", "udp");
	if (sp == 0) {
		exit(1);
	}
	sin.sin_port = sp->s_port;

	f = socket(AF_INET, SOCK_DGRAM, 0);
	if (f < 0) {
		exit(0);
	}

	/* BREAKS HERE		*/
	/* Returns EINVAL	*/

	if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, (char *)0, sizeof(int)) < 0) {
		perror("tftpd: setsockopt (SO_REUSEADDR)");
	}
	(void) close(f);
}



----------------------------- Cut Here -------------------------------

Now for the bug.

I quote from the manual entry for {get|set}sockopt(2):

	setsockopt(s,level,optname,optval,optlen)
	int s,level,optname;
	char *optval;
	int optlen;

	...To manipulate options at the "socket" level,
	level is specified as SOL_SOCKET. 

	The parameters optval and optlen are used to access option values
	for setsockopt. ... If not option value is to be supplied or
	returned, optval may be suppled as 0.

	Most socket level options take an int parameter for optval.
	For setsockopt, the parameter should [be] non-zero to enable a boolean
	option, or zero if the option is be disabled.
	[SO_REUSEADDR is such an option]

Now this is a classic example of overdriving innocent arguments.
Here's what really happens:

First, look at the actual code in uipc_syscalls.c...


setsockopt()
{
	struct a {
		int	s;
		int	level;
		int	name;
		caddr_t	val;
		int	valsize;
	} *uap = (struct a *)u.u_ap;
	struct file *fp;
	struct mbuf *m = NULL;

	fp = getsock(uap->s);
	if (fp == 0)
		return;
	if (uap->valsize > MLEN) {
		u.u_error = EINVAL;
		return;
	}

/* SFU DEBUG

	On those occaisions when val is supposesd to
	be 0 (i.e. resetting many socket level flags)
	we lose. mbuf pointer m stays NULL, causing
	sosetopt to return EINVAL.
 

	if (uap->val) {

  END SFU DEBUG
*/ 	
	if (uap->val >= 0){	
			
		get an mbuf 'm', copyin 'uap->valsize' bytes
		from where 'uap->val' points to;
		m->m_len = uap->valsize;
	}
	u.u_error =
	    sosetopt((struct socket *)fp->f_data, uap->level, uap->name, m);
}
	
Now we go to uipc_socket.c, where sosetopt() lives:
Unfortunately, it wants the arguments nicely packaged 
in an mbuf. But the distributed version doesn't build
one if the option value is 0.
	

sosetopt(so, level, optname, m0)
	register struct socket *so;
	int level, optname;
	struct mbuf *m0;
{
	int error = 0;
	register struct mbuf *m = m0;

	if (level != SOL_SOCKET) {
		if (so->so_proto && so->so_proto->pr_ctloutput)
			return ((*so->so_proto->pr_ctloutput)
				  (PRCO_SETOPT, so, level, optname, &m0));
		error = ENOPROTOOPT;
	} else {
		switch (optname) {

		case SO_REUSEADDR: (and other socket level toggles)


/* SFU COMMENT.
	This is the wrong test, maybe.
	As distributed, 'm' is null whenever
	a socket level toggle is being reset.
	Unfortunately, this causes an (utterly mysterious)
	EINVAL, as you can see.
*/	
			if (m == NULL || m->m_len < sizeof (int)) {
				error = EINVAL;
				goto bad;
			}
			if (*mtod(m, int *))
				so->so_options |= optname;
			else
				so->so_options &= ~optname;
			break;
/*
	forget this part
*/

		}
	}
bad:
	if (m)
		(void) m_free(m);
	return (error);
}



My fix, which appears to work correctly, is to change the test in
setsockopt() so that an mbuf is always built. (As shown above)

It would probably be better if setsockopt()
did its test on optlen rather than
optval, encoding toggle values when optlen is zero.

soseopt() could then dispense with the sanity check on
its'  mbuf arument, which seems superfluous anyway.
Then (mbuf *)m  could be coerced into a (int *)
and the option toggled accordingly. This gets around the overhead
of generating an mbuf, which I guess was the point in the first 
place. Of course to do it right, sosetopt() would require
an extra parameter.



Steve Cumming

School of Computing Science
Simon Fraser University
Burnaby, B.C.
Canada

...ubc-cs!fornax!stevec
steve@lccr.sfu.cdn

jtkohl@athena.mit.edu (John T Kohl) (05/07/88)

In article <514@fornax.UUCP> stevec@fornax.UUCP (Steve Cumming) writes:
>
>Here's an interesting little glitch in the 4.3BSD socket handling code - 
>specifically in the toggling of socket level options.
...
>What happens is that setsockopt(3) returns with EINVAL
>whenever a socket level boolean option is reset.

I believe what you are describing is correct behavior.  Read on.
>Here's how to duplicate it:
>	if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, (char *)0, sizeof(int)) < 0) {
The problem is the option value you are passing in should be a
POINTER to the desired value.
If you wish to turn the option on, make it point to a storage cell with
some non-zero value.
If you wish to turn the option OFF, make it point to a storage cell with
a value of zero:
	int zero = 0;
	if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&zero, sizeof(int)) < 0 {

>I quote from the manual entry for {get|set}sockopt(2):
>	setsockopt(s,level,optname,optval,optlen)
>	int s,level,optname;
>	char *optval;
>	int optlen;
>	...To manipulate options at the "socket" level,
>	level is specified as SOL_SOCKET. 
>	The parameters optval and optlen are used to access option values
>	for setsockopt. ... If not option value is to be supplied or
>	returned, optval may be suppled as 0.
This is correct.  In your test case you should supply an option value, zero or
non-zero.
>	For setsockopt, the parameter should [be] non-zero to enable a boolean
>	option, or zero if the option is be disabled.
>	[SO_REUSEADDR is such an option]

I believe your fix is unnecessary, given a stricter interpretation of
the manual pages.

----
John Kohl
MIT/Project Athena

lekkas@cernvax.UUCP (George P. Lekkas) (07/18/88)

 A question concerning the setsockopt(2) call. First I remind you that:

     getsockopt, setsockopt - get and set options on sockets

     setsockopt(s, level, optname, optval, optlen)
     int s, level, optname;
     char *optval;
     int optlen;

     When manipulating socket options  the  level  at  which  the
     option resides and the name of the option must be specified.
     To manipulate options at the socket level, _l_e_v_e_l  is  speci-
     fied  as  SOLSOCKET.   To  manipulate  options at any other
     level the protocol number of the appropriate  protocol  con-
     trolling the option must be supplied.  For example, to indi-
     cate an option is to be interpreted  by  the  TCP  protocol,
     l_e_v_e_l  should  be  set  to  the protocol number of TCP.  For
     further information, see getprotoent(3n).

	Trying to figure out how Internet sockets work/behave  and 
watching them closely, I decided to do a setsockopt at the TCP 
protocol level, that is set the level=6. This call was made on
the socket returned from accepting a connection request, at the server side.
All sockets were of the SOCK_STREAM type and you can find this typical
client/server paradigm in the 4.3BSD Communications Primer.
	Just add the setsockopt(msgsock,..) call after the 
msgsock = accept() line. Optval and optlen were set to zero, optname
can be 0 or SO_DEBUG. It is not clear whether this kind of call is
supposed to produce output of any kind. My question and remark is
on the result: A simple user can cause a VAX (8530 on Ultrix 2.0-1 and
750 on 4.3BSD) to crash ("protection trap 9" or something)
	Should this be so easy ? Is there a patch for it ? That is the 
question. I know this is not a bug report, but I hope it's enough for 
someone to answer me.

		George Lekkas
		lekkas@theseas.uucp   NTUA, Athens, Greece,
		lekkas@cernvax.uucp   CERN-SPS Div., Geneva, Switzerland.
							

chris@mimsy.UUCP (Chris Torek) (07/27/88)

In article <758@cernvax.UUCP> lekkas@cernvax.UUCP (George P. Lekkas) writes:
-... a setsockopt at the TCP protocol level, that is set the level=6.
-This call was made on the socket returned from accepting a connection
-request, at the server side.  All sockets were of the SOCK_STREAM type ....
-Just add the setsockopt(msgsock,..) call after the msgsock = accept()
-line. Optval and optlen were set to zero, optname can be 0 or SO_DEBUG.
-... the result: ... to crash ("protection trap 9" or something)
-	Should this be so easy?

Certainly not.  And indeed, my machine (an 8250 running 4.3BSD-tahoe,
more or less) does not crash.  I get an `EINVAL' (invalid argument)
error.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris