]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/landlock: Add tests for UDP bind/connect
authorMatthieu Buffet <matthieu@buffet.re>
Thu, 11 Jun 2026 16:21:03 +0000 (18:21 +0200)
committerMickaël Salaün <mic@digikod.net>
Sat, 13 Jun 2026 21:15:06 +0000 (23:15 +0200)
Make basic changes to the existing bind() and connect() test suite to
cover UDP restriction.

Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
Link: https://patch.msgid.link/20260611162107.49278-4-matthieu@buffet.re
[mic: Update audit.connect_bound, fix comment formatting]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
tools/testing/selftests/landlock/net_test.c

index 23d860e763725fa683f7240668e6e644a45f6e42..72e6fbdc2ea2aa4a19164239237d805f8bca3efe 100644 (file)
@@ -35,6 +35,7 @@ enum sandbox_type {
        NO_SANDBOX,
        /* This may be used to test rules that allow *and* deny accesses. */
        TCP_SANDBOX,
+       UDP_SANDBOX,
 };
 
 static int set_service(struct service_fixture *const srv,
@@ -93,23 +94,53 @@ static bool prot_is_tcp(const struct protocol_variant *const prot)
               (prot->protocol == IPPROTO_TCP || prot->protocol == IPPROTO_IP);
 }
 
+static bool prot_is_udp(const struct protocol_variant *const prot)
+{
+       return (prot->domain == AF_INET || prot->domain == AF_INET6) &&
+              prot->type == SOCK_DGRAM &&
+              (prot->protocol == IPPROTO_UDP || prot->protocol == IPPROTO_IP);
+}
+
 static bool is_restricted(const struct protocol_variant *const prot,
                          const enum sandbox_type sandbox)
 {
        if (sandbox == TCP_SANDBOX)
                return prot_is_tcp(prot);
+       else if (sandbox == UDP_SANDBOX)
+               return prot_is_udp(prot);
        return false;
 }
 
 static int socket_variant(const struct service_fixture *const srv)
 {
+       /* Arbitrary value just to not block other tests indefinitely. */
+       const struct timeval timeout = {
+               .tv_sec = 0,
+               .tv_usec = 100000,
+       };
+       int sockfd;
        int ret;
 
-       ret = socket(srv->protocol.domain, srv->protocol.type | SOCK_CLOEXEC,
-                    srv->protocol.protocol);
-       if (ret < 0)
+       sockfd = socket(srv->protocol.domain, srv->protocol.type | SOCK_CLOEXEC,
+                       srv->protocol.protocol);
+       if (sockfd < 0)
                return -errno;
-       return ret;
+
+       ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
+                        sizeof(timeout));
+       if (ret != 0) {
+               ret = -errno;
+               close(sockfd);
+               return ret;
+       }
+       ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
+                        sizeof(timeout));
+       if (ret != 0) {
+               ret = -errno;
+               close(sockfd);
+               return ret;
+       }
+       return sockfd;
 }
 
 #ifndef SIN6_LEN_RFC2133
@@ -271,10 +302,9 @@ FIXTURE_VARIANT(protocol)
 
 FIXTURE_SETUP(protocol)
 {
-       const struct protocol_variant prot_unspec = {
-               .domain = AF_UNSPEC,
-               .type = SOCK_STREAM,
-       };
+       struct protocol_variant prot_unspec = variant->prot;
+
+       prot_unspec.domain = AF_UNSPEC;
 
        disable_caps(_metadata);
 
@@ -510,6 +540,92 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
        },
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_udp1) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET,
+               .type = SOCK_DGRAM,
+               .protocol = IPPROTO_UDP,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_udp2) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET,
+               .type = SOCK_DGRAM,
+               /* IPPROTO_IP == 0 */
+               .protocol = IPPROTO_IP,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_udp1) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET6,
+               .type = SOCK_DGRAM,
+               .protocol = IPPROTO_UDP,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_udp2) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET6,
+               .type = SOCK_DGRAM,
+               /* IPPROTO_IP == 0 */
+               .protocol = IPPROTO_IP,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_tcp) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET,
+               .type = SOCK_STREAM,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_tcp) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET6,
+               .type = SOCK_STREAM,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_unix_stream) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_UNIX,
+               .type = SOCK_STREAM,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_unix_datagram) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_UNIX,
+               .type = SOCK_DGRAM,
+       },
+};
+
 static void test_bind_and_connect(struct __test_metadata *const _metadata,
                                  const struct service_fixture *const srv,
                                  const bool deny_bind, const bool deny_connect)
