[PATCH net v2 2/3] selftests/net/af_unix: test listen() rejects wrong socket states
From: John Ericson <hidden>
Date: 2026-07-03 08:15:09
Also in:
lkml
Subsystem:
kernel selftest framework, networking [general], networking [unix sockets], the rest · Maintainers:
Shuah Khan, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima, Linus Torvalds
From: John Ericson <redacted>
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 <redacted>
---
.../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