aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
authorMark Brown <broonie@kernel.org>2026-05-30 00:25:53 +0100
committerMark Brown <broonie@kernel.org>2026-05-30 00:25:53 +0100
commitcd9f27bc50e764e91f3014a851d97872a7fd6544 (patch)
treeb04afc6e4aa509a08c8918cac9017ce383dd6b95 /tools
parentfaa2be6c7cb758bb167e23978bbf3f93a1740b8c (diff)
parentfe7832557561ed6312563368854d5f8df1fa55e3 (diff)
downloadlinux-next-history-cd9f27bc50e764e91f3014a851d97872a7fd6544.tar.gz
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
Diffstat (limited to 'tools')
-rw-r--r--tools/testing/selftests/landlock/audit.h119
-rw-r--r--tools/testing/selftests/landlock/audit_test.c189
-rw-r--r--tools/testing/selftests/landlock/base_test.c4
-rw-r--r--tools/testing/selftests/landlock/net_test.c1146
-rw-r--r--tools/testing/selftests/landlock/ptrace_test.c1
-rw-r--r--tools/testing/selftests/landlock/scoped_abstract_unix_test.c1
6 files changed, 1338 insertions, 122 deletions
diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
index 834005b2b0f09..936fe20f020e6 100644
--- a/tools/testing/selftests/landlock/audit.h
+++ b/tools/testing/selftests/landlock/audit.h
@@ -45,17 +45,25 @@ struct audit_message {
};
};
-static const struct timeval audit_tv_dom_drop = {
+static const struct timeval audit_tv_default = {
/*
- * Because domain deallocation is tied to asynchronous credential
- * freeing, receiving such event may take some time. In practice,
- * on a small VM, it should not exceed 100k usec, but let's wait up
- * to 1 second to be safe.
+ * Default socket timeout for audit_match_record() callers that expect a
+ * record to arrive. Asynchronous kauditd delivery can exceed 1 usec
+ * under heavy debug configs (KASAN, lockdep), where kauditd_thread
+ * scheduling between audit_log_end() and netlink_unicast() takes longer
+ * than the previous 1 usec timeout. 1 second is a generous ceiling: on
+ * the happy path, kauditd delivers within dozens of usec.
*/
.tv_sec = 1,
};
-static const struct timeval audit_tv_default = {
+static const struct timeval audit_tv_fast = {
+ /*
+ * Fast timeout for paths that expect no record (audit_init() drain,
+ * audit_count_records(), probes). Causes audit_recv() to return
+ * -EAGAIN once the socket buffer is empty, naturally terminating the
+ * read loop.
+ */
.tv_usec = 1,
};
@@ -334,8 +342,13 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid,
* Matches a domain deallocation record. When expected_domain_id is non-zero,
* the pattern includes the specific domain ID so that stale deallocation
* records from a previous test (with a different domain ID) are skipped by
- * audit_match_record(), and the socket timeout is temporarily increased to
- * audit_tv_dom_drop to wait for the asynchronous kworker deallocation.
+ * audit_match_record(), waiting for the asynchronous kworker deallocation with
+ * the default patient timeout.
+ *
+ * When expected_domain_id is zero, the caller is probing for any dealloc record
+ * that may or may not arrive. Temporarily lowers the socket timeout to
+ * audit_tv_fast for this probe so it returns promptly when no record is
+ * pending; restores audit_tv_default after.
*/
static int __maybe_unused
matches_log_domain_deallocated(int audit_fd, unsigned int num_denials,
@@ -361,16 +374,21 @@ matches_log_domain_deallocated(int audit_fd, unsigned int num_denials,
if (log_match_len >= sizeof(log_match))
return -E2BIG;
- if (expected_domain_id)
- setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
- &audit_tv_dom_drop, sizeof(audit_tv_dom_drop));
+ if (!expected_domain_id) {
+ if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
+ &audit_tv_fast, sizeof(audit_tv_fast)))
+ return -errno;
+ }
err = audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
domain_id);
- if (expected_domain_id)
- setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
- sizeof(audit_tv_default));
+ if (!expected_domain_id) {
+ if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO,
+ &audit_tv_default, sizeof(audit_tv_default)) &&
+ !err)
+ err = -errno;
+ }
return err;
}
@@ -381,30 +399,46 @@ struct audit_records {
};
/*
- * WARNING: Do not assert records.domain == 0 without a preceding
- * audit_match_record() call. Domain deallocation records are emitted
- * asynchronously from kworker threads and can arrive after the drain in
- * audit_init(), corrupting the domain count. A preceding audit_match_record()
- * call consumes stale records while scanning, making the assertion safe in
- * practice because stale deallocation records arrive before the expected access
- * records.
+ * Counts remaining audit records by type, skipping domain deallocation records.
+ * Deallocation records are emitted asynchronously from kworker threads after a
+ * previous test's child has exited, so they can arrive after the drain in
+ * audit_init() and after the preceding audit_match_record() call. Allocation
+ * records are emitted synchronously during landlock_log_denial() in the current
+ * test's syscall context, so only those are counted in records->domain.
+ *
+ * Temporarily lowers SO_RCVTIMEO to audit_tv_fast for the read loop: this is a
+ * "no record expected" path that should terminate on the first -EAGAIN. The
+ * default patient timeout is restored on exit for subsequent
+ * audit_match_record() callers.
*/
static int audit_count_records(int audit_fd, struct audit_records *records)
{
+ static const char dealloc_pattern[] = REGEX_LANDLOCK_PREFIX
+ " status=deallocated ";
struct audit_message msg;
- int err;
+ regex_t dealloc_re;
+ int ret, err = 0;
+
+ ret = regcomp(&dealloc_re, dealloc_pattern, 0);
+ if (ret)
+ return -ENOMEM;
records->access = 0;
records->domain = 0;
+ if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_fast,
+ sizeof(audit_tv_fast))) {
+ err = -errno;
+ goto out;
+ }
+
do {
memset(&msg, 0, sizeof(msg));
err = audit_recv(audit_fd, &msg);
if (err) {
if (err == -EAGAIN)
- return 0;
- else
- return err;
+ err = 0;
+ break;
}
switch (msg.header.nlmsg_type) {
@@ -412,12 +446,24 @@ static int audit_count_records(int audit_fd, struct audit_records *records)
records->access++;
break;
case AUDIT_LANDLOCK_DOMAIN:
- records->domain++;
+ ret = regexec(&dealloc_re, msg.data, 0, NULL, 0);
+ if (ret == REG_NOMATCH) {
+ records->domain++;
+ } else if (ret != 0) {
+ err = -EIO;
+ goto out;
+ }
break;
}
} while (true);
- return 0;
+out:
+ if (setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
+ sizeof(audit_tv_default)) &&
+ !err)
+ err = -errno;
+ regfree(&dealloc_re);
+ return err;
}
static int audit_init(void)
@@ -436,9 +482,9 @@ static int audit_init(void)
if (err)
goto err_close;
- /* Sets a timeout for negative tests. */
- err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
- sizeof(audit_tv_default));
+ /* Uses the fast timeout to drain stale records below. */
+ err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_fast,
+ sizeof(audit_tv_fast));
if (err) {
err = -errno;
goto err_close;
@@ -454,6 +500,19 @@ static int audit_init(void)
while (audit_recv(fd, NULL) == 0)
;
+ /*
+ * Restores the default timeout for audit_match_record() callers that
+ * expect a record to arrive. Paths that expect no record restore the
+ * fast timeout locally (audit_count_records(), the expected_domain_id
+ * == 0 probe in matches_log_domain_deallocated()).
+ */
+ err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
+ sizeof(audit_tv_default));
+ if (err) {
+ err = -errno;
+ goto err_close;
+ }
+
return fd;
err_close:
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 93ae5bd0dcce0..132c18006d078 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -6,14 +6,17 @@
*/
#define _GNU_SOURCE
+#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/landlock.h>
+#include <netinet/in.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/prctl.h>
+#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -160,6 +163,190 @@ TEST_F(audit, layers)
EXPECT_EQ(0, close(ruleset_fd));
}
+static int matches_log_net_bind(struct __test_metadata *const _metadata,
+ int audit_fd, __u16 port, __u64 *domain_id)
+{
+ /*
+ * The socket is unbound at bind() time, so laddr/lport/faddr/fport from
+ * the socket object are zero and not printed. Only the sockaddr fields
+ * (src) appear.
+ */
+ static const char log_template[] = REGEX_LANDLOCK_PREFIX
+ " blockers=net\\.bind_tcp src=%u$";
+ char log_match[sizeof(log_template) + 10];
+
+ snprintf(log_match, sizeof(log_match), log_template, port);
+ return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+ domain_id);
+}
+
+/*
+ * Verifies that network denial audit records include enriched socket
+ * information (laddr/lport/faddr/fport) from the socket object.
+ */
+TEST_F(audit, net_bind)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+ };
+ struct landlock_net_port_attr net_port = {
+ .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+ .port = 1024,
+ };
+ int status, ruleset_fd;
+ pid_t child;
+ __u64 denial_dom = 1;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Allow port 1024 only. */
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &net_port, 0));
+
+ EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(1025),
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ };
+ int sock_fd;
+
+ EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+ close(ruleset_fd);
+
+ /* Bind to port 1025 (not allowed). */
+ sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-1, bind(sock_fd, (struct sockaddr *)&addr,
+ sizeof(addr)));
+ EXPECT_EQ(EACCES, errno);
+ close(sock_fd);
+
+ /* Verify audit record with enriched socket info. */
+ EXPECT_EQ(0, matches_log_net_bind(_metadata, self->audit_fd,
+ 1025, &denial_dom));
+ EXPECT_NE(denial_dom, 1);
+ EXPECT_NE(denial_dom, 0);
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
+static int matches_log_net_connect(struct __test_metadata *const _metadata,
+ int audit_fd, __u16 denied_port,
+ __u16 bound_port, __u64 *domain_id)
+{
+ /*
+ * After bind(), the socket has local address state. The audit record
+ * should include laddr/lport from the socket (via audit_net.sk) and
+ * daddr/dest from the connect sockaddr.
+ */
+ static const char log_template[] = REGEX_LANDLOCK_PREFIX
+ " blockers=net\\.connect_tcp"
+ " laddr=127\\.0\\.0\\.1 lport=%u"
+ " daddr=127\\.0\\.0\\.1 dest=%u$";
+ char log_match[sizeof(log_template) + 20];
+
+ snprintf(log_match, sizeof(log_match), log_template, bound_port,
+ denied_port);
+ return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
+ domain_id);
+}
+
+/*
+ * Verifies that network denial audit records for connect include enriched
+ * socket information (laddr/lport) from the socket object after a prior bind.
+ * This complements net_bind which tests the unbound case.
+ */
+TEST_F(audit, net_connect)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ };
+ struct landlock_net_port_attr net_port;
+ int status, ruleset_fd;
+ pid_t child;
+ __u64 denial_dom = 1;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Allow bind to port 1024 and connect to port 1024. */
+ net_port.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP;
+ net_port.port = 1024;
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &net_port, 0));
+
+ EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ struct sockaddr_in bind_addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(1024),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ struct sockaddr_in conn_addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(1025),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ int sock_fd, optval = 1;
+
+ EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+ close(ruleset_fd);
+
+ sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_LE(0, sock_fd);
+ ASSERT_EQ(0, setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
+ &optval, sizeof(optval)));
+
+ /* Bind to allowed port 1024 (succeeds). */
+ ASSERT_EQ(0, bind(sock_fd, (struct sockaddr *)&bind_addr,
+ sizeof(bind_addr)));
+
+ /* Connect to denied port 1025 (fails). */
+ EXPECT_EQ(-1, connect(sock_fd, (struct sockaddr *)&conn_addr,
+ sizeof(conn_addr)));
+ EXPECT_EQ(EACCES, errno);
+ close(sock_fd);
+
+ /* Verify audit record with laddr/lport from bound socket. */
+ EXPECT_EQ(0, matches_log_net_connect(_metadata, self->audit_fd,
+ 1025, 1024, &denial_dom));
+ EXPECT_NE(denial_dom, 1);
+ EXPECT_NE(denial_dom, 0);
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ _metadata->exit_code = KSFT_FAIL;
+
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
struct thread_data {
pid_t parent_pid;
int ruleset_fd, pipe_child, pipe_parent;
@@ -730,6 +917,7 @@ TEST_F(audit_flags, signal)
} else {
EXPECT_EQ(1, records.access);
}
+ EXPECT_EQ(0, records.domain);
/* Updates filter rules to match the drop record. */
set_cap(_metadata, CAP_AUDIT_CONTROL);
@@ -917,6 +1105,7 @@ TEST_F(audit_exec, signal_and_open)
/* Tests that there was no denial until now. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
/*
* Wait for the child to do a first denied action by layer1 and
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 30d37234086c4..6c8113c2ded12 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,8 +76,8 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
- LANDLOCK_CREATE_RULESET_VERSION));
+ ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
+ LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
LANDLOCK_CREATE_RULESET_VERSION));
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 4c528154ea92b..2c72fda3c6064 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -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
@@ -258,9 +289,163 @@ static int connect_variant(const int sock_fd,
return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
}
+static int sendto_variant_addrlen(const int sock_fd,
+ const struct service_fixture *const srv,
+ const socklen_t addrlen, void *buf,
+ size_t len, size_t flags)
+{
+ const struct sockaddr *dst = NULL;
+ ssize_t ret;
+
+ /*
+ * We never want our processes to be killed by SIGPIPE: we check
+ * return codes and errno, so that we have actual error messages.
+ */
+ flags |= MSG_NOSIGNAL;
+
+ if (srv != NULL) {
+ switch (srv->protocol.domain) {
+ case AF_UNSPEC:
+ case AF_INET:
+ dst = (const struct sockaddr *)&srv->ipv4_addr;
+ break;
+
+ case AF_INET6:
+ dst = (const struct sockaddr *)&srv->ipv6_addr;
+ break;
+
+ case AF_UNIX:
+ dst = (const struct sockaddr *)&srv->unix_addr;
+ break;
+
+ default:
+ errno = EAFNOSUPPORT;
+ return -errno;
+ }
+ }
+
+ ret = sendto(sock_fd, buf, len, flags, dst, addrlen);
+ if (ret < 0)
+ return -errno;
+
+ /* errno is not set in cases of partial writes. */
+ if (ret != len)
+ return -EINTR;
+
+ return 0;
+}
+
+static int sendto_variant(const int sock_fd,
+ const struct service_fixture *const srv, void *buf,
+ size_t len, size_t flags)
+{
+ socklen_t addrlen = 0;
+
+ if (srv != NULL)
+ addrlen = get_addrlen(srv, false);
+
+ return sendto_variant_addrlen(sock_fd, srv, addrlen, buf, len, flags);
+}
+
+static int test_sendmsg(struct __test_metadata *const _metadata,
+ const struct protocol_variant *prot, int client_fd,
+ int server_fd, const struct service_fixture *srv,
+ bool bind_denied, bool send_denied)
+{
+ int ret;
+ socklen_t opt_len;
+ int sock_type;
+ int addr_family;
+ struct sockaddr_storage peer_addr = { 0 };
+ bool has_remote_port;
+ bool needs_autobind;
+ char read_buf[1] = { 0 };
+
+ /*
+ * Prepare the test by inspecting the socket type and whether it
+ * has a local/remote address set (all of which determine the
+ * expected outcomes).
+ */
+ opt_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(client_fd, SOL_SOCKET, SO_TYPE, &sock_type,
+ &opt_len));
+ opt_len = sizeof(addr_family);
+ ASSERT_EQ(0, getsockopt(client_fd, SOL_SOCKET, SO_DOMAIN, &addr_family,
+ &opt_len));
+ opt_len = sizeof(peer_addr);
+ has_remote_port = (getpeername(client_fd, (struct sockaddr *)&peer_addr,
+ &opt_len) == 0);
+ needs_autobind = (addr_family == AF_INET || addr_family == AF_INET6) &&
+ get_binded_port(client_fd, prot) == 0;
+
+ /* First, check error code with truncated explicit address. */
+ if (srv != NULL) {
+ ret = sendto_variant_addrlen(
+ client_fd, srv, get_addrlen(srv, true) - 1, "A", 1, 0);
+ if (sock_type == SOCK_STREAM && !has_remote_port) {
+ EXPECT_EQ(-EPIPE, ret)
+ {
+ return -1;
+ }
+ } else if (bind_denied && needs_autobind) {
+ EXPECT_EQ(-EACCES, ret)
+ {
+ return -1;
+ }
+ } else {
+ EXPECT_EQ(-EINVAL, ret)
+ {
+ return -1;
+ }
+ }
+ }
+
+ /* With or without explicit destination address (srv can be NULL). */
+ ret = sendto_variant(client_fd, srv, "B", 1, 0);
+ if (sock_type == SOCK_STREAM && !has_remote_port) {
+ EXPECT_EQ(-EPIPE, ret)
+ {
+ return -1;
+ }
+ } else if ((send_denied && srv != NULL) ||
+ (bind_denied && needs_autobind)) {
+ ASSERT_EQ(-EACCES, ret)
+ {
+ return -1;
+ }
+ } else if (srv == NULL && !has_remote_port) {
+ if (addr_family == AF_UNIX) {
+ ASSERT_EQ(-ENOTCONN, ret)
+ {
+ return -1;
+ }
+ } else if (sock_type == SOCK_STREAM) {
+ ASSERT_EQ(-EPIPE, ret)
+ {
+ return -1;
+ }
+ } else {
+ ASSERT_EQ(-EDESTADDRREQ, ret)
+ {
+ return -1;
+ }
+ }
+ } else {
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(1, recv(server_fd, read_buf, 1, 0));
+ ASSERT_EQ(read_buf[0], 'B')
+ {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
FIXTURE(protocol)
{
- struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
+ struct service_fixture srv0, srv1, srv2;
+ struct service_fixture unspec_any0, unspec_srv0, unspec_srv1;
};
FIXTURE_VARIANT(protocol)
@@ -271,10 +456,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);
@@ -283,6 +467,7 @@ FIXTURE_SETUP(protocol)
ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2));
ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
+ ASSERT_EQ(0, set_service(&self->unspec_srv1, prot_unspec, 1));
ASSERT_EQ(0, set_service(&self->unspec_any0, prot_unspec, 0));
self->unspec_any0.ipv4_addr.sin_addr.s_addr = htonl(INADDR_ANY);
@@ -510,6 +695,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 +873,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 +912,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 +942,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 +983,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 +1013,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 +1050,20 @@ TEST_F(protocol, connect)
TEST_F(protocol, bind_unspec)
{
+ const int 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 +1071,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 +1099,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 +1146,15 @@ TEST_F(protocol, bind_unspec)
TEST_F(protocol, connect_unspec)
{
+ const __u64 connect_right =
+ (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_CONNECT_TCP,
+ .handled_access_net = connect_right,
};
- const struct landlock_net_port_attr tcp_connect = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr rule_connect = {
+ .allowed_access = connect_right,
.port = self->srv0.port,
};
int bind_fd, client_fd, status;
@@ -865,7 +1187,8 @@ 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);
ASSERT_LE(0, ruleset_fd);
@@ -873,7 +1196,7 @@ TEST_F(protocol, connect_unspec)
/* 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,7 +1219,8 @@ 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);
ASSERT_LE(0, ruleset_fd);
@@ -950,6 +1274,441 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(protocol, sendmsg_stream)
+{
+ int srv0_fd, tmp_fd, client_fd, res;
+ char read_buf[1] = { 0 };
+
+ /*
+ * Simple test for stream sockets: just deny all connect()/
+ * send(explicit addr)/bind(), and make sure we don't interfere
+ * with any operation.
+ */
+ if (variant->prot.type != SOCK_STREAM)
+ return;
+
+ if (variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ };
+ 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));
+ }
+
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ ASSERT_LE(0, srv0_fd = socket_variant(&self->srv0));
+ ASSERT_EQ(0, bind_variant(srv0_fd, &self->srv0));
+ ASSERT_EQ(0, listen(srv0_fd, backlog));
+
+ /* Send on a non-connected socket. */
+ res = sendto_variant(client_fd, NULL, "A", 1, 0);
+ if (variant->prot.domain == AF_UNIX) {
+ EXPECT_EQ(-ENOTCONN, res);
+ } else {
+ EXPECT_EQ(-EPIPE, res);
+ }
+
+ /* Send to a truncated (invalid) address on a non-connected socket. */
+ res = sendto_variant_addrlen(client_fd, &self->srv0,
+ get_addrlen(&self->srv0, true) - 1, "B", 1,
+ 0);
+ if (variant->prot.domain == AF_UNIX) {
+ EXPECT_EQ(-EOPNOTSUPP, res);
+ } else {
+ EXPECT_EQ(-EPIPE, res);
+ }
+
+ /* Connect. */
+ ASSERT_EQ(0, connect_variant(client_fd, &self->srv0));
+ tmp_fd = accept(srv0_fd, NULL, 0);
+ ASSERT_LE(0, tmp_fd);
+ EXPECT_EQ(0, close(srv0_fd));
+ srv0_fd = tmp_fd;
+
+ /* Send without an explicit address. */
+ EXPECT_EQ(0, sendto_variant(client_fd, NULL, "C", 1, 0));
+ EXPECT_EQ(1, recv(srv0_fd, read_buf, 1, 0))
+ {
+ TH_LOG("recv() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'C');
+
+ /* Send to a truncated (invalid) address. */
+ res = sendto_variant_addrlen(client_fd, &self->srv0,
+ get_addrlen(&self->srv0, true) - 1, "D", 1,
+ 0);
+ if (variant->prot.domain == AF_UNIX) {
+ EXPECT_EQ(-EISCONN, res);
+ } else {
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, recv(srv0_fd, read_buf, 1, 0))
+ {
+ TH_LOG("recv() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'D');
+ }
+
+ /* Send to a valid but different address. */
+ res = sendto_variant(client_fd, &self->srv1, "E", 1, 0);
+ if (variant->prot.domain == AF_UNIX) {
+ EXPECT_EQ(-EISCONN, res);
+ } else {
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, recv(srv0_fd, read_buf, 1, 0))
+ {
+ TH_LOG("recv() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'E');
+ }
+
+ EXPECT_EQ(0, close(client_fd));
+}
+
+TEST_F(protocol, sendmsg_dgram)
+{
+ const bool restricted = is_restricted(&variant->prot, variant->sandbox);
+ int srv0_fd, srv1_fd, client_fd, child, status, res;
+
+ if (variant->prot.type != SOCK_DGRAM)
+ return;
+
+ /* Prepare server on port #0 to be allowed. */
+ ASSERT_LE(0, srv0_fd = socket_variant(&self->srv0));
+ ASSERT_EQ(0, bind_variant(srv0_fd, &self->srv0));
+
+ /* And another server on port #1 to be denied. */
+ ASSERT_LE(0, srv1_fd = socket_variant(&self->srv1));
+ ASSERT_EQ(0, bind_variant(srv1_fd, &self->srv1));
+
+ /*
+ * Check that sockets connected before restrictions are not
+ * impacted in any way.
+ */
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ ASSERT_EQ(0, connect_variant(client_fd, &self->srv0));
+ if (variant->sandbox == UDP_SANDBOX) {
+ /* Deny all connect()/send(explicit addr)/bind(). */
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ };
+ 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));
+ }
+ EXPECT_EQ(0,
+ test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, NULL, restricted, restricted));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, &self->srv0, restricted,
+ restricted));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv1_fd, &self->srv1, restricted,
+ restricted));
+ EXPECT_EQ(0, close(client_fd));
+ _exit(_metadata->exit_code);
+ }
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /*
+ * Restrict connect/send, but not bind(). Then try sending with
+ * no destination (and no remote peer set), an allowed
+ * destination, then a denied destination.
+ */
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ if (variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ };
+ const struct landlock_net_port_attr send_p0 = {
+ .allowed_access =
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ .port = self->srv0.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,
+ &send_p0, 0));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ -1, NULL, false, false));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, &self->srv0, false, false));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv1_fd, &self->srv1, false,
+ restricted));
+ EXPECT_EQ(0, close(client_fd));
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /*
+ * Rest of this test is just for autobind enforcement, which only
+ * exists in IP sockets.
+ */
+ if (variant->prot.domain != AF_INET && variant->prot.domain != AF_INET6)
+ return;
+
+ /* Restrict bind() to explicit calls with an arbitrary (non-0) port. */
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ const uint16_t allowed_src_port = 42424;
+ struct service_fixture allowed_src;
+
+ allowed_src = self->srv0;
+ set_port(&allowed_src, allowed_src_port);
+ if (variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_BIND_UDP,
+ };
+ const struct landlock_net_port_attr rule = {
+ .allowed_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+ .port = allowed_src_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,
+ &rule, 0));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+
+ /* Check that implicit bind(0) in sendmsg() is denied. */
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, &self->srv0, restricted,
+ false));
+
+ /* Same thing for autobind in connect(). */
+ res = connect_variant(client_fd, &self->srv0);
+ if (restricted) {
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ EXPECT_EQ(0, res);
+ }
+ EXPECT_EQ(0, close(client_fd));
+
+ /* Make sendmsg() work by explicitly binding to the only allowed port. */
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ EXPECT_EQ(0, bind_variant(client_fd, &allowed_src));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, &self->srv0, restricted,
+ false));
+ EXPECT_EQ(0, close(client_fd));
+
+ /* Make connect() work by explicitly binding to the only allowed port. */
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ EXPECT_EQ(0, bind_variant(client_fd, &allowed_src));
+ EXPECT_EQ(0, connect_variant(client_fd, &self->srv0));
+ EXPECT_EQ(0, close(client_fd));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /*
+ * Check that %LANDLOCK_ACCESS_NET_BIND_UDP on port 0 allows
+ * implicit autobinds.
+ */
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ if (variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_BIND_UDP,
+ };
+ const struct landlock_net_port_attr rule = {
+ .allowed_access = LANDLOCK_ACCESS_NET_BIND_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,
+ &rule, 0));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+ EXPECT_EQ(0, test_sendmsg(_metadata, &variant->prot, client_fd,
+ srv0_fd, &self->srv0, false, false));
+ EXPECT_EQ(0, close(client_fd));
+ _exit(_metadata->exit_code);
+ }
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F(protocol, sendmsg_unspec)
+{
+ const bool restricted = is_restricted(&variant->prot, variant->sandbox);
+ int client_fd, srv0_fd, srv1_fd, res;
+ char read_buf[1] = { 0 };
+
+ /*
+ * We already test for the absence of influence on sendmsg for
+ * other socket types and other address families, there's no
+ * point in adapting this test for stream sockets too.
+ */
+ if (variant->prot.type != SOCK_DGRAM)
+ return;
+
+ /* Prepare client of the right family. */
+ ASSERT_LE(0, client_fd = socket_variant(&self->srv0));
+
+ /* Prepare server on port #0 to be allowed. */
+ ASSERT_LE(0, srv0_fd = socket_variant(&self->srv0));
+ ASSERT_EQ(0, bind_variant(srv0_fd, &self->srv0));
+
+ /* And another server on port #1 to be denied. */
+ ASSERT_LE(0, srv1_fd = socket_variant(&self->srv1));
+ ASSERT_EQ(0, bind_variant(srv1_fd, &self->srv1));
+
+ if (variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net =
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ };
+ const struct landlock_net_port_attr rule = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ .port = self->srv0.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,
+ &rule, 0));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+
+ /* Explicit AF_UNSPEC address but truncated. */
+ EXPECT_EQ(-EINVAL, sendto_variant_addrlen(
+ client_fd, &self->unspec_srv0,
+ get_addrlen(&self->unspec_srv0, true) - 1,
+ "A", 1, 0));
+
+ /*
+ * Explicit AF_UNSPEC address, should be treated as AF_INET by
+ * IPv4 sockets (and thus map to srv0, allowed), but be denied by
+ * IPv6 sockets.
+ */
+ res = sendto_variant(client_fd, &self->unspec_srv0, "B", 1, 0);
+ if (variant->prot.domain == AF_INET6) {
+ if (restricted) {
+ /* Always denied on IPv6 socket. */
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ /* IPv6 sockets treat AF_UNSPEC as a NULL address. */
+ EXPECT_EQ(-EDESTADDRREQ, res);
+ }
+ } else if (variant->prot.domain == AF_INET) {
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv0_fd, read_buf, 1))
+ {
+ TH_LOG("read() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'B');
+ } else {
+ /* Unix sockets don't accept AF_UNSPEC. */
+ EXPECT_EQ(-EINVAL, res);
+ }
+
+ /*
+ * Explicit AF_UNSPEC address, should be treated as AF_INET on
+ * IPv4 sockets (and thus map to srv1, denied), and be denied
+ * on IPv6 sockets as always.
+ */
+ res = sendto_variant(client_fd, &self->unspec_srv1, "C", 1, 0);
+ if (variant->prot.domain == AF_INET6) {
+ if (restricted) {
+ /* Always denied on IPv6 socket. */
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ /* IPv6 sockets treat AF_UNSPEC as a NULL address. */
+ EXPECT_EQ(-EDESTADDRREQ, res);
+ }
+ } else if (variant->prot.domain == AF_INET) {
+ if (restricted) {
+ /* Sending to srv1 is not allowed, only srv0. */
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv1_fd, read_buf, 1))
+ {
+ TH_LOG("read() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'C');
+ }
+ } else {
+ /* Unix sockets don't accept AF_UNSPEC. */
+ EXPECT_EQ(-EINVAL, res);
+ }
+
+ ASSERT_EQ(0, connect_variant(client_fd, &self->srv0));
+
+ /* Minimal explicit AF_UNSPEC address (just the sa_family_t field) */
+ res = sendto_variant_addrlen(client_fd, &self->unspec_srv0,
+ get_addrlen(&self->unspec_srv0, true), "D",
+ 1, 0);
+ if (variant->prot.domain == AF_INET6) {
+ if (restricted) {
+ /* AF_UNSPEC is always denied in IPv6. */
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ /*
+ * IPv6 sockets treat AF_UNSPEC as a NULL address,
+ * falling back to the connected address.
+ */
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv0_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'D');
+ }
+ } else {
+ /*
+ * IPv4 socket will expect a struct sockaddr_in, our address
+ * is considered truncated.
+ * And Unix sockets don't accept AF_UNSPEC at all.
+ */
+ EXPECT_EQ(-EINVAL, res);
+ }
+}
+
FIXTURE(ipv4)
{
struct service_fixture srv0, srv1;
@@ -976,6 +1735,13 @@ FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_tcp) {
};
/* 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 */
.sandbox = NO_SANDBOX,
@@ -989,6 +1755,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 +1785,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 int 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;
@@ -1326,11 +2104,13 @@ FIXTURE_TEARDOWN(mini)
/* clang-format off */
-#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP
#define ACCESS_ALL ( \
LANDLOCK_ACCESS_NET_BIND_TCP | \
- LANDLOCK_ACCESS_NET_CONNECT_TCP)
+ LANDLOCK_ACCESS_NET_CONNECT_TCP | \
+ LANDLOCK_ACCESS_NET_BIND_UDP | \
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)
/* clang-format on */
@@ -1678,6 +2458,7 @@ TEST_F(ipv4_tcp, with_fs)
FIXTURE(port_specific)
{
struct service_fixture srv0;
+ struct service_fixture cli1;
};
FIXTURE_VARIANT(port_specific)
@@ -1697,7 +2478,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 = {
@@ -1707,6 +2488,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 */
.sandbox = NO_SANDBOX,
@@ -1717,7 +2508,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 = {
@@ -1726,11 +2517,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);
};
@@ -1745,14 +2547,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 int 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;
@@ -1764,7 +2571,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));
@@ -1785,11 +2592,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);
@@ -1813,23 +2625,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 int bind_right = (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
+ const int 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,
@@ -1838,10 +2662,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));
@@ -1865,8 +2694,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);
@@ -1885,7 +2725,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);
@@ -1895,23 +2738,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;
@@ -1922,6 +2772,8 @@ static int matches_log_tcp(const int audit_fd, const char *const blockers,
FIXTURE(audit)
{
struct service_fixture srv0;
+ struct service_fixture srv1;
+ struct service_fixture unspec_srv0;
struct audit_filter audit_filter;
int audit_fd;
};
@@ -1933,7 +2785,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 = {
@@ -1943,7 +2795,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 = {
@@ -1952,9 +2814,26 @@ 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)
{
+ struct protocol_variant prot_unspec = variant->prot;
+
+ prot_unspec.domain = AF_UNSPEC;
+
ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+ ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
+ ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
+
setup_loopback(_metadata);
set_cap(_metadata, CAP_AUDIT_CONTROL);
@@ -1972,9 +2851,17 @@ FIXTURE_TEARDOWN(audit)
TEST_F(audit, bind)
{
+ const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+ "net\\.bind_tcp" :
+ "net\\.bind_udp");
+ const int 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;
@@ -1988,8 +2875,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);
@@ -2000,9 +2887,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 int bind_right = (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
+ const int conn_right = (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+ const int 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;
@@ -2010,19 +2910,85 @@ 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, records.access);
+ EXPECT_EQ(1, records.domain);
+ }
+
+ EXPECT_EQ(0, close(sock_fd));
+}
+
+TEST_F(audit, sendmsg)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP |
+ LANDLOCK_ACCESS_NET_BIND_UDP,
+ };
+ const struct landlock_net_port_attr rule = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ .port = self->srv1.port,
+ };
+ const int ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ struct audit_records records;
+ int sock_fd;
+
+ /* Sendmsg on stream sockets is never denied. */
+ if (variant->prot.type != SOCK_DGRAM)
+ return;
+
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &rule, 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, sendto_variant(sock_fd, &self->srv0, "A", 1, 0));
+ EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
+ "daddr", variant->addr, "dest"));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(1, records.domain);
+
+ /* Check that autobind generates a denied bind event. */
+ EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv1, "A", 1, 0));
+ EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp", NULL,
+ NULL, NULL));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(1, records.domain);
+
+ EXPECT_EQ(-EACCES,
+ sendto_variant(sock_fd, &self->unspec_srv0, "B", 1, 0));
+ EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
+ "daddr", NULL, "dest"));
+
+ 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));
}
diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index 1b6c8b53bf33a..4f64c90583cd6 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -342,6 +342,7 @@ TEST_F(audit, trace)
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
yama_ptrace_scope = get_yama_ptrace_scope();
ASSERT_LE(0, yama_ptrace_scope);
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index c47491d2d1c14..72f97648d4a7d 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -312,6 +312,7 @@ TEST_F(scoped_audit, connect_to_child)
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));