@@ -602,7 +718,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
                ret = connect_variant(connect_fd, srv);
                if (deny_connect) {
                        EXPECT_EQ(-EACCES, ret);
-               } else if (deny_bind) {
+               } else if (deny_bind && srv->protocol.type == SOCK_STREAM) {
                        /* No listening server. */
                        EXPECT_EQ(-ECONNREFUSED, ret);
                } else {
@@ -641,18 +757,25 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
 
 TEST_F(protocol, bind)
 {
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
+               const __u64 bind_access =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_BIND_TCP :
+                                LANDLOCK_ACCESS_NET_BIND_UDP);
+               const __u64 conn_access =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
                const struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                             LANDLOCK_ACCESS_NET_CONNECT_TCP,
+                       .handled_access_net = bind_access | conn_access,
                };
-               const struct landlock_net_port_attr tcp_bind_connect_p0 = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr bind_connect_p0 = {
+                       .allowed_access = bind_access | conn_access,
                        .port = self->srv0.port,
                };
-               const struct landlock_net_port_attr tcp_connect_p1 = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr connect_p1 = {
+                       .allowed_access = conn_access,
                        .port = self->srv1.port,
                };
                int ruleset_fd;
@@ -664,12 +787,26 @@ TEST_F(protocol, bind)
                /* Allows connect and bind for the first port.  */
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_connect_p0, 0));
+                                           &bind_connect_p0, 0));
 
                /* Allows connect and denies bind for the second port. */
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_connect_p1, 0));
+                                           &connect_p1, 0));
+
+               /*
+                * For UDP sockets, allows binding to ephemeral ports (required
+                * to connect or send a first datagram)
+                */
+               if (variant->sandbox == UDP_SANDBOX) {
+                       const struct landlock_net_port_attr bind_ephemeral = {
+                               .allowed_access = bind_access,
+                               .port = 0,
+                       };
+                       ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+                                                      LANDLOCK_RULE_NET_PORT,
+                                                      &bind_ephemeral, 0));
+               }
 
                enforce_ruleset(_metadata, ruleset_fd);
                EXPECT_EQ(0, close(ruleset_fd));
@@ -691,18 +828,25 @@ TEST_F(protocol, bind)
 
 TEST_F(protocol, connect)
 {
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
+               const __u64 bind_access =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_BIND_TCP :
+                                LANDLOCK_ACCESS_NET_BIND_UDP);
+               const __u64 conn_access =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
                const struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                             LANDLOCK_ACCESS_NET_CONNECT_TCP,
+                       .handled_access_net = bind_access | conn_access,
                };
-               const struct landlock_net_port_attr tcp_bind_connect_p0 = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr bind_connect_p0 = {
+                       .allowed_access = bind_access | conn_access,
                        .port = self->srv0.port,
                };
-               const struct landlock_net_port_attr tcp_bind_p1 = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+               const struct landlock_net_port_attr bind_p1 = {
+                       .allowed_access = bind_access,
                        .port = self->srv1.port,
                };
                int ruleset_fd;
