From: Matthieu Buffet Date: Wed, 1 Jul 2026 21:46:28 +0000 (+0200) Subject: selftests/landlock: Add test for TCP fast open X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f4b30e0b1d488e7ffd8ea28d1365b9ba8e551edb;p=thirdparty%2Fkernel%2Flinux.git selftests/landlock: Add test for TCP fast open Enforce that TCP Fast Open is controlled by LANDLOCK_ACCESS_NET_CONNECT_TCP. Semantics of connect() and sendmsg(MSG_FASTOPEN) should be identical from Landlock's perspective. Also enforce error code consistency, since UDP sockets ignore the MSG_FASTOPEN flag while Unix sockets reject it. Signed-off-by: Matthieu Buffet Link: https://patch.msgid.link/20260701214628.33319-2-matthieu@buffet.re Cc: stable@vger.kernel.org [mic: Fix formatting] Signed-off-by: Mickaël Salaün --- diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2ed1f76b7a8b1..be2eb88092fb3 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -1281,6 +1281,103 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +TEST_F(protocol, tcp_fastopen) +{ + const bool restricted = variant->sandbox == TCP_SANDBOX && + variant->prot.type == SOCK_STREAM && + (variant->prot.protocol == IPPROTO_TCP || + variant->prot.protocol == IPPROTO_IP) && + (variant->prot.domain == AF_INET || + variant->prot.domain == AF_INET6); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + int bind_fd, client_fd, status; + char buf; + pid_t child; + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + + if (variant->sandbox == TCP_SANDBOX) { + const int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Fast Open with no address. */ + ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN); + if (self->srv0.protocol.domain == AF_UNIX) { + EXPECT_EQ(-ENOTCONN, ret); + } else if (self->srv0.protocol.type == SOCK_DGRAM) { + EXPECT_EQ(-EDESTADDRREQ, ret); + } else { + EXPECT_EQ(-EINVAL, ret); + } + + /* Fast Open to a denied address. */ + ret = sendto_variant(connect_fd, &self->srv0, "A", 1, + MSG_FASTOPEN); + if (restricted) { + EXPECT_EQ(-EACCES, ret); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EOPNOTSUPP, ret); + } else { + EXPECT_EQ(0, ret); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->exit_code); + return; + } + + client_fd = bind_fd; + if (!restricted && self->srv0.protocol.type == SOCK_STREAM && + self->srv0.protocol.domain != AF_UNIX) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + if (restricted) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(ENOTCONN, errno); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(EINVAL, errno); + } else { + EXPECT_EQ(1, read(client_fd, &buf, 1)); + EXPECT_EQ('A', buf); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + EXPECT_EQ(0, close(bind_fd)); +} + TEST_F(protocol, sendmsg_stream) { int srv0_fd, tmp_fd, client_fd, res;