Network denial events now report the port from their one authoritative
validated address through one signed field.  Verify this value directly
without inferring a source or destination role.

Exercise a nonzero IPv4 TCP bind, an explicit-zero IPv4 UDP bind, a
synthetic-zero IPv6 UDP autobind, and a family-only AF_UNSPEC UDP send.
Require exactly one event with the exact policy blocker for each shape.
The value -1 distinguishes a family-only address with no validated port
from the two valid port-zero cases.

Cc: Günther Noack <[email protected]>
Cc: Steven Rostedt <[email protected]>
Signed-off-by: Mickaël Salaün <[email protected]>
---
 tools/testing/selftests/landlock/net_test.c | 259 +++++++++++---------
 1 file changed, 147 insertions(+), 112 deletions(-)

diff --git a/tools/testing/selftests/landlock/net_test.c 
b/tools/testing/selftests/landlock/net_test.c
index 28942438e154..16afbfdf06bb 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind)
 }
 
 /*
- * Anchors the denial fields shared by every deny_access_net event so a field
- * test proves more than the checked endpoint: the denying domain, the 
same-exec
- * bit, the audit-logging verdict, and the blocked access all stay populated.
+ * Anchors the denial fields shared by every deny_access_net event so a port
+ * test also proves the denying domain, execution status, logging verdict, and
+ * exact blocked access.
  */
 static void
 expect_net_deny_common_fields(struct __test_metadata *const _metadata,
-                             const char *const buf)
+                             const char *const buf,
+                             const char *const expected_blockers)
 {
        char field[64];
 
@@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata 
*const _metadata,
        ASSERT_EQ(0,
                  tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
                                        "blockers", field, sizeof(field)));
-       EXPECT_STRNE("", field);
+       EXPECT_STREQ(expected_blockers, field);
 }
 
-/* Connect and field-check tests use a separate fixture without variants. */
+enum trace_net_operation {
+       TRACE_NET_BIND,
+       TRACE_NET_SEND,
+};
 
 /* clang-format off */
-FIXTURE(trace_net_connect) {
+FIXTURE(trace_net_address) {
        /* clang-format on */
        int tracefs_ok;
 };
 
-FIXTURE_SETUP(trace_net_connect)
+FIXTURE_SETUP(trace_net_address)
 {
        int ret;
 
@@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect)
        clear_cap(_metadata, CAP_SYS_ADMIN);
 }
 
-FIXTURE_TEARDOWN(trace_net_connect)
+FIXTURE_TEARDOWN(trace_net_address)
 {
        if (!self->tracefs_ok)
                return;
@@ -3559,146 +3563,177 @@ FIXTURE_TEARDOWN(trace_net_connect)
 }
 
 /* clang-format off */
-FIXTURE_VARIANT(trace_net_connect) {
+FIXTURE_VARIANT(trace_net_address) {
        /* clang-format on */
-       /* handled_access_net, also the access allowed on the base port. */
-       __u64 handled;
-       /* Bind the allowed base port before the denied operation. */
-       bool bind_base_first;
-       /* Denied operation on the next port: connect (true) or bind (false). */
-       bool deny_connect;
+       int socket_family;
+       int socket_type;
+       enum trace_net_operation operation;
+       int address_family;
+       socklen_t addrlen;
+       __u64 handled_access;
+       const char *expected_blockers;
+       bool address_port_zero;
+       bool expected_address_port;
+       int expected_port;
 };
 
-/* Denied connect() to the next IPv4 port. */
 /* clang-format off */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) {
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) {
        /* clang-format on */
-       .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP,
-       .bind_base_first = false,
-       .deny_connect = true,
+       .socket_family = AF_INET,
+       .socket_type = SOCK_STREAM,
+       .operation = TRACE_NET_BIND,
+       .address_family = AF_INET,
+       .addrlen = sizeof(struct sockaddr_in),
+       .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+       .expected_blockers = "bind_tcp",
+       .expected_address_port = true,
 };
 
-/* Denied bind() to the next IPv4 port. */
+/* Explicit bind(0) has a checked zero port. */
 /* clang-format off */
-FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) {
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) {
        /* clang-format on */
-       .handled = LANDLOCK_ACCESS_NET_BIND_TCP,
-       .bind_base_first = false,
-       .deny_connect = false,
+       .socket_family = AF_INET,
+       .socket_type = SOCK_DGRAM,
+       .operation = TRACE_NET_BIND,
+       .address_family = AF_INET,
+       .addrlen = sizeof(struct sockaddr_in),
+       .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+       .expected_blockers = "bind_udp",
+       .address_port_zero = true,
+       .expected_port = 0,
 };
 
-/* Denied connect() after an allowed bind() uses the checked destination. */
+/* A UDP send can deny its synthetic unspecified bind endpoint. */
 /* clang-format off */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) {
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) {
        /* clang-format on */
-       .handled = LANDLOCK_ACCESS_NET_BIND_TCP |
-                  LANDLOCK_ACCESS_NET_CONNECT_TCP,
-       .bind_base_first = true,
-       .deny_connect = true,
+       .socket_family = AF_INET6,
+       .socket_type = SOCK_DGRAM,
+       .operation = TRACE_NET_SEND,
+       .address_family = AF_INET6,
+       .addrlen = sizeof(struct sockaddr_in6),
+       .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+       .expected_blockers = "bind_udp",
+       .expected_port = 0,
 };
 
-/*
- * A denied TCP bind(2) or connect(2) emits one deny_access_net event with the
- * checked IPv4 port in host endianness (the UAPI landlock_net_port_attr.port
- * convention).  A prior allowed bind does not change a connect denial's port.
- */
-TEST_F(trace_net_connect, deny_access_net)
+/* A family-only address has no checked port. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) {
+       /* clang-format on */
+       .socket_family = AF_INET6,
+       .socket_type = SOCK_DGRAM,
+       .operation = TRACE_NET_SEND,
+       .address_family = AF_UNSPEC,
+       .addrlen = sizeof(sa_family_t),
+       .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+       .expected_blockers = "connect_send_udp",
+       .expected_port = -1,
+};
+
+static void set_trace_net_address(struct sockaddr_storage *const storage,
+                                 const int socket_family,
+                                 const int address_family,
+                                 const unsigned short port)
 {
-       pid_t child;
-       int status;
-       char *buf;
+       memset(storage, 0, sizeof(*storage));
+
+       if (socket_family == AF_INET) {
+               struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage;
+
+               addr4->sin_family = address_family;
+               addr4->sin_port = htons(port);
+               addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       } else {
+               struct sockaddr_in6 *const addr6 =
+                       (struct sockaddr_in6 *)storage;
+
+               addr6->sin6_family = address_family;
+               addr6->sin6_port = htons(port);
+               addr6->sin6_addr = in6addr_loopback;
+       }
+}
+
+/* Verifies the actionable signed port for representative checked shapes. */
+TEST_F(trace_net_address, deny_access_net)
+{
+       const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK);
+       const unsigned short address_port =
+               variant->address_port_zero ? 0 : sock_port_start + 1;
+       const int expected_port = variant->expected_address_port ?
+                                         address_port :
+                                         variant->expected_port;
+       const struct landlock_ruleset_attr ruleset_attr = {
+               .handled_access_net = variant->handled_access,
+       };
+       struct sockaddr_storage address;
        char field[64], expected[16];
+       char *buf;
+       int count, ret, ruleset_fd, socket_fd, status;
+       pid_t child;
 
        if (!self->tracefs_ok)
                SKIP(return, "tracefs not available");
 
+       set_trace_net_address(&address, variant->socket_family,
+                             variant->address_family, address_port);
+       socket_fd = socket(variant->socket_family,
+                          variant->socket_type | SOCK_CLOEXEC, 0);
+       ASSERT_LE(0, socket_fd);
+       ruleset_fd =
+               landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+       ASSERT_LE(0, ruleset_fd);
+       ASSERT_EQ(0, tracefs_clear_buf());
+
        child = fork();
        ASSERT_LE(0, child);