@@ -714,12 +858,26 @@ TEST_F(protocol, connect)
                /* Allows connect and bind for the first port. */
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_connect_p0, 0));
+                                           &bind_connect_p0, 0));
 
                /* Allows bind and denies connect for the second port. */
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_p1, 0));
+                                           &bind_p1, 0));
+
+               /*
+                * For UDP sockets, allows binding to ephemeral ports (required
+                * to connect or send a first datagram)
+                */
+               if (variant->sandbox == UDP_SANDBOX) {
+                       const struct landlock_net_port_attr bind_ephemeral = {
+                               .allowed_access = bind_access,
+                               .port = 0,
+                       };
+                       ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+                                                      LANDLOCK_RULE_NET_PORT,
+                                                      &bind_ephemeral, 0));
+               }
 
                enforce_ruleset(_metadata, ruleset_fd);
                EXPECT_EQ(0, close(ruleset_fd));
@@ -737,16 +895,20 @@ TEST_F(protocol, connect)
 
 TEST_F(protocol, bind_unspec)
 {
+       const __u64 bind_access = (variant->sandbox == TCP_SANDBOX ?
+                                          LANDLOCK_ACCESS_NET_BIND_TCP :
+                                          LANDLOCK_ACCESS_NET_BIND_UDP);
        const struct landlock_ruleset_attr ruleset_attr = {
-               .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+               .handled_access_net = bind_access,
        };
-       const struct landlock_net_port_attr tcp_bind = {
-               .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+       const struct landlock_net_port_attr rule_bind = {
+               .allowed_access = bind_access,
                .port = self->srv0.port,
        };
        int bind_fd, ret;
 
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
                const int ruleset_fd = landlock_create_ruleset(
                        &ruleset_attr, sizeof(ruleset_attr), 0);
                ASSERT_LE(0, ruleset_fd);
@@ -754,7 +916,7 @@ TEST_F(protocol, bind_unspec)
                /* Allows bind. */
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind, 0));
+                                           &rule_bind, 0));
                enforce_ruleset(_metadata, ruleset_fd);
                EXPECT_EQ(0, close(ruleset_fd));
        }
@@ -782,7 +944,8 @@ TEST_F(protocol, bind_unspec)
        }
        EXPECT_EQ(0, close(bind_fd));
 
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
                const int ruleset_fd = landlock_create_ruleset(
                        &ruleset_attr, sizeof(ruleset_attr), 0);
                ASSERT_LE(0, ruleset_fd);
@@ -828,11 +991,21 @@ TEST_F(protocol, bind_unspec)
 
 TEST_F(protocol, connect_unspec)
 {
-       const struct landlock_ruleset_attr ruleset_attr = {
-               .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+       const __u64 connect_right =
+               (variant->sandbox == TCP_SANDBOX ?
+                        LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                        LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+       const __u64 bind_right = (variant->sandbox == TCP_SANDBOX ?
+                                         LANDLOCK_ACCESS_NET_BIND_TCP :
+                                         LANDLOCK_ACCESS_NET_BIND_UDP);
+       const struct landlock_ruleset_attr ruleset_conn = {
+               .handled_access_net = connect_right,
        };
-       const struct landlock_net_port_attr tcp_connect = {
-               .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+       const struct landlock_ruleset_attr ruleset_conn_bind = {
+               .handled_access_net = connect_right | bind_right,
+       };
+       const struct landlock_net_port_attr rule_connect = {
+               .allowed_access = connect_right,
                .port = self->srv0.port,
        };
        int bind_fd, client_fd, status;
@@ -865,15 +1038,16 @@ TEST_F(protocol, connect_unspec)
                        EXPECT_EQ(0, ret);
                }
 
-               if (variant->sandbox == TCP_SANDBOX) {
+               if (variant->sandbox == TCP_SANDBOX ||
+                   variant->sandbox == UDP_SANDBOX) {
                        const int ruleset_fd = landlock_create_ruleset(
-                               &ruleset_attr, sizeof(ruleset_attr), 0);
+                               &ruleset_conn, sizeof(ruleset_conn), 0);
                        ASSERT_LE(0, ruleset_fd);
 
                        /* Allows connect. */
                        ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
                                                       LANDLOCK_RULE_NET_PORT,
-                                                      &tcp_connect, 0));
+                                                      &rule_connect, 0));
                        enforce_ruleset(_metadata, ruleset_fd);
                        EXPECT_EQ(0, close(ruleset_fd));
                }
