(Sorry if you are getting this message duplicated, I tried sending it to
tech@ before but it seems to get stuck somewhere along the way.)

I found this while looking at netcat and it seemed odd:

        if (family == AF_UNIX && uflag) {
                if (connect(s, NULL, 0) == -1)
                        err(1, "connect");
        }

POSIX 2008 [0] says:

  For SOCK_DGRAM sockets, [...] If the sa_family member of address is
  AF_UNSPEC, the socket's peer address shall be reset.

It also says:

  Issue 7
  [...]
    - Austin Group Interpretation 1003.1-2001 #188 is applied, changing
      the method used to reset a peer address for a datagram socket.
  [...]

Indeed, in POSIX 2004 [1] it said:

  For SOCK_DGRAM sockets, [...] If address is a null address for the
  protocol, the socket's peer address shall be reset.

Which is similar to connect(2):

  Datagram sockets may dissolve the association by connecting to an
  invalid address, such as a null address.

Now, I don't know if "null address for the protocol" was at any point in
time supposed to be interpreted as NULL-pointer/zero-size as opposed to
zeroed structure for example (notice that AF_UNSPEC is 0) or whether
such a thing ever worked in OpenBSD, but it certainly doesn't now and
hasn't for a while: it fails in sys_connect -> sockargs when checking
for the sockaddr size.

OpenBSD does support disassociating via a zeroed sockaddr, but the
syscall fails and it shouldn't; in fact, it supports disassociating by
using *any* sort of address as long as it's consistent in terms of size
because the disassociation (sodisconnect) happens before even looking at
the user-supplied sockaddr.  I believe this should to be cleaned up but
it is a change in syscall semantics (according to CVS this code,
soconnect, mostly comes directly from NetBSD from 23 years ago).

I did not look at what other BSD do, but below is a possible minimal
patch that aims to retain most of the current behaviour, which might or
might not be TRTTD.  It should apply to "current".

Cheers.

0: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
1: https://pubs.opengroup.org/onlinepubs/009695399/functions/connect.html

--- sys/kern/uipc_socket.c      2019-08-09 17:27:45 +0000
+++ sys/kern/uipc_socket.c      2019-08-10 17:41:07 +0000
@@ -66,6 +66,8 @@ void  soreaper(void *);
 void   soput(void *);
 int    somove(struct socket *, int);
 
+int    nam2sa(struct mbuf *, struct sockaddr **);
+
 void   filt_sordetach(struct knote *kn);
 int    filt_soread(struct knote *kn, long hint);
 void   filt_sowdetach(struct knote *kn);
@@ -344,7 +346,8 @@ soaccept(struct socket *so, struct mbuf
 int
 soconnect(struct socket *so, struct mbuf *nam)
 {
-       int error;
+       struct sockaddr *sa;
+       int error = 0;
 
        soassertlocked(so);
 
@@ -353,14 +356,16 @@ soconnect(struct socket *so, struct mbuf
        /*
         * If protocol is connection-based, can only connect once.
         * Otherwise, if connected, try to disconnect first.
-        * This allows user to disconnect by connecting to, e.g.,
-        * a null address.
+        * This allows the user to disconnect by connecting to an invalid
+        * address, although only a "zero" address (AF_UNSPEC) will not
+        * result in error.
         */
        if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
            ((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
            (error = sodisconnect(so))))
                error = EISCONN;
-       else
+       else if (!(so->so_type == SOCK_DGRAM && nam2sa(nam, &sa) == 0 &&
+           sa->sa_family == AF_UNSPEC))
                error = (*so->so_proto->pr_usrreq)(so, PRU_CONNECT,
                    NULL, nam, NULL, curproc);
        return (error);
@@ -1965,6 +1970,19 @@ sohasoutofband(struct socket *so)
 }
 
 int
+nam2sa(struct mbuf *nam, struct sockaddr **soa)
+{
+       struct sockaddr *sa = mtod(nam, struct sockaddr *);
+
+       if (nam->m_len < offsetof(struct sockaddr, sa_data))
+               return EINVAL;
+       if (sa->sa_len != nam->m_len)
+               return EINVAL;
+       *soa = sa;
+       return 0;
+}
+
+int
 soo_kqfilter(struct file *fp, struct knote *kn)
 {
        struct socket *so = kn->kn_fp->f_data;
--- lib/libc/sys/connect.2      2019-08-10 17:34:42 +0000
+++ lib/libc/sys/connect.2      2019-08-10 17:38:22 +0000
@@ -73,7 +73,7 @@ only once; datagram sockets may use
 .Fn connect
 multiple times to change their association.
 Datagram sockets may dissolve the association
-by connecting to an invalid address, such as a null address.
+by connecting to an address of family AF_UNSPEC.
 .Pp
 If the socket is in non-blocking mode and the connection cannot be
 completed immediately, or if it is interrupted by a signal,
--- usr.bin/nc/netcat.c 2019-08-09 17:30:13 +0000
+++ usr.bin/nc/netcat.c 2019-08-10 17:47:16 +0000
@@ -629,7 +629,9 @@ main(int argc, char *argv[])
                                tls_free(tls_cctx);
                        }
                        if (family == AF_UNIX && uflag) {
-                               if (connect(s, NULL, 0) == -1)
+                               /* AF_UNSPEC == 0 */
+                               if (connect(s, &(struct sockaddr){0},
+                                   sizeof(struct sockaddr)) == -1)
                                        err(1, "connect");
                        }
 

Reply via email to