Subject: tightening control on socket bind()ing
To: None <tech-security@netbsd.org, tech-net@netbsd.org>
From: Luke Mewburn <lukem@cs.rmit.edu.au>
List: tech-security
Date: 03/09/1999 18:04:00
[ x-posted to tech-net, followups-to tech-security ]

a known problem with the way that sockets are bound to wildcard ports
when SO_REUSEADDR is in use is that any user/process may later bind
to the same address. e.g, given that a machine has two address;
localhost and 128.128.128.128, and:

	user	process		address		port	options
	----	-------		-------		----	-------
	root	nfsd		*		2049	SO_REUSEADDR

there's nothing to stop a user binding:

	haquer	someproc	127.0.0.1	2049	SO_REUSEADDR
	haquer	someproc	128.128.128.128	2049	SO_REUSEADDR

this effectively means that someproc will get the packets because of
the `tighter' binding than the root nfsd on *.2049.

i've looked at various solutions to this problem, including:

1. adding a setsockopt() - SO_NOREUSE - that if set forces SO_REUSEADDR
   to be ignored. this would require a lot of code modification.

2. don't use SO_REUSEADDR. unfortunately, this causes problems with
   some servers; they need it.

3. use a port < 1024. doesn't help nfs, etc. doesn't help non-root
   processes.

4. use the solution that FreeBSD use (derived from the OpenBSD
   solution): prevent a bind if another socket on the same port is
   bound, irregardless of whether SO_REUSE{ADDR,PORT} is set, if
   the following is not true:
	* the new bind() is being done by euid==0, or
	* the new bind() is of a different euid to the existing socket

after discussion with a few other people, it appears that `4.' is
probably the sanest/easiest solution; it requires no code modification
of existing daemons, and shouldn't affect.

i've attached diffs against -current which implement this.
an extension of this would be to store the realuid as well,
as that could possibly be used to speed up identd lookups (i
think openbsd did this after freebsd grabbed the code from
openbsd).

thoughts/comments/objections?


Index: kern/uipc_socket.c
===================================================================
RCS file: /cvsroot/src/sys/kern/uipc_socket.c,v
retrieving revision 1.43
diff -p -u -r1.43 uipc_socket.c
--- uipc_socket.c	1999/01/21 22:09:10	1.43
+++ uipc_socket.c	1999/03/09 06:47:27
@@ -100,6 +100,8 @@ socreate(dom, aso, type, proto)
 	so->so_proto = prp;
 	so->so_send = sosend;
 	so->so_receive = soreceive;
+	if (p != 0)
+		so->so_uid = p->p_ucred->cr_uid;
 	error = (*prp->pr_usrreq)(so, PRU_ATTACH, (struct mbuf *)0,
 	    (struct mbuf *)(long)proto, (struct mbuf *)0, p);
 	if (error) {
 
Index: kern/uipc_socket2.c
===================================================================
RCS file: /cvsroot/src/sys/kern/uipc_socket2.c,v
retrieving revision 1.27
diff -p -u -r1.27 uipc_socket2.c
--- uipc_socket2.c	1999/01/20 09:15:41	1.27
+++ uipc_socket2.c	1999/03/09 06:47:27
@@ -174,6 +174,7 @@ sonewconn1(head, connstatus)
 	so->so_pgid = head->so_pgid;
 	so->so_send = head->so_send;
 	so->so_receive = head->so_receive;
+	so->so_uid = head->so_uid;
 	(void) soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat);
 	soqinsque(head, so, soqueue);
 	if ((*so->so_proto->pr_usrreq)(so, PRU_ATTACH,
Index: netinet/in_pcb.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/in_pcb.c,v
retrieving revision 1.57
diff -p -u -r1.57 in_pcb.c
--- in_pcb.c	1998/12/19 02:46:12	1.57
+++ in_pcb.c	1999/03/09 06:47:27
@@ -219,6 +219,16 @@ in_pcbbind(v, nam, p)
 		    (p == 0 || (error = suser(p->p_ucred, &p->p_acflag))))
 			return (EACCES);
 #endif
+		if (so->so_uid && !IN_MULTICAST(sin->sin_addr.s_addr)) {
+			t = in_pcblookup_port(table, sin->sin_addr, lport, 1);
+			if (t &&
+			    (!in_nullhost(sin->sin_addr) ||
+			     !in_nullhost(t->inp_laddr) ||
+			     (t->inp_socket->so_options & SO_REUSEPORT) == 0)
+			    && (so->so_uid != t->inp_socket->so_uid)) {
+				return (EADDRINUSE);
+			}
+		}
 		t = in_pcblookup_port(table, sin->sin_addr, lport, wild);
 		if (t && (reuseport & t->inp_socket->so_options) == 0)
 			return (EADDRINUSE);
Index: sys/socketvar.h
===================================================================
RCS file: /cvsroot/src/sys/sys/socketvar.h,v
retrieving revision 1.36
diff -p -u -r1.36 socketvar.h
--- socketvar.h	1999/02/10 14:37:25	1.36
+++ socketvar.h	1999/03/09 06:47:28
@@ -121,7 +121,7 @@ struct socket {
 	int	(*so_receive) __P((struct socket *so, struct mbuf **paddr,
 				   struct uio *uio, struct mbuf **mp0,
 				   struct mbuf **controlp, int *flagsp));
-
+	uid_t	so_uid;			/* who opened the socket */
 };
 
 /*