@@ -896,12 +1070,14 @@ TEST_F(protocol, connect_unspec)
                        EXPECT_EQ(0, ret);
                }
 
-               if (variant->sandbox == TCP_SANDBOX) {
+               if (variant->sandbox == TCP_SANDBOX ||
+                   variant->sandbox == UDP_SANDBOX) {
                        const int ruleset_fd = landlock_create_ruleset(
-                               &ruleset_attr, sizeof(ruleset_attr), 0);
+                               &ruleset_conn_bind, sizeof(ruleset_conn_bind),
+                               0);
                        ASSERT_LE(0, ruleset_fd);
 
-                       /* Denies connect. */
+                       /* Denies connect and bind. */
                        enforce_ruleset(_metadata, ruleset_fd);
                        EXPECT_EQ(0, close(ruleset_fd));
                }
@@ -975,6 +1151,13 @@ FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_tcp) {
        .type = SOCK_STREAM,
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ipv4, udp_sandbox_with_tcp) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .type = SOCK_STREAM,
+};
+
 /* clang-format off */
 FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_udp) {
        /* clang-format on */
@@ -989,6 +1172,13 @@ FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_udp) {
        .type = SOCK_DGRAM,
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ipv4, udp_sandbox_with_udp) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .type = SOCK_DGRAM,
+};
+
 FIXTURE_SETUP(ipv4)
 {
        const struct protocol_variant prot = {
@@ -1012,14 +1202,19 @@ TEST_F(ipv4, from_unix_to_inet)
 {
        int unix_stream_fd, unix_dgram_fd;
 
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
+               const __u64 access_rights =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_BIND_TCP |
+                                        LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                LANDLOCK_ACCESS_NET_BIND_UDP |
+                                        LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
                const struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                             LANDLOCK_ACCESS_NET_CONNECT_TCP,
+                       .handled_access_net = access_rights,
                };
                const struct landlock_net_port_attr tcp_bind_connect_p0 = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+                       .allowed_access = access_rights,
                        .port = self->srv0.port,
                };
                int ruleset_fd;
@@ -1680,6 +1875,7 @@ TEST_F(ipv4_tcp, with_fs)
 FIXTURE(port_specific)
 {
        struct service_fixture srv0;
+       struct service_fixture cli1;
 };
 
 FIXTURE_VARIANT(port_specific)
@@ -1699,7 +1895,7 @@ FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv4) {
 };
 
 /* clang-format off */
-FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv4) {
+FIXTURE_VARIANT_ADD(port_specific, tcp_sandbox_with_ipv4) {
        /* clang-format on */
        .sandbox = TCP_SANDBOX,
        .prot = {
@@ -1708,6 +1904,16 @@ FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv4) {
        },
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(port_specific, udp_sandbox_with_ipv4) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET,
+               .type = SOCK_DGRAM,
+       },
+};
+
 /* clang-format off */
 FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv6) {
        /* clang-format on */
@@ -1719,7 +1925,7 @@ FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv6) {
 };
 
 /* clang-format off */
-FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv6) {
+FIXTURE_VARIANT_ADD(port_specific, tcp_sandbox_with_ipv6) {
        /* clang-format on */
        .sandbox = TCP_SANDBOX,
        .prot = {
@@ -1728,11 +1934,22 @@ FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv6) {
        },
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(port_specific, udp_sandbox_with_ipv6) {
+       /* clang-format on */
+       .sandbox = UDP_SANDBOX,
+       .prot = {
+               .domain = AF_INET6,
+               .type = SOCK_DGRAM,
+       },
+};
+
 FIXTURE_SETUP(port_specific)
 {
        disable_caps(_metadata);
 
        ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+       ASSERT_EQ(0, set_service(&self->cli1, variant->prot, 1));
 
        setup_loopback(_metadata);
 };
@@ -1747,14 +1964,19 @@ TEST_F(port_specific, bind_connect_zero)
        uint16_t port;
 
        /* Adds a rule layer with bind and connect actions. */
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
+               const __u64 access_rights =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                LANDLOCK_ACCESS_NET_BIND_TCP |
+                                        LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                LANDLOCK_ACCESS_NET_BIND_UDP |
+                                        LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
                const struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                             LANDLOCK_ACCESS_NET_CONNECT_TCP
+                       .handled_access_net = access_rights,
                };
-               const struct landlock_net_port_attr tcp_bind_connect_zero = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr bind_connect_zero = {
+                       .allowed_access = access_rights,
                        .port = 0,
                };
                int ruleset_fd;
@@ -1766,7 +1988,7 @@ TEST_F(port_specific, bind_connect_zero)
                /* Checks zero port value on bind and connect actions. */
                EXPECT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_connect_zero, 0));
