The branch main has been updated by glebius:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=c7f803c71dae9ca64abccc0b17e9014fe66e6d21

commit c7f803c71dae9ca64abccc0b17e9014fe66e6d21
Author:     Gleb Smirnoff <gleb...@freebsd.org>
AuthorDate: 2025-03-07 06:57:44 +0000
Commit:     Gleb Smirnoff <gleb...@freebsd.org>
CommitDate: 2025-03-07 06:57:44 +0000

    inpcb: fix a panic with SO_REUSEPORT_LB + connect(2) misuse
    
    This combination doesn't make any sense.  This socket option makes sense
    only on a socket that is going to be a listening one.  There are two
    options here: refuse connect(2) on a socket that has the option set
    previously, or ignore (and clear) the option.  After some discussion on
    phabricator, we have chosen the former, for safety and consistency
    reasons.  Any programmer that runs this sequence is doing something wrong
    and should be informed of that with appropriate error code.
    
    Since connect(2) is a SUS API that has a defined set of error codes, none
    of which corresponds to "a socket has non-standard incompatible socket
    option set", we decided to return the same error that an already listening
    socket would return.
    
    Reviewed by:            markj
    Differential Revision:  https://reviews.freebsd.org/D49150
---
 sys/netinet/tcp_usrreq.c                 |  4 +-
 tests/sys/netinet/so_reuseport_lb_test.c | 65 ++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c
index 03687c1d3515..13e66f758d45 100644
--- a/sys/netinet/tcp_usrreq.c
+++ b/sys/netinet/tcp_usrreq.c
@@ -526,7 +526,7 @@ tcp_usr_connect(struct socket *so, struct sockaddr *nam, 
struct thread *td)
        }
        if ((error = prison_remote_ip4(td->td_ucred, &sinp->sin_addr)) != 0)
                goto out;
-       if (SOLISTENING(so)) {
+       if (SOLISTENING(so) || so->so_options & SO_REUSEPORT_LB) {
                error = EOPNOTSUPP;
                goto out;
        }
@@ -593,7 +593,7 @@ tcp6_usr_connect(struct socket *so, struct sockaddr *nam, 
struct thread *td)
                error = EAFNOSUPPORT;
                goto out;
        }
-       if (SOLISTENING(so)) {
+       if (SOLISTENING(so) || so->so_options & SO_REUSEPORT_LB) {
                error = EOPNOTSUPP;
                goto out;
        }
diff --git a/tests/sys/netinet/so_reuseport_lb_test.c 
b/tests/sys/netinet/so_reuseport_lb_test.c
index aaadaead5e23..a1b5a3f94f61 100644
--- a/tests/sys/netinet/so_reuseport_lb_test.c
+++ b/tests/sys/netinet/so_reuseport_lb_test.c
@@ -478,6 +478,69 @@ ATF_TC_BODY(bind_without_listen, tc)
        ATF_REQUIRE_MSG(error == 0, "close() failed: %s", strerror(errno));
 }
 
+/*
+ * Check that SO_REUSEPORT_LB doesn't mess with connect(2).
+ * Two sockets:
+ * 1) auxiliary peer socket 'p', where we connect to
+ * 2) test socket 's', that sets SO_REUSEPORT_LB and then connect(2)s to 'p'
+ */
+ATF_TC_WITHOUT_HEAD(connect_not_bound);
+ATF_TC_BODY(connect_not_bound, tc)
+{
+       struct sockaddr_in sin = {
+               .sin_family = AF_INET,
+               .sin_len = sizeof(sin),
+               .sin_addr = { htonl(INADDR_LOOPBACK) },
+       };
+       socklen_t slen = sizeof(struct sockaddr_in);
+       int p, s, rv;
+
+       ATF_REQUIRE((p = socket(PF_INET, SOCK_STREAM, 0)) > 0);
+       ATF_REQUIRE(bind(p, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+       ATF_REQUIRE(listen(p, 1) == 0);
+       ATF_REQUIRE(getsockname(p, (struct sockaddr *)&sin, &slen) == 0);
+
+       s = lb_listen_socket(PF_INET, 0);
+       rv = connect(s, (struct sockaddr *)&sin, sizeof(sin));
+       ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+           "Expected EOPNOTSUPP on connect(2) not met. Got %d, errno %d",
+           rv, errno);
+
+       close(p);
+       close(s);
+}
+
+/*
+ * Same as above, but we also bind(2) between setsockopt(2) of SO_REUSEPORT_LB
+ * and the connect(2).
+ */
+ATF_TC_WITHOUT_HEAD(connect_bound);
+ATF_TC_BODY(connect_bound, tc)
+{
+       struct sockaddr_in sin = {
+               .sin_family = AF_INET,
+               .sin_len = sizeof(sin),
+               .sin_addr = { htonl(INADDR_LOOPBACK) },
+       };
+       socklen_t slen = sizeof(struct sockaddr_in);
+       int p, s, rv;
+
+       ATF_REQUIRE((p = socket(PF_INET, SOCK_STREAM, 0)) > 0);
+       ATF_REQUIRE(bind(p, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+       ATF_REQUIRE(listen(p, 1) == 0);
+
+       s = lb_listen_socket(PF_INET, 0);
+       ATF_REQUIRE(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+       ATF_REQUIRE(getsockname(p, (struct sockaddr *)&sin, &slen) == 0);
+       rv = connect(s, (struct sockaddr *)&sin, sizeof(sin));
+       ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+           "Expected EOPNOTSUPP on connect(2) not met. Got %d, errno %d",
+           rv, errno);
+
+       close(p);
+       close(s);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
        ATF_TP_ADD_TC(tp, basic_ipv4);
@@ -486,6 +549,8 @@ ATF_TP_ADD_TCS(tp)
        ATF_TP_ADD_TC(tp, double_listen_ipv4);
        ATF_TP_ADD_TC(tp, double_listen_ipv6);
        ATF_TP_ADD_TC(tp, bind_without_listen);
+       ATF_TP_ADD_TC(tp, connect_not_bound);
+       ATF_TP_ADD_TC(tp, connect_bound);
 
        return (atf_no_error());
 }

Reply via email to