-
        if (child == 0) {
-               struct landlock_ruleset_attr ruleset_attr = {
-                       .handled_access_net = variant->handled,
-               };
-               struct landlock_net_port_attr port_attr = {
-                       .allowed_access = variant->handled,
-                       .port = sock_port_start,
-               };
-               struct sockaddr_in addr = {
-                       .sin_family = AF_INET,
-                       .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
-               };
-               int ruleset_fd, sock_fd, optval = 1, ret;
-
-               ruleset_fd = landlock_create_ruleset(&ruleset_attr,
-                                                    sizeof(ruleset_attr), 0);
-               if (ruleset_fd < 0)
+               if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
                        _exit(1);
-               if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
-                                     &port_attr, 0)) {
-                       close(ruleset_fd);
-                       _exit(1);
-               }
-               prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
-               if (landlock_restrict_self(ruleset_fd, 0)) {
-                       close(ruleset_fd);
-                       _exit(1);
-               }
+               if (landlock_restrict_self(ruleset_fd, 0))
+                       _exit(2);
                close(ruleset_fd);
 
-               sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
-               if (sock_fd < 0)
-                       _exit(1);
-
-               /* Bind the allowed base port first (succeeds, no event). */
-               if (variant->bind_base_first) {
-                       setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
-                                  sizeof(optval));
-                       addr.sin_port = htons(sock_port_start);
-                       if (bind(sock_fd, (struct sockaddr *)&addr,
-                                sizeof(addr))) {
-                               close(sock_fd);
-                               _exit(1);
-                       }
-               }
-
-               /* Denied operation on the next port. */
-               addr.sin_port = htons(sock_port_start + 1);
-               if (variant->deny_connect)
-                       ret = connect(sock_fd, (struct sockaddr *)&addr,
-                                     sizeof(addr));
-               else
-                       ret = bind(sock_fd, (struct sockaddr *)&addr,
-                                  sizeof(addr));
-               if (ret == 0) {
-                       close(sock_fd);
-                       _exit(2);
-               }
-               if (errno != EACCES) {
-                       close(sock_fd);
+               switch (variant->operation) {
+               case TRACE_NET_BIND:
+                       ret = bind(socket_fd, (const struct sockaddr *)&address,
+                                  variant->addrlen);
+                       break;
+               case TRACE_NET_SEND:
+                       ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL,
+                                    (const struct sockaddr *)&address,
+                                    variant->addrlen);
+                       break;
+               default:
                        _exit(3);
                }
-               close(sock_fd);
+               if (ret >= 0 || errno != EACCES)
+                       _exit(4);
+               close(socket_fd);
+
                _exit(0);
        }
+       close(ruleset_fd);
+       close(socket_fd);
 
        ASSERT_EQ(child, waitpid(child, &status, 0));
        ASSERT_TRUE(WIFEXITED(status));
-       EXPECT_EQ(0, WEXITSTATUS(status));
+       ASSERT_EQ(0, WEXITSTATUS(status));
 
        buf = tracefs_read_buf();
        ASSERT_NE(NULL, buf);
-
-       EXPECT_EQ(1, tracefs_count_matches(buf,
-                                          REGEX_DENY_ACCESS_NET(TRACE_TASK)));
-
-       expect_net_deny_common_fields(_metadata, buf);
-
-       snprintf(expected, sizeof(expected), "%llu",
-                (unsigned long long)(sock_port_start + 1));
-
-       ASSERT_EQ(0,
-                 tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
-                                       "port", field, sizeof(field)));
+       count = tracefs_count_matches(buf, event_regex);
+       if (count != 1)
+               TH_LOG("Expected 1 denial event, got %d\n%s", count, buf);
+       ASSERT_EQ(1, count);
+       expect_net_deny_common_fields(_metadata, buf,
+                                     variant->expected_blockers);
+
+       ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field,
+                                          sizeof(field)));
+       snprintf(expected, sizeof(expected), "%d", expected_port);
        EXPECT_STREQ(expected, field);
 
        free(buf);
-- 
2.55.0


Reply via email to