+                                           &bind_connect_zero, 0));
 
                enforce_ruleset(_metadata, ruleset_fd);
                EXPECT_EQ(0, close(ruleset_fd));
@@ -1787,11 +2009,16 @@ TEST_F(port_specific, bind_connect_zero)
        ret = bind_variant(bind_fd, &self->srv0);
        EXPECT_EQ(0, ret);
 
-       EXPECT_EQ(0, listen(bind_fd, backlog));
+       if (variant->prot.type == SOCK_STREAM)
+               EXPECT_EQ(0, listen(bind_fd, backlog));
 
        /* Connects on port 0. */
        ret = connect_variant(connect_fd, &self->srv0);
-       EXPECT_EQ(-ECONNREFUSED, ret);
+       if (variant->prot.type == SOCK_STREAM) {
+               EXPECT_EQ(-ECONNREFUSED, ret);
+       } else {
+               EXPECT_EQ(0, ret);
+       }
 
        /* Sets binded port for both protocol families. */
        port = get_binded_port(bind_fd, &variant->prot);
@@ -1815,23 +2042,35 @@ TEST_F(port_specific, bind_connect_1023)
        int bind_fd, connect_fd, ret;
 
        /* Adds a rule layer with bind and connect actions. */
-       if (variant->sandbox == TCP_SANDBOX) {
+       if (variant->sandbox == TCP_SANDBOX ||
+           variant->sandbox == UDP_SANDBOX) {
+               const __u64 bind_right = (variant->sandbox == TCP_SANDBOX ?
+                                                 LANDLOCK_ACCESS_NET_BIND_TCP :
+                                                 LANDLOCK_ACCESS_NET_BIND_UDP);
+               const __u64 access_rights =
+                       (variant->sandbox == TCP_SANDBOX ?
+                                (LANDLOCK_ACCESS_NET_BIND_TCP |
+                                 LANDLOCK_ACCESS_NET_CONNECT_TCP) :
+                                (LANDLOCK_ACCESS_NET_BIND_UDP |
+                                 LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP));
                const struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                             LANDLOCK_ACCESS_NET_CONNECT_TCP
+                       .handled_access_net = access_rights,
                };
                /* A rule with port value less than 1024. */
-               const struct landlock_net_port_attr tcp_bind_connect_low_range = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr bind_connect_low_range = {
+                       .allowed_access = access_rights,
                        .port = 1023,
                };
                /* A rule with 1024 port. */
-               const struct landlock_net_port_attr tcp_bind_connect = {
-                       .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                         LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               const struct landlock_net_port_attr bind_connect = {
+                       .allowed_access = access_rights,
                        .port = 1024,
                };
+               /* A rule with cli1's port, to use as source port. */
+               const struct landlock_net_port_attr srcport = {
+                       .allowed_access = bind_right,
+                       .port = self->cli1.port,
+               };
                int ruleset_fd;
 
                ruleset_fd = landlock_create_ruleset(&ruleset_attr,
@@ -1840,10 +2079,15 @@ TEST_F(port_specific, bind_connect_1023)
 
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_connect_low_range, 0));
+                                           &bind_connect_low_range, 0));
                ASSERT_EQ(0,
                          landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                           &tcp_bind_connect, 0));
