mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
selftests/net/af_unix: test listen() rejects wrong socket states
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, calling listen() again on a
socket already in TCP_LISTEN is allowed, and an unbound socket fails with
EINVAL.
Each case runs for both listenable socket types (SOCK_STREAM and
SOCK_SEQPACKET) and both pathname and abstract addresses.
Fixes: fd0a109a0f ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Signed-off-by: John Ericson <mail@johnericson.me>
Link: https://patch.msgid.link/20260718182903.2295560-2-John.Ericson@Obsidian.Systems
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
f0d9c3ffc2
commit
7f57c650d0
|
|
@ -6,3 +6,4 @@ scm_rights
|
|||
so_peek_off
|
||||
unix_connect
|
||||
unix_connreset
|
||||
unix_listen
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ TEST_GEN_PROGS := \
|
|||
so_peek_off \
|
||||
unix_connect \
|
||||
unix_connreset \
|
||||
unix_listen \
|
||||
# end of TEST_GEN_PROGS
|
||||
|
||||
include ../../lib.mk
|
||||
|
|
|
|||
187
tools/testing/selftests/net/af_unix/unix_listen.c
Normal file
187
tools/testing/selftests/net/af_unix/unix_listen.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// 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.
|
||||
*
|
||||
* Every case runs for both listenable socket types (SOCK_STREAM and
|
||||
* SOCK_SEQPACKET) and both pathname and abstract addresses.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#define SK_NAME "unix_listen_sk"
|
||||
#define SRV_NAME "unix_listen_srv"
|
||||
|
||||
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_VARIANT(unix_listen)
|
||||
{
|
||||
int type;
|
||||
int abstract;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_listen, stream_pathname)
|
||||
{
|
||||
.type = SOCK_STREAM,
|
||||
.abstract = 0,
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_listen, stream_abstract)
|
||||
{
|
||||
.type = SOCK_STREAM,
|
||||
.abstract = 1,
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname)
|
||||
{
|
||||
.type = SOCK_SEQPACKET,
|
||||
.abstract = 0,
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract)
|
||||
{
|
||||
.type = SOCK_SEQPACKET,
|
||||
.abstract = 1,
|
||||
};
|
||||
|
||||
/* Fill @addr with a pathname or abstract address named @name. */
|
||||
static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name,
|
||||
int abstract)
|
||||
{
|
||||
size_t len = strlen(name);
|
||||
|
||||
memset(addr, 0, sizeof(*addr));
|
||||
addr->sun_family = AF_UNIX;
|
||||
/* An abstract address leads with a NUL and has no filesystem entry. */
|
||||
memcpy(addr->sun_path + (abstract ? 1 : 0), name, len);
|
||||
|
||||
return offsetof(struct sockaddr_un, sun_path) + len + 1;
|
||||
}
|
||||
|
||||
FIXTURE_SETUP(unix_listen)
|
||||
{
|
||||
self->sk = -1;
|
||||
self->server = -1;
|
||||
self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract);
|
||||
self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME,
|
||||
variant->abstract);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(unix_listen)
|
||||
{
|
||||
if (self->sk >= 0)
|
||||
close(self->sk);
|
||||
if (self->server >= 0)
|
||||
close(self->server);
|
||||
|
||||
/* Pathname sockets leave a filesystem entry behind; abstract ones do not. */
|
||||
if (!variant->abstract) {
|
||||
remove(SK_NAME);
|
||||
remove(SRV_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
/* A bound socket in TCP_CLOSE is the normal, allowed case. */
|
||||
TEST_F(unix_listen, bound_is_ok)
|
||||
{
|
||||
int err;
|
||||
|
||||
self->sk = socket(AF_UNIX, variant->type, 0);
|
||||
ASSERT_LE(0, self->sk);
|
||||
|
||||
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = listen(self->sk, 8);
|
||||
EXPECT_EQ(0, err);
|
||||
}
|
||||
|
||||
/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
|
||||
TEST_F(unix_listen, relisten_is_ok)
|
||||
{
|
||||
int err;
|
||||
|
||||
self->sk = socket(AF_UNIX, variant->type, 0);
|
||||
ASSERT_LE(0, self->sk);
|
||||
|
||||
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = listen(self->sk, 8);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = listen(self->sk, 16);
|
||||
EXPECT_EQ(0, err);
|
||||
}
|
||||
|
||||
/* listen() on an unbound socket fails: there is nothing to listen on. */
|
||||
TEST_F(unix_listen, unbound_is_einval)
|
||||
{
|
||||
int err;
|
||||
|
||||
self->sk = socket(AF_UNIX, variant->type, 0);
|
||||
ASSERT_LE(0, self->sk);
|
||||
|
||||
err = listen(self->sk, 8);
|
||||
EXPECT_EQ(-1, err);
|
||||
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 err;
|
||||
|
||||
self->server = socket(AF_UNIX, variant->type, 0);
|
||||
ASSERT_LE(0, self->server);
|
||||
|
||||
err = bind(self->server, (struct sockaddr *)&self->srv_addr,
|
||||
self->srv_addrlen);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = listen(self->server, 8);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
self->sk = socket(AF_UNIX, variant->type, 0);
|
||||
ASSERT_LE(0, self->sk);
|
||||
|
||||
/* Bind first so the unbound check does not mask the state check. */
|
||||
err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = connect(self->sk, (struct sockaddr *)&self->srv_addr,
|
||||
self->srv_addrlen);
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
err = listen(self->sk, 8);
|
||||
EXPECT_EQ(-1, err);
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Loading…
Reference in New Issue
Block a user