[RFC PATCH v1 7/7] selftests/landlock: Add UDP sendmsg/recvmsg tests
Matthieu Buffet
matthieu at buffet.re
Mon Sep 16 12:22:30 UTC 2024
Add tests specific to send/recv, orthogonal to whether the process
is allowed to bind/connect.
Signed-off-by: Matthieu Buffet <matthieu at buffet.re>
---
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
More information about the Linux-security-module-archive
mailing list