+                                           &bind_connect, 0));
+               if (variant->sandbox == UDP_SANDBOX) {
+                       ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+                                                      LANDLOCK_RULE_NET_PORT,
+                                                      &srcport, 0));
+               }
 
                enforce_ruleset(_metadata, ruleset_fd);
                EXPECT_EQ(0, close(ruleset_fd));
@@ -1852,9 +2096,6 @@ TEST_F(port_specific, bind_connect_1023)
        bind_fd = socket_variant(&self->srv0);
        ASSERT_LE(0, bind_fd);
 
-       connect_fd = socket_variant(&self->srv0);
-       ASSERT_LE(0, connect_fd);
-
        /* Sets address port to 1023 for both protocol families. */
        set_port(&self->srv0, 1023);
        /* Binds on port 1023. */
@@ -1867,8 +2108,19 @@ TEST_F(port_specific, bind_connect_1023)
        ret = bind_variant(bind_fd, &self->srv0);
        clear_cap(_metadata, CAP_NET_BIND_SERVICE);
        EXPECT_EQ(0, ret);
-       EXPECT_EQ(0, listen(bind_fd, backlog));
+       if (variant->prot.type == SOCK_STREAM)
+               EXPECT_EQ(0, listen(bind_fd, backlog));
 
+       connect_fd = socket_variant(&self->srv0);
+       ASSERT_LE(0, connect_fd);
+       if (variant->prot.type == SOCK_DGRAM) {
+               /*
+                * We are about to connect(), but bind() is restricted, so for
+                * UDP sockets we need to use cli1's port as source port (the
+                * only one we are allowed to use).
+                */
+               EXPECT_EQ(0, bind_variant(connect_fd, &self->cli1));
+       }
        /* Connects on the binded port 1023. */
        ret = connect_variant(connect_fd, &self->srv0);
        EXPECT_EQ(0, ret);
@@ -1887,7 +2139,10 @@ TEST_F(port_specific, bind_connect_1023)
        /* Binds on port 1024. */
        ret = bind_variant(bind_fd, &self->srv0);
        EXPECT_EQ(0, ret);
-       EXPECT_EQ(0, listen(bind_fd, backlog));
+       if (variant->prot.type == SOCK_STREAM)
+               EXPECT_EQ(0, listen(bind_fd, backlog));
+       if (variant->prot.type == SOCK_DGRAM)
+               EXPECT_EQ(0, bind_variant(connect_fd, &self->cli1));
 
        /* Connects on the binded port 1024. */
        ret = connect_variant(connect_fd, &self->srv0);
@@ -1897,23 +2152,30 @@ TEST_F(port_specific, bind_connect_1023)
        EXPECT_EQ(0, close(bind_fd));
 }
 
