[RFC PATCH v1 7/7] selftests/landlock: Add UDP sendmsg/recvmsg tests
From: Matthieu Buffet <hidden>
Date: 2024-09-16 12:24:48
Also in:
linux-security-module, lkml
Subsystem:
kernel selftest framework, landlock security module, the rest · Maintainers:
Shuah Khan, Mickaël Salaün, Linus Torvalds
Add tests specific to send/recv, orthogonal to whether the process is allowed to bind/connect. Signed-off-by: Matthieu Buffet <redacted> --- tools/testing/selftests/landlock/net_test.c | 373 ++++++++++++++++++++ 1 file changed, 373 insertions(+)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 883e6648e79a..a02307ba069c 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c@@ -285,6 +285,29 @@ static int connect_variant(const int sock_fd, return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); } +static int get_msg_addr_variant(struct msghdr *msg, + const struct service_fixture *const srv) +{ + switch (srv->protocol.domain) { + case AF_UNSPEC: + case AF_INET: + msg->msg_name = (void *)&srv->ipv4_addr; + break; + case AF_INET6: + msg->msg_name = (void *)&srv->ipv6_addr; + break; + case AF_UNIX: + msg->msg_name = (void *)&srv->unix_addr; + break; + default: + errno = -EAFNOSUPPORT; + return -errno; + } + + msg->msg_namelen = get_addrlen(srv, false); + return 0; +} + FIXTURE(protocol) { struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
@@ -927,6 +950,356 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +FIXTURE(udp_send_recv) +{ + struct service_fixture srv_allowed; + struct service_fixture srv_denied; + struct service_fixture srv_ephemeral; + struct service_fixture addr_unspec0, addr_unspec1; + int srv_allowed_fd, srv_denied_fd, srv_ephemeral_fd, client_fd; + char read_buf[1]; + struct iovec testmsg_contents; + struct msghdr testmsg; +}; + +FIXTURE_VARIANT(udp_send_recv) +{ + const struct protocol_variant prot; +}; + +FIXTURE_SETUP(udp_send_recv) +{ + const struct timeval read_timeout = { + .tv_sec = 0, + .tv_usec = 100000, + }; + const struct protocol_variant prot_unspec = { + .domain = AF_UNSPEC, + .type = SOCK_STREAM, + }; + + disable_caps(_metadata); + + ASSERT_EQ(0, set_service(&self->addr_unspec0, prot_unspec, 0)); + ASSERT_EQ(0, set_service(&self->addr_unspec1, prot_unspec, 1)); + + /* Prepare one server socket to be denied */ + ASSERT_EQ(0, set_service(&self->srv_denied, variant->prot, 0)); + self->srv_denied_fd = socket_variant(&self->srv_denied); + ASSERT_LE(0, self->srv_denied_fd); + ASSERT_EQ(0, bind_variant(self->srv_denied_fd, &self->srv_denied)); + + /* Prepare one server socket on specific port to be allowed */ + ASSERT_EQ(0, set_service(&self->srv_allowed, variant->prot, 1)); + self->srv_allowed_fd = socket_variant(&self->srv_allowed); + ASSERT_LE(0, self->srv_allowed_fd); + ASSERT_EQ(0, bind_variant(self->srv_allowed_fd, &self->srv_allowed)); + + /* Prepare one server socket on ephemeral port to be allowed */ + ASSERT_EQ(0, set_service(&self->srv_ephemeral, variant->prot, 0)); + if (variant->prot.domain == AF_INET) + self->srv_ephemeral.ipv4_addr.sin_port = 0; + else if (variant->prot.domain == AF_INET6) + self->srv_ephemeral.ipv6_addr.sin6_port = 0; + self->srv_ephemeral_fd = socket_variant(&self->srv_ephemeral); + ASSERT_LE(0, self->srv_ephemeral_fd); + ASSERT_EQ(0, bind_variant(self->srv_ephemeral_fd, + &self->srv_ephemeral)); + self->srv_ephemeral.port = get_binded_port(self->srv_ephemeral_fd, + &variant->prot); + ASSERT_NE(0, self->srv_ephemeral.port); + if (variant->prot.domain == AF_INET) + self->srv_ephemeral.ipv4_addr.sin_port = htons(self->srv_ephemeral.port); + else if (variant->prot.domain == AF_INET6) + self->srv_ephemeral.ipv6_addr.sin6_port = htons(self->srv_ephemeral.port); + + /* We must absolutely avoid blocking other tests indefinitely */ + EXPECT_EQ(0, setsockopt(self->srv_allowed_fd, SOL_SOCKET, + SO_RCVTIMEO, + &read_timeout, sizeof(read_timeout))); + EXPECT_EQ(0, setsockopt(self->srv_denied_fd, SOL_SOCKET, + SO_RCVTIMEO, + &read_timeout, sizeof(read_timeout))); + EXPECT_EQ(0, setsockopt(self->srv_ephemeral_fd, SOL_SOCKET, + SO_RCVTIMEO, + &read_timeout, sizeof(read_timeout))); + + self->client_fd = socket_variant(&self->srv_denied); + ASSERT_LE(0, self->client_fd); + + self->read_buf[0] = 0; + + self->testmsg_contents.iov_len = 1; + memset(&self->testmsg, 0, sizeof(self->testmsg)); + self->testmsg.msg_iov = &self->testmsg_contents; + self->testmsg.msg_iovlen = 1; +} + +FIXTURE_TEARDOWN(udp_send_recv) +{ + EXPECT_EQ(0, close(self->srv_allowed_fd)); + EXPECT_EQ(0, close(self->srv_denied_fd)); + EXPECT_EQ(0, close(self->client_fd)); +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(udp_send_recv, ipv4) { + .prot = { + .domain = AF_INET, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(udp_send_recv, ipv6) { + .prot = { + .domain = AF_INET6, + .type = SOCK_DGRAM, + }, +}; + +TEST_F(udp_send_recv, sendmsg) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_SENDMSG_UDP, + }; + const struct landlock_net_port_attr allow_one_server = { + .allowed_access = LANDLOCK_ACCESS_NET_SENDMSG_UDP, + .port = self->srv_allowed.port, + }; + const int 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, + &allow_one_server, 0)); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Send without bind nor explicit address */ + EXPECT_EQ(-1, write(self->client_fd, "A", 1)); + EXPECT_EQ(EDESTADDRREQ, errno); + + /* Send with an explicit denied address */ + self->testmsg_contents.iov_base = "B"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_denied)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(EAGAIN, errno); + + /* Send with an explicit allowed address */ + self->testmsg_contents.iov_base = "C"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed)); + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)) + { + TH_LOG("sendmsg failed: %s", strerror(errno)); + } + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(self->read_buf[0], 'C'); + + /* + * Sending to (AF_UNSPEC, port) should be equivalent to not specifying + * a destination in IPv6, and equivalent to (AF_INET, port) in IPv4. + */ + if (variant->prot.domain == AF_INET) { + self->testmsg_contents.iov_base = "D"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EACCES, errno); + + self->testmsg_contents.iov_base = "E"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec1)); + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, + sizeof(self->read_buf))); + EXPECT_EQ(self->read_buf[0], 'E'); + } else if (variant->prot.domain == AF_INET6) { + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EDESTADDRREQ, errno); + } + + /* Send without an address, after connect()ing to an allowed address */ + self->testmsg.msg_name = NULL; + self->testmsg.msg_namelen = 0; + self->testmsg_contents.iov_base = "F"; + ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_allowed)); + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)) + { + TH_LOG("sendmsg failed: %s", strerror(errno)); + } + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, + sizeof(self->read_buf))); + EXPECT_EQ(self->read_buf[0], 'F'); + + /* + * Sending to AF_UNSPEC should be equivalent to not specifying an + * address (in IPv6 only) and falling back to the allowed address. + */ + if (variant->prot.domain == AF_INET6) { + self->testmsg_contents.iov_base = "G"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0)); + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)) { + TH_LOG("sendmsg failed: %s", strerror(errno)); + } + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, + sizeof(self->read_buf))); + EXPECT_EQ(self->read_buf[0], 'G'); + } + + /* Send without an address, after connect()ing to a denied address */ + self->testmsg_contents.iov_base = "H"; + ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_denied)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(EAGAIN, errno); + + /* + * Sending to AF_UNSPEC should be equivalent to not specifying an + * address (in IPv6 only) and falling back to the denied address. + */ + if (variant->prot.domain == AF_INET6) { + self->testmsg_contents.iov_base = "I"; + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EACCES, errno); + } + + /* Send with an explicit allowed address, should overrule connect() */ + self->testmsg_contents.iov_base = "J"; + ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_denied)); + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed)); + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)) + { + TH_LOG("sendmsg failed: %s", strerror(errno)); + } + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(self->read_buf[0], 'J'); + + /* Send with an explicit denied address, should overrule connect() */ + self->testmsg_contents.iov_base = "K"; + ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_allowed)); + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_denied)); + EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(-1, read(self->srv_allowed_fd, + self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(EAGAIN, errno); + EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(EAGAIN, errno); +} + +TEST_F(udp_send_recv, recvmsg_on_fixed_port) +{ + struct sockaddr_storage from = {0}; + socklen_t from_len = sizeof(from); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_RECVMSG_UDP, + }; + const struct landlock_net_port_attr allow_one_server = { + .allowed_access = LANDLOCK_ACCESS_NET_RECVMSG_UDP, + .port = self->srv_allowed.port, + }; + const int 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, + &allow_one_server, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Receive on an allowed port with read() */ + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed)); + self->testmsg_contents.iov_base = "A"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, + sizeof(self->read_buf))) { + TH_LOG("read failed: %s", strerror(errno)); + } + EXPECT_EQ(self->read_buf[0], 'A'); + + /* Receive on an allowed port with recv() */ + self->testmsg_contents.iov_base = "B"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(1, recv(self->srv_allowed_fd, + self->read_buf, sizeof(self->read_buf), 0)) { + TH_LOG("recv failed: %s", strerror(errno)); + } + EXPECT_EQ(self->read_buf[0], 'B'); + + /* Receive on an allowed port with recvfrom() */ + self->testmsg_contents.iov_base = "C"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(1, recvfrom(self->srv_allowed_fd, + self->read_buf, sizeof(self->read_buf), 0, + (struct sockaddr *)&from, &from_len)) { + TH_LOG("recv failed: %s", strerror(errno)); + } + EXPECT_EQ(self->read_buf[0], 'C'); + + /* Receive on a denied port with read() */ + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed)); + self->testmsg_contents.iov_base = "D"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf))); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(self->read_buf[0], 'C'); + + /* Receive on an denied port with recv() */ + self->testmsg_contents.iov_base = "B"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(-1, recv(self->srv_denied_fd, self->read_buf, + sizeof(self->read_buf), 0)); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(self->read_buf[0], 'C'); + + /* Receive on an denied port with recvfrom() */ + self->testmsg_contents.iov_base = "C"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(-1, recvfrom(self->srv_denied_fd, self->read_buf, + sizeof(self->read_buf), 0, + (struct sockaddr *)&from, &from_len)); + EXPECT_EQ(EACCES, errno); + EXPECT_EQ(self->read_buf[0], 'C'); +} + +TEST_F(udp_send_recv, recvmsg_on_ephemeral_port) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_RECVMSG_UDP, + }; + const struct landlock_net_port_attr allow_one_server = { + .allowed_access = LANDLOCK_ACCESS_NET_RECVMSG_UDP, + .port = 0, + }; + const int 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, + &allow_one_server, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, + &self->srv_ephemeral)); + + self->testmsg_contents.iov_base = "A"; + EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)); + EXPECT_EQ(1, read(self->srv_ephemeral_fd, self->read_buf, + sizeof(self->read_buf))) { + TH_LOG("read failed: %s", strerror(errno)); + } + EXPECT_EQ(self->read_buf[0], 'A'); +} + FIXTURE(ipv4) { struct service_fixture srv0, srv1;
--
2.39.5