git: 3baada83bf2d - main - unix: only treat an empty sun_path as a peer descriptor for connectat(2)

From: Devin Teske <dteske_at_FreeBSD.org>
Date: Thu, 13 Aug 2026 20:19:14 UTC
The branch main has been updated by dteske:

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

commit 3baada83bf2deb48c7251f8de3f73e489338d4f1
Author:     Devin Teske <dteske@FreeBSD.org>
AuthorDate: 2026-08-13 20:17:09 +0000
Commit:     Devin Teske <dteske@FreeBSD.org>
CommitDate: 2026-08-13 20:17:09 +0000

    unix: only treat an empty sun_path as a peer descriptor for connectat(2)
    
    connect(2) passes AT_FDCWD to unp_connectat(), so the empty-path
    descriptor branch added in 6563dcb6b1f5 turned any sockaddr whose
    sun_path begins with a NUL byte into getsock(AT_FDCWD), failing with
    EBADF where the pathname lookup historically failed with ENOENT.
    
    Linux abstract namespace names are exactly that: the linuxulator
    passes them through with the leading NUL intact, and libxcb tries the
    abstract socket first, falling back to the pathname socket only on
    ENOENT or ECONNREFUSED. The EBADF made every Linux X11 client fail
    at startup with "Missing X server or $DISPLAY".
    
    Restrict the descriptor interpretation to fd != AT_FDCWD, matching
    the contract stated in 6563dcb6b1f5's commit message ("Accept an
    empty sun_path when fd is not AT_FDCWD"): connect(2) again reaches
    the pathname lookup and fails with ENOENT as it always did.
    
    Add a regression test: a NUL-leading, nonzero-length sun_path through
    connect(2) or connectat(2) with AT_FDCWD must fail the pathname
    lookup with ENOENT, not EBADF.
    
    Fixes:  6563dcb6b1f5 ("unix: allow connectat(2) to name the peer socket by descriptor")
    
    Reviewed by:    John Ericson <John.Ericson@Obsidian.Systems>, markj
    Differential Revision:  https://reviews.freebsd.org/D58792
---
 sys/kern/uipc_usrreq.c          |  2 +-
 tests/sys/kern/unix_connectat.c | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
index 8c67b623323e..1e1f340bd0c1 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -3115,7 +3115,7 @@ unp_connectat_peer(struct thread *td, int fd, const char *buf,
 	 * fallback; if not, it may be an O_PATH handle for a bound socket's
 	 * vnode, so fall through to an EMPTYPATH lookup.
 	 */
-	if (*buf == '\0') {
+	if (*buf == '\0' && fd != AT_FDCWD) {
 		error = unp_socket_fd_peer(td, fd, so2p);
 		if (error != ENOTSOCK)
 			return (error);
diff --git a/tests/sys/kern/unix_connectat.c b/tests/sys/kern/unix_connectat.c
index 9861287f5362..04885ee464ee 100644
--- a/tests/sys/kern/unix_connectat.c
+++ b/tests/sys/kern/unix_connectat.c
@@ -78,6 +78,13 @@ static const struct sockaddr_un empty_sun = {
 	.sun_len = offsetof(struct sockaddr_un, sun_path),
 };
 
+/* A nonempty address whose path starts with NUL, as Linux abstract names do. */
+static const struct sockaddr_un nul_sun = {
+	.sun_family = AF_UNIX,
+	.sun_len = offsetof(struct sockaddr_un, sun_path) + 2,
+	.sun_path = "\0x",
+};
+
 /* Make a bound, listening stream socket. */
 static int
 mklistener(const char *path)
@@ -657,6 +664,32 @@ ATF_TC_BODY(empty_path_at_fdcwd, tc)
 	ATF_REQUIRE_EQ(0, close(s));
 }
 
+/*
+ * A NUL-leading path with a nonzero length is not the empty-path
+ * extension: connect(2) and connectat(2) with AT_FDCWD must perform a
+ * pathname lookup and fail with ENOENT, not treat AT_FDCWD as a peer
+ * descriptor and fail with EBADF.
+ *
+ * Such addresses occur in the wild: they name Linux abstract namespace
+ * sockets, and the linuxulator passes them through with the leading NUL
+ * intact.  libxcb tries the abstract X11 socket first and falls back to
+ * the pathname socket only on ENOENT or ECONNREFUSED, so when connect(2)
+ * briefly returned EBADF here, every Linux X11 client on the linuxulator
+ * failed at startup with "Missing X server or $DISPLAY".
+ */
+ATF_TC_WITHOUT_HEAD(nul_path_at_fdcwd);
+ATF_TC_BODY(nul_path_at_fdcwd, tc)
+{
+	int s;
+
+	ATF_REQUIRE((s = socket(PF_UNIX, SOCK_STREAM, 0)) >= 0);
+	ATF_REQUIRE_ERRNO(ENOENT, connect(s,
+	    (const struct sockaddr *)&nul_sun, nul_sun.sun_len) == -1);
+	ATF_REQUIRE_ERRNO(ENOENT, connectat(AT_FDCWD, s,
+	    (const struct sockaddr *)&nul_sun, nul_sun.sun_len) == -1);
+	ATF_REQUIRE_EQ(0, close(s));
+}
+
 /* Error matrix for unsuitable descriptors and peers. */
 ATF_TC_WITHOUT_HEAD(bad_peers);
 ATF_TC_BODY(bad_peers, tc)
@@ -763,6 +796,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, devfd_mode_rdlnk);
 	ATF_TP_ADD_TC(tp, devfd_mode_nodup_rdlnk);
 	ATF_TP_ADD_TC(tp, empty_path_at_fdcwd);
+	ATF_TP_ADD_TC(tp, nul_path_at_fdcwd);
 	ATF_TP_ADD_TC(tp, bad_peers);
 	ATF_TP_ADD_TC(tp, cap_connectat);
 	ATF_TP_ADD_TC(tp, cap_connectat_denied);