-static int matches_log_tcp(const int audit_fd, const char *const blockers,
-                          const char *const dir_addr, const char *const addr,
-                          const char *const dir_port)
+static int matches_auditlog(const int audit_fd, const char *const blockers,
+                           const char *const dir_addr, const char *const addr,
+                           const char *const dir_port)
 {
-       static const char log_template[] = REGEX_LANDLOCK_PREFIX
+       static const char log_with_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
                " blockers=%s %s=%s %s=1024$";
+       static const char log_without_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
+               " blockers=%s";
        /*
         * Max strlen(blockers): 16
         * Max strlen(dir_addr): 5
         * Max strlen(addr): 12
         * Max strlen(dir_port): 4
         */
-       char log_match[sizeof(log_template) + 37];
+       char log_match[sizeof(log_with_addrport_tmpl) + 37];
        int log_match_len;
 
-       log_match_len = snprintf(log_match, sizeof(log_match), log_template,
-                                blockers, dir_addr, addr, dir_port);
+       if (addr == NULL)
+               log_match_len = snprintf(log_match, sizeof(log_match),
+                                        log_without_addrport_tmpl, blockers);
+       else
+               log_match_len = snprintf(log_match, sizeof(log_match),
+                                        log_with_addrport_tmpl, blockers,
+                                        dir_addr, addr, dir_port);
        if (log_match_len > sizeof(log_match))
                return -E2BIG;
 
@@ -1924,6 +2186,7 @@ static int matches_log_tcp(const int audit_fd, const char *const blockers,
 FIXTURE(audit)
 {
        struct service_fixture srv0;
+       struct service_fixture srv1;
        struct audit_filter audit_filter;
        int audit_fd;
 };
@@ -1935,7 +2198,7 @@ FIXTURE_VARIANT(audit)
 };
 
 /* clang-format off */
-FIXTURE_VARIANT_ADD(audit, ipv4) {
+FIXTURE_VARIANT_ADD(audit, ipv4_tcp) {
        /* clang-format on */
        .addr = "127\\.0\\.0\\.1",
        .prot = {
@@ -1945,7 +2208,17 @@ FIXTURE_VARIANT_ADD(audit, ipv4) {
 };
 
 /* clang-format off */
-FIXTURE_VARIANT_ADD(audit, ipv6) {
+FIXTURE_VARIANT_ADD(audit, ipv4_udp) {
+       /* clang-format on */
+       .addr = "127\\.0\\.0\\.1",
+       .prot = {
+               .domain = AF_INET,
+               .type = SOCK_DGRAM,
+       },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv6_tcp) {
        /* clang-format on */
        .addr = "::1",
        .prot = {
@@ -1954,9 +2227,21 @@ FIXTURE_VARIANT_ADD(audit, ipv6) {
        },
 };
 
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv6_udp) {
+       /* clang-format on */
+       .addr = "::1",
+       .prot = {
+               .domain = AF_INET6,
+               .type = SOCK_DGRAM,
+       },
+};
+
 FIXTURE_SETUP(audit)
 {
        ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+       ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
+
        setup_loopback(_metadata);
 
        set_cap(_metadata, CAP_AUDIT_CONTROL);
@@ -1974,9 +2259,17 @@ FIXTURE_TEARDOWN(audit)
 
 TEST_F(audit, bind)
 {
+       const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+                                        "net\\.bind_tcp" :
+                                        "net\\.bind_udp");
+       const __u64 access_rights =
+               (variant->prot.type == SOCK_STREAM ?
+                        LANDLOCK_ACCESS_NET_BIND_TCP |
+                                LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                        LANDLOCK_ACCESS_NET_BIND_UDP |
+                                LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
        const struct landlock_ruleset_attr ruleset_attr = {
-               .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                     LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               .handled_access_net = access_rights,
        };
        struct audit_records records;
        int ruleset_fd, sock_fd;
@@ -1990,8 +2283,8 @@ TEST_F(audit, bind)
        sock_fd = socket_variant(&self->srv0);
        ASSERT_LE(0, sock_fd);
        EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
-       EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.bind_tcp", "saddr",
-                                    variant->addr, "src"));
+       EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "saddr",
+                                     variant->addr, "src"));
 
        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
@@ -2002,9 +2295,22 @@ TEST_F(audit, bind)
 
 TEST_F(audit, connect)
 {
+       const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+                                        "net\\.connect_tcp" :
+                                        "net\\.connect_send_udp");
+       const __u64 bind_right = (variant->prot.type == SOCK_STREAM ?
+                                         LANDLOCK_ACCESS_NET_BIND_TCP :
+                                         LANDLOCK_ACCESS_NET_BIND_UDP);
+       const __u64 conn_right = (variant->prot.type == SOCK_STREAM ?
+                                         LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                         LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+       const __u64 access_rights = bind_right | conn_right;
        const struct landlock_ruleset_attr ruleset_attr = {
-               .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                     LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               .handled_access_net = access_rights,
+       };
+       const struct landlock_net_port_attr rule_connect_p1 = {
+               .allowed_access = conn_right,
+               .port = self->srv1.port,
        };
        struct audit_records records;
        int ruleset_fd, sock_fd;
@@ -2012,33 +2318,47 @@ TEST_F(audit, connect)
        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);
+       ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+                                      &rule_connect_p1, 0));
        enforce_ruleset(_metadata, ruleset_fd);
        EXPECT_EQ(0, close(ruleset_fd));
 
        sock_fd = socket_variant(&self->srv0);
        ASSERT_LE(0, sock_fd);
        EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
-       EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.connect_tcp",
-                                    "daddr", variant->addr, "dest"));
+       EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "daddr",
+                                     variant->addr, "dest"));
 
        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);
        EXPECT_EQ(1, records.domain);
 
