From: John Ericson <John.Ericson@Obsidian.Systems>
To: "David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Kuniyuki Iwashima <kuniyu@google.com>
Cc: John Ericson <mail@johnericson.me>,
Simon Horman <horms@kernel.org>,
Christian Brauner <brauner@kernel.org>,
David Rheinsberg <david@readahead.eu>,
Cong Wang <cwang@multikernel.io>,
John Ericson <john.ericson@obsidian.systems>,
Sergei Zimmerman <sergei@zimmerman.foo>,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH net v2 2/3] selftests/net/af_unix: test listen() rejects wrong socket states
Date: Fri, 3 Jul 2026 04:14:11 -0400 [thread overview]
Message-ID: <20260703081416.2583118-2-John.Ericson@Obsidian.Systems> (raw)
In-Reply-To: <20260703081416.2583118-1-John.Ericson@Obsidian.Systems>
From: John Ericson <mail@johnericson.me>
Add a regression test for the `unix_listen()` state check. The key case
is `listen()` on a bound socket that has already been connected: it is
no longer in `TCP_CLOSE` or `TCP_LISTEN`, so it must fail with `EINVAL`.
A `prepare_peercred()` call slipped in ahead of that check once left
`err` at 0 and made `listen()` silently succeed there instead; this
guards against a repeat.
The neighbouring outcomes are covered too so they cannot regress the
same way: a bound socket in `TCP_CLOSE` listens fine, re-`listen()`ing a
socket already in `TCP_LISTEN` is allowed, and an unbound socket fails
with `EINVAL`.
Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@johnericson.me>
---
.../testing/selftests/net/af_unix/.gitignore | 1 +
tools/testing/selftests/net/af_unix/Makefile | 1 +
.../selftests/net/af_unix/unix_listen.c | 126 ++++++++++++++++++
3 files changed, 128 insertions(+)
create mode 100644 tools/testing/selftests/net/af_unix/unix_listen.c
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..973176644103 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -6,3 +6,4 @@ scm_rights
so_peek_off
unix_connect
unix_connreset
+unix_listen
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..57d159803a3a 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -14,6 +14,7 @@ TEST_GEN_PROGS := \
so_peek_off \
unix_connect \
unix_connreset \
+ unix_listen \
# end of TEST_GEN_PROGS
include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c
new file mode 100644
index 000000000000..7b5264c97f52
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/unix_listen.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the state checks in AF_UNIX listen().
+ *
+ * The central case is a regression test: listen() on a bound socket that
+ * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
+ * fail with EINVAL. A prior change accidentally let it return success
+ * without doing anything, because a helper called in between reset the
+ * error code to 0. The neighbouring checks (unbound, already listening)
+ * are tested too so they cannot silently regress the same way.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "kselftest_harness.h"
+
+/* Fill @addr with an abstract-namespace address named @name. */
+static socklen_t unix_abstract(struct sockaddr_un *addr, const char *name)
+{
+ size_t len = strlen(name);
+
+ memset(addr, 0, sizeof(*addr));
+ addr->sun_family = AF_UNIX;
+ /* Leading NUL selects the abstract namespace (no filesystem entry). */
+ memcpy(addr->sun_path + 1, name, len);
+
+ return offsetof(struct sockaddr_un, sun_path) + 1 + len;
+}
+
+FIXTURE(unix_listen)
+{
+ int sk; /* socket under test */
+ int server; /* a listening peer, when a test needs one */
+ struct sockaddr_un addr, srv_addr;
+ socklen_t addrlen, srv_addrlen;
+};
+
+FIXTURE_SETUP(unix_listen)
+{
+ self->sk = -1;
+ self->server = -1;
+ self->addrlen = unix_abstract(&self->addr, "unix_listen.sk");
+ self->srv_addrlen = unix_abstract(&self->srv_addr, "unix_listen.srv");
+}
+
+FIXTURE_TEARDOWN(unix_listen)
+{
+ if (self->sk >= 0)
+ close(self->sk);
+ if (self->server >= 0)
+ close(self->server);
+ /* Abstract addresses are released automatically on close. */
+}
+
+/* A bound socket in TCP_CLOSE is the normal, allowed case. */
+TEST_F(unix_listen, bound_is_ok)
+{
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+
+ EXPECT_EQ(0, listen(self->sk, 8));
+}
+
+/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
+TEST_F(unix_listen, relisten_is_ok)
+{
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+ ASSERT_EQ(0, listen(self->sk, 8));
+
+ EXPECT_EQ(0, listen(self->sk, 16));
+}
+
+/* listen() on an unbound socket fails: there is nothing to listen on. */
+TEST_F(unix_listen, unbound_is_einval)
+{
+ int ret;
+
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+
+ ret = listen(self->sk, 8);
+ EXPECT_EQ(-1, ret);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * The regression: a bound socket that has already been connected is not in
+ * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
+ * than quietly succeeding.
+ */
+TEST_F(unix_listen, connected_is_einval)
+{
+ int ret;
+
+ self->server = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->server);
+ ASSERT_EQ(0, bind(self->server, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen));
+ ASSERT_EQ(0, listen(self->server, 8));
+
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ /* Bind first so the unbound check does not mask the state check. */
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+ ASSERT_EQ(0, connect(self->sk, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen));
+
+ ret = listen(self->sk, 8);
+ EXPECT_EQ(-1, ret);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_HARNESS_MAIN
--
2.54.0
next prev parent reply other threads:[~2026-07-03 8:15 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-03 8:14 [PATCH net v2 1/3] af_unix: fix listen() succeeding on sockets in the wrong state John Ericson
2026-07-03 8:14 ` John Ericson [this message]
2026-07-03 13:17 ` [PATCH net v2 2/3] selftests/net/af_unix: test listen() rejects wrong socket states Christian Brauner
2026-07-04 1:54 ` Kuniyuki Iwashima
2026-07-03 8:14 ` [PATCH net-next v2 3/3] af_unix: Clean up error handling in unix_listen() John Ericson
2026-07-03 13:17 ` Christian Brauner
2026-07-04 1:46 ` Kuniyuki Iwashima
2026-07-03 13:17 ` [PATCH net v2 1/3] af_unix: fix listen() succeeding on sockets in the wrong state Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260703081416.2583118-2-John.Ericson@Obsidian.Systems \
--to=john.ericson@obsidian.systems \
--cc=brauner@kernel.org \
--cc=cwang@multikernel.io \
--cc=davem@davemloft.net \
--cc=david@readahead.eu \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mail@johnericson.me \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sergei@zimmerman.foo \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox