]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/landlock: Add test for TCP fast open
authorMatthieu Buffet <matthieu@buffet.re>
Wed, 1 Jul 2026 21:46:28 +0000 (23:46 +0200)
committerMickaël Salaün <mic@digikod.net>
Fri, 10 Jul 2026 10:59:09 +0000 (12:59 +0200)
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 <matthieu@buffet.re>
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 <mic@digikod.net>
tools/testing/selftests/landlock/net_test.c

index 2ed1f76b7a8b15eef16ca9c9760d11d9485d975c..be2eb88092fb34fa7179e1c09fde79b6f012c1f5 100644 (file)
@@ -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;