+       if (variant->prot.type == SOCK_DGRAM) {
+               /* Check that autobind generates a denied bind event. */
+               EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
+
+               EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp",
+                                             NULL, NULL, NULL));
+               EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+               EXPECT_EQ(0, records.access);
+               EXPECT_EQ(0, records.domain);
+       }
+
        EXPECT_EQ(0, close(sock_fd));
 }
 
-static int matches_log_tcp_bound(int audit_fd, const char *const addr,
-                                __u16 lport, __u16 dport)
+static int matches_log_connect_bound(int audit_fd, const char *const blockers,
+                                    const char *const addr, __u16 lport,
+                                    __u16 dport)
 {
        static const char log_template[] = REGEX_LANDLOCK_PREFIX
-               " blockers=net\\.connect_tcp laddr=%s lport=%u daddr=%s dest=%u$";
-       /* Slack for two addresses and two port numbers. */
-       char log_match[sizeof(log_template) + 40];
+               " blockers=%s laddr=%s lport=%u daddr=%s dest=%u$";
+       /* Slack for the blockers, two addresses and two port numbers. */
+       char log_match[sizeof(log_template) + 60];
        int log_match_len;
 
        log_match_len = snprintf(log_match, sizeof(log_match), log_template,
-                                addr, lport, addr, dport);
+                                blockers, addr, lport, addr, dport);
        if (log_match_len > sizeof(log_match))
                return -E2BIG;
 
@@ -2051,14 +2371,22 @@ static int matches_log_tcp_bound(int audit_fd, const char *const addr,
  * from the bound socket (made available through audit_net.sk) in addition to
  * the connect sockaddr's daddr/dest.
  */
-TEST_F(audit, connect_tcp_bound)
+TEST_F(audit, connect_bound)
 {
+       const __u64 bind_right = (variant->prot.type == SOCK_STREAM ?
+                                         LANDLOCK_ACCESS_NET_BIND_TCP :
+                                         LANDLOCK_ACCESS_NET_BIND_UDP);
+       const __u64 conn_right = (variant->prot.type == SOCK_STREAM ?
+                                         LANDLOCK_ACCESS_NET_CONNECT_TCP :
+                                         LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+       const char *const audit_evt = (variant->prot.type == SOCK_STREAM ?
+                                              "net\\.connect_tcp" :
+                                              "net\\.connect_send_udp");
        const struct landlock_ruleset_attr ruleset_attr = {
-               .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
-                                     LANDLOCK_ACCESS_NET_CONNECT_TCP,
+               .handled_access_net = bind_right | conn_right,
        };
        const struct landlock_net_port_attr rule_bind = {
-               .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+               .allowed_access = bind_right,
                .port = self->srv0.port,
        };
        struct service_fixture srv_remote;
@@ -2080,8 +2408,9 @@ TEST_F(audit, connect_tcp_bound)
        ASSERT_LE(0, sock_fd);
        EXPECT_EQ(0, bind_variant(sock_fd, &self->srv0));
        EXPECT_EQ(-EACCES, connect_variant(sock_fd, &srv_remote));
-       EXPECT_EQ(0, matches_log_tcp_bound(self->audit_fd, variant->addr,
-                                          self->srv0.port, srv_remote.port));
+       EXPECT_EQ(0, matches_log_connect_bound(self->audit_fd, audit_evt,
+                                              variant->addr, self->srv0.port,
+                                              srv_remote.port));
 
        EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
        EXPECT_EQ(0, records.access);