ASSERT_EQ(child_pid, waitpid(child_pid, NULL, 0));
}
-/* For named UNIX domain sockets, no IOCTL restrictions apply. */
-TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
+/*
+ * set_up_named_unix_server - Create a pathname unix socket
+ *
+ * If the socket type is not SOCK_DGRAM, also invoke listen(2).
+ *
+ * Return: The listening FD - it is the caller responsibility to close it.
+ */
+static int set_up_named_unix_server(struct __test_metadata *const _metadata,
+ int type, const char *const path)
{
- const char *const path = file1_s1d1;
- int srv_fd, cli_fd, ruleset_fd;
- struct sockaddr_un srv_un = {
+ int fd;
+ struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
- struct sockaddr_un cli_un = {
+
+ fd = socket(AF_UNIX, type, 0);
+ ASSERT_LE(0, fd);
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ ASSERT_EQ(0, bind(fd, (struct sockaddr *)&addr, sizeof(addr)));
+
+ if (type != SOCK_DGRAM)
+ ASSERT_EQ(0, listen(fd, 10 /* qlen */));
+ return fd;
+}
+
+/*
+ * test_connect_named_unix - connect to the given named UNIX socket
+ *
+ * Return: The errno from connect(), or 0
+ */
+static int test_connect_named_unix(struct __test_metadata *const _metadata,
+ int fd, const char *const path)
+{
+ struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ return errno;
+ return 0;
+}
+
+/* For named UNIX domain sockets, no IOCTL restrictions apply. */
+TEST_F_FORK(layout1, named_unix_domain_socket_ioctl)
+{
+ const char *const path = file1_s1d1;
+ int srv_fd, cli_fd, ruleset_fd;
const struct landlock_ruleset_attr attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
};
/* Sets up a server */
ASSERT_EQ(0, unlink(path));
- srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- ASSERT_LE(0, srv_fd);
-
- strncpy(srv_un.sun_path, path, sizeof(srv_un.sun_path));
- ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, sizeof(srv_un)));
-
- ASSERT_EQ(0, listen(srv_fd, 10 /* qlen */));
+ srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path);
/* Enables Landlock. */
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
ASSERT_LE(0, cli_fd);
- strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path));
- ASSERT_EQ(0,
- connect(cli_fd, (struct sockaddr *)&cli_un, sizeof(cli_un)));
+ ASSERT_EQ(0, test_connect_named_unix(_metadata, cli_fd, path));
/* FIONREAD and other IOCTLs should not be forbidden. */
EXPECT_EQ(0, test_fionread_ioctl(cli_fd));
ASSERT_EQ(0, close(file_fd));
}
+/*
+ * test_sendto_named_unix - sendto to the given named UNIX socket
+ *
+ * sendto() is equivalent to sendmsg() in this respect.
+ *
+ * Return: The errno from sendto(), or 0
+ */
+static int test_sendto_named_unix(struct __test_metadata *const _metadata,
+ int fd, const char *const path)
+{
+ static const char buf[] = "dummy";
+ struct sockaddr_un addr = {
+ .sun_family = AF_UNIX,
+ };
+
+ ASSERT_LT(strlen(path), sizeof(addr.sun_path));
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ if (sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&addr,
+ sizeof(addr)) == -1)
+ return errno;
+ return 0;
+}
+
+/* clang-format off */
+FIXTURE(scoped_domains) {};
+/* clang-format on */
+
+#include "scoped_base_variants.h"
+
+FIXTURE_SETUP(scoped_domains)
+{
+ drop_caps(_metadata);
+};
+
+FIXTURE_TEARDOWN(scoped_domains)
+{
+}
+
+static void enforce_fs_resolve_unix(struct __test_metadata *const _metadata,
+ const struct rule rules[])
+{
+ if (rules) {
+ int fd = create_ruleset(_metadata,
+ LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules);
+ enforce_ruleset(_metadata, fd);
+ EXPECT_EQ(0, close(fd));
+ } else {
+ struct landlock_ruleset_attr attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ };
+ drop_access_rights(_metadata, &attr);
+ }
+}
+
+/*
+ * Flags for test_connect_to_parent and test_connect_to_child:
+ *
+ * USE_SENDTO: Use sendto() instead of connect() (for SOCK_DGRAM only)
+ * ENFORCE_ALL: Enforce a Landlock domain even when the variant says
+ * we shouldn't. We enforce a domain where the path is allow-listed,
+ * and expect the behavior to be the same as if none was used.
+ */
+#define USE_SENDTO (1 << 0)
+#define ENFORCE_ALL (1 << 1)
+
+static void test_connect_to_parent(struct __test_metadata *const _metadata,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ int sock_type, int flags)
+{
+ const char *const path = "sock";
+ const struct rule rules[] = {
+ {
+ .path = ".",
+ .access = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ },
+ {},
+ };
+ int cli_fd, srv_fd, res, status;
+ pid_t child_pid;
+ int readiness_pipe[2];
+ char buf[1];
+
+ if (variant->domain_both)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ unlink(path);
+ ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
+
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+
+ if (child_pid == 0) {
+ if (variant->domain_child)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ /* Wait for server to be available. */
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+ EXPECT_EQ(1, read(readiness_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+
+ /* Talk to server. */
+ cli_fd = socket(AF_UNIX, sock_type, 0);
+ ASSERT_LE(0, cli_fd);
+
+ if (flags & USE_SENDTO)
+ res = test_sendto_named_unix(_metadata, cli_fd, path);
+ else
+ res = test_connect_named_unix(_metadata, cli_fd, path);
+
+ EXPECT_EQ(variant->domain_child ? EACCES : 0, res);
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(cli_fd));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ if (variant->domain_parent)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
+
+ /* Tell the child that it can connect. */
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+ EXPECT_EQ(sizeof(buf), write(readiness_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+
+ /* Wait for child. */
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(srv_fd));
+ EXPECT_EQ(0, unlink(path));
+}
+
+static void test_connect_to_child(struct __test_metadata *const _metadata,
+ const FIXTURE_VARIANT(scoped_domains) *
+ variant,
+ int sock_type, int flags)
+{
+ const char *const path = "sock";
+ const struct rule rules[] = {
+ {
+ .path = ".",
+ .access = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+ },
+ {},
+ };
+ int readiness_pipe[2];
+ int shutdown_pipe[2];
+ int cli_fd, srv_fd, res, status;
+ pid_t child_pid;
+ char buf[1];
+
+ if (variant->domain_both)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ unlink(path);
+ ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC));
+ ASSERT_EQ(0, pipe2(shutdown_pipe, O_CLOEXEC));
+
+ child_pid = fork();
+ ASSERT_LE(0, child_pid);
+
+ if (child_pid == 0) {
+ if (variant->domain_child)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ srv_fd = set_up_named_unix_server(_metadata, sock_type, path);
+
+ /* Tell the parent that it can connect. */
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+ EXPECT_EQ(sizeof(buf),
+ write(readiness_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+
+ /* Wait until it is time to shut down. */
+ EXPECT_EQ(0, close(shutdown_pipe[1]));
+ EXPECT_EQ(1, read(shutdown_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(shutdown_pipe[0]));
+
+ /* Cleanup */
+ EXPECT_EQ(0, close(srv_fd));
+ EXPECT_EQ(0, unlink(path));
+
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ if (variant->domain_parent)
+ enforce_fs_resolve_unix(_metadata, NULL);
+ else if (flags & ENFORCE_ALL)
+ enforce_fs_resolve_unix(_metadata, rules);
+
+ /* Wait for server to be available. */
+ EXPECT_EQ(0, close(readiness_pipe[1]));
+ EXPECT_EQ(1, read(readiness_pipe[0], &buf, 1));
+ EXPECT_EQ(0, close(readiness_pipe[0]));
+
+ /* Talk to server. */
+ cli_fd = socket(AF_UNIX, sock_type, 0);
+ ASSERT_LE(0, cli_fd);
+
+ if (flags & USE_SENDTO)
+ res = test_sendto_named_unix(_metadata, cli_fd, path);
+ else
+ res = test_connect_named_unix(_metadata, cli_fd, path);
+
+ EXPECT_EQ(variant->domain_parent ? EACCES : 0, res);
+
+ /* Clean up. */
+ EXPECT_EQ(0, close(cli_fd));
+
+ /* Tell the server to shut down. */
+ EXPECT_EQ(0, close(shutdown_pipe[0]));
+ EXPECT_EQ(sizeof(buf), write(shutdown_pipe[1], buf, sizeof(buf)));
+ EXPECT_EQ(0, close(shutdown_pipe[1]));
+
+ /* Wait for child. */
+ ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_STREAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, USE_SENDTO);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_parent)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_SEQPACKET, 0);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_STREAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_DGRAM,
+ USE_SENDTO | ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_parent_full)
+{
+ test_connect_to_parent(_metadata, variant, SOCK_SEQPACKET, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_STREAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, 0);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, USE_SENDTO);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_child)
+{
+ test_connect_to_child(_metadata, variant, SOCK_SEQPACKET, 0);
+}
+
+TEST_F(scoped_domains, unix_stream_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_STREAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM, ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_dgram_sendmsg_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_DGRAM,
+ USE_SENDTO | ENFORCE_ALL);
+}
+
+TEST_F(scoped_domains, unix_seqpacket_connect_to_child_full)
+{
+ test_connect_to_child(_metadata, variant, SOCK_SEQPACKET, ENFORCE_ALL);
+}
+
+#undef USE_SENDTO
+#undef ENFORCE_ALL
+
/* clang-format off */
FIXTURE(layout1_bind) {};
/* clang-format on */