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