From 874c8f83826c95c62c21d9edfe9ef43e5c346724 Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Tue, 7 Apr 2026 18:41:04 +0200 Subject: landlock: Fix LOG_SUBDOMAINS_OFF inheritance across fork() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hook_cred_transfer() only copies the Landlock security blob when the source credential has a domain. This is inconsistent with landlock_restrict_self() which can set LOG_SUBDOMAINS_OFF on a credential without creating a domain (via the ruleset_fd=-1 path): the field is committed but not preserved across fork() because the child's prepare_creds() calls hook_cred_transfer() which skips the copy when domain is NULL. This breaks the documented use case where a process mutes subdomain logs before forking sandboxed children: the children lose the muting and their domains produce unexpected audit records. Fix this by unconditionally copying the Landlock credential blob. Cc: Günther Noack Cc: Jann Horn Cc: stable@vger.kernel.org Fixes: ead9079f7569 ("landlock: Add LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260407164107.2012589-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/audit_test.c | 88 +++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c index 46d02d49835a..20099b8667e7 100644 --- a/tools/testing/selftests/landlock/audit_test.c +++ b/tools/testing/selftests/landlock/audit_test.c @@ -279,6 +279,94 @@ TEST_F(audit, thread) &audit_tv_default, sizeof(audit_tv_default))); } +/* + * Verifies that log_subdomains_off set via the ruleset_fd=-1 path (without + * creating a domain) is inherited by children across fork(). This exercises + * the hook_cred_transfer() fix: the Landlock credential blob must be copied + * even when the source credential has no domain. + * + * Phase 1 (baseline): a child without muting creates a domain and triggers a + * denial that IS logged. + * + * Phase 2 (after muting): the parent mutes subdomain logs, forks another child + * who creates a domain and triggers a denial that is NOT logged. + */ +TEST_F(audit, log_subdomains_off_fork) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .scoped = LANDLOCK_SCOPE_SIGNAL, + }; + struct audit_records records; + int ruleset_fd, status; + pid_t child; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + + /* + * Phase 1: forks a child that creates a domain and triggers a denial + * before any muting. This proves the audit path works. + */ + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); + ASSERT_EQ(-1, kill(getppid(), 0)); + ASSERT_EQ(EPERM, errno); + _exit(0); + return; + } + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(true, WIFEXITED(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); + + /* The denial must be logged (baseline). */ + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, getpid(), + NULL)); + + /* Drains any remaining records (e.g. domain allocation). */ + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + + /* + * Mutes subdomain logs without creating a domain. The parent's + * credential has domain=NULL and log_subdomains_off=1. + */ + ASSERT_EQ(0, landlock_restrict_self( + -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)); + + /* + * Phase 2: forks a child that creates a domain and triggers a denial. + * Because log_subdomains_off was inherited via fork(), the child's + * domain has log_status=LANDLOCK_LOG_DISABLED. + */ + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); + ASSERT_EQ(-1, kill(getppid(), 0)); + ASSERT_EQ(EPERM, errno); + _exit(0); + return; + } + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(true, WIFEXITED(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); + + /* No denial record should appear. */ + EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd, + getpid(), NULL)); + + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + + EXPECT_EQ(0, close(ruleset_fd)); +} + FIXTURE(audit_flags) { struct audit_filter audit_filter; -- cgit v1.2.3 From e75e38055b9df5eafd663c6db00e634f534dc426 Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Tue, 7 Apr 2026 18:41:05 +0200 Subject: landlock: Allow TSYNC with LOG_SUBDOMAINS_OFF and fd=-1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LANDLOCK_RESTRICT_SELF_TSYNC does not allow LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with ruleset_fd=-1, preventing a multithreaded process from atomically propagating subdomain log muting to all threads without creating a domain layer. Relax the fd=-1 condition to accept TSYNC alongside LOG_SUBDOMAINS_OFF, and update the documentation accordingly. Add flag validation tests for all TSYNC combinations with ruleset_fd=-1, and audit tests verifying both transition directions: muting via TSYNC (logged to not logged) and override via TSYNC (not logged to logged). Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: 42fc7e6543f6 ("landlock: Multithreading support for landlock_restrict_self()") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260407164107.2012589-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 4 +- security/landlock/syscalls.c | 14 +- tools/testing/selftests/landlock/audit_test.c | 233 ++++++++++++++++++++++++++ tools/testing/selftests/landlock/tsync_test.c | 77 +++++++++ 4 files changed, 322 insertions(+), 6 deletions(-) (limited to 'tools/testing') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index f88fa1f68b77..d37603efc273 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -116,7 +116,9 @@ struct landlock_ruleset_attr { * ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``, this flag only affects * future nested domains, not the one being created. It can also be used * with a @ruleset_fd value of -1 to mute subdomain logs without creating a - * domain. + * domain. When combined with %LANDLOCK_RESTRICT_SELF_TSYNC and a + * @ruleset_fd value of -1, this configuration is propagated to all threads + * of the current process. * * The following flag supports policy enforcement in multithreaded processes: * diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 0d66a68677b7..a0bb664e0d31 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -512,10 +512,13 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, /* * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with - * -1 as ruleset_fd, but no other flag must be set. + * -1 as ruleset_fd, optionally combined with + * LANDLOCK_RESTRICT_SELF_TSYNC to propagate this configuration to all + * threads. No other flag must be set. */ if (!(ruleset_fd == -1 && - flags == LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { + (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) == + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { /* Gets and checks the ruleset. */ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); if (IS_ERR(ruleset)) @@ -537,9 +540,10 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, /* * The only case when a ruleset may not be set is if - * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set and ruleset_fd is -1. - * We could optimize this case by not calling commit_creds() if this flag - * was already set, but it is not worth the complexity. + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with + * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could + * optimize this case by not calling commit_creds() if this flag was + * already set, but it is not worth the complexity. */ if (ruleset) { /* diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c index 20099b8667e7..897596cd7c80 100644 --- a/tools/testing/selftests/landlock/audit_test.c +++ b/tools/testing/selftests/landlock/audit_test.c @@ -162,6 +162,7 @@ TEST_F(audit, layers) struct thread_data { pid_t parent_pid; int ruleset_fd, pipe_child, pipe_parent; + bool mute_subdomains; }; static void *thread_audit_test(void *arg) @@ -367,6 +368,238 @@ TEST_F(audit, log_subdomains_off_fork) EXPECT_EQ(0, close(ruleset_fd)); } +/* + * Thread function: runs two rounds of (create domain, trigger denial, signal + * back), waiting for the main thread before each round. When mute_subdomains + * is set, phase 1 also mutes subdomain logs via the fd=-1 path before creating + * the domain. The ruleset_fd is kept open across both rounds so each + * restrict_self call stacks a new domain layer. + */ +static void *thread_sandbox_deny_twice(void *arg) +{ + const struct thread_data *data = (struct thread_data *)arg; + uintptr_t err = 0; + char buffer; + + /* Phase 1: optionally mutes, creates a domain, and triggers a denial. */ + if (read(data->pipe_parent, &buffer, 1) != 1) { + err = 1; + goto out; + } + + if (data->mute_subdomains && + landlock_restrict_self(-1, + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { + err = 2; + goto out; + } + + if (landlock_restrict_self(data->ruleset_fd, 0)) { + err = 3; + goto out; + } + + if (kill(data->parent_pid, 0) != -1 || errno != EPERM) { + err = 4; + goto out; + } + + if (write(data->pipe_child, ".", 1) != 1) { + err = 5; + goto out; + } + + /* Phase 2: stacks another domain and triggers a denial. */ + if (read(data->pipe_parent, &buffer, 1) != 1) { + err = 6; + goto out; + } + + if (landlock_restrict_self(data->ruleset_fd, 0)) { + err = 7; + goto out; + } + + if (kill(data->parent_pid, 0) != -1 || errno != EPERM) { + err = 8; + goto out; + } + + if (write(data->pipe_child, ".", 1) != 1) { + err = 9; + goto out; + } + +out: + close(data->ruleset_fd); + close(data->pipe_child); + close(data->pipe_parent); + return (void *)err; +} + +/* + * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with + * LANDLOCK_RESTRICT_SELF_TSYNC and ruleset_fd=-1 propagates log_subdomains_off + * to a sibling thread, suppressing audit logging on domains it subsequently + * creates. + * + * Phase 1 (before TSYNC) acts as an inline baseline: the sibling creates a + * domain and triggers a denial that IS logged. + * + * Phase 2 (after TSYNC) verifies suppression: the sibling stacks another domain + * and triggers a denial that is NOT logged. + */ +TEST_F(audit, log_subdomains_off_tsync) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .scoped = LANDLOCK_SCOPE_SIGNAL, + }; + struct audit_records records; + struct thread_data child_data = {}; + int pipe_child[2], pipe_parent[2]; + char buffer; + pthread_t thread; + void *thread_ret; + + child_data.parent_pid = getppid(); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + child_data.pipe_child = pipe_child[1]; + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + child_data.pipe_parent = pipe_parent[0]; + child_data.ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, child_data.ruleset_fd); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + + /* Creates the sibling thread. */ + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_sandbox_deny_twice, + &child_data)); + + /* + * Phase 1: the sibling creates a domain and triggers a denial before + * any log muting. This proves the audit path works. + */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); + + /* The denial must be logged. */ + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, + child_data.parent_pid, NULL)); + + /* Drains any remaining records (e.g. domain allocation). */ + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + + /* + * Mutes subdomain logs and propagates to the sibling thread via TSYNC, + * without creating a domain. + */ + ASSERT_EQ(0, landlock_restrict_self( + -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | + LANDLOCK_RESTRICT_SELF_TSYNC)); + + /* + * Phase 2: the sibling stacks another domain and triggers a denial. + * Because log_subdomains_off was propagated via TSYNC, the new domain + * has log_status=LANDLOCK_LOG_DISABLED. + */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); + + /* No denial record should appear. */ + EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd, + child_data.parent_pid, NULL)); + + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + + EXPECT_EQ(0, close(pipe_child[0])); + EXPECT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, pthread_join(thread, &thread_ret)); + EXPECT_EQ(NULL, thread_ret); +} + +/* + * Verifies that LANDLOCK_RESTRICT_SELF_TSYNC without + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF overrides a sibling thread's + * log_subdomains_off, re-enabling audit logging on domains the sibling + * subsequently creates. + * + * Phase 1: the sibling sets log_subdomains_off, creates a muted domain, and + * triggers a denial that is NOT logged. + * + * Phase 2 (after TSYNC without LOG_SUBDOMAINS_OFF): the sibling stacks another + * domain and triggers a denial that IS logged, proving the muting was + * overridden. + */ +TEST_F(audit, tsync_override_log_subdomains_off) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .scoped = LANDLOCK_SCOPE_SIGNAL, + }; + struct audit_records records; + struct thread_data child_data = {}; + int pipe_child[2], pipe_parent[2]; + char buffer; + pthread_t thread; + void *thread_ret; + + child_data.parent_pid = getppid(); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + child_data.pipe_child = pipe_child[1]; + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + child_data.pipe_parent = pipe_parent[0]; + child_data.ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, child_data.ruleset_fd); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + + child_data.mute_subdomains = true; + + /* Creates the sibling thread. */ + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_sandbox_deny_twice, + &child_data)); + + /* + * Phase 1: the sibling mutes subdomain logs, creates a domain, and + * triggers a denial. The denial must not be logged. + */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); + + EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd, + child_data.parent_pid, NULL)); + + /* Drains any remaining records. */ + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + + /* + * Overrides the sibling's log_subdomains_off by calling TSYNC without + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF. + */ + ASSERT_EQ(0, landlock_restrict_self(child_data.ruleset_fd, + LANDLOCK_RESTRICT_SELF_TSYNC)); + + /* + * Phase 2: the sibling stacks another domain and triggers a denial. + * Because TSYNC replaced its log_subdomains_off with 0, the new domain + * has log_status=LANDLOCK_LOG_PENDING. + */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); + + /* The denial must be logged. */ + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, + child_data.parent_pid, NULL)); + + EXPECT_EQ(0, close(pipe_child[0])); + EXPECT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, pthread_join(thread, &thread_ret)); + EXPECT_EQ(NULL, thread_ret); +} + FIXTURE(audit_flags) { struct audit_filter audit_filter; diff --git a/tools/testing/selftests/landlock/tsync_test.c b/tools/testing/selftests/landlock/tsync_test.c index 2b9ad4f154f4..9cf1491bbaaf 100644 --- a/tools/testing/selftests/landlock/tsync_test.c +++ b/tools/testing/selftests/landlock/tsync_test.c @@ -247,4 +247,81 @@ TEST(tsync_interrupt) EXPECT_EQ(0, close(ruleset_fd)); } +/* clang-format off */ +FIXTURE(tsync_without_ruleset) {}; +/* clang-format on */ + +FIXTURE_VARIANT(tsync_without_ruleset) +{ + const __u32 flags; + const int expected_errno; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tsync_without_ruleset, tsync_only) { + /* clang-format on */ + .flags = LANDLOCK_RESTRICT_SELF_TSYNC, + .expected_errno = EBADF, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off_same_exec_off) { + /* clang-format on */ + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | + LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF | + LANDLOCK_RESTRICT_SELF_TSYNC, + .expected_errno = EBADF, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off_new_exec_on) { + /* clang-format on */ + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON | + LANDLOCK_RESTRICT_SELF_TSYNC, + .expected_errno = EBADF, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tsync_without_ruleset, all_flags) { + /* clang-format on */ + .flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF | + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON | + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | + LANDLOCK_RESTRICT_SELF_TSYNC, + .expected_errno = EBADF, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off) { + /* clang-format on */ + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | + LANDLOCK_RESTRICT_SELF_TSYNC, + .expected_errno = 0, +}; + +FIXTURE_SETUP(tsync_without_ruleset) +{ + disable_caps(_metadata); +} + +FIXTURE_TEARDOWN(tsync_without_ruleset) +{ +} + +TEST_F(tsync_without_ruleset, check) +{ + int ret; + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + + ret = landlock_restrict_self(-1, variant->flags); + if (variant->expected_errno) { + EXPECT_EQ(-1, ret); + EXPECT_EQ(variant->expected_errno, errno); + } else { + EXPECT_EQ(0, ret); + } +} + TEST_HARNESS_MAIN -- cgit v1.2.3 From b566f7a4f0e4f15f78f2e5fac273fa954991e03a Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Thu, 2 Apr 2026 21:26:02 +0200 Subject: selftests/landlock: Fix snprintf truncation checks in audit helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snprintf() returns the number of characters that would have been written, excluding the terminating NUL byte. When the output is truncated, this return value equals or exceeds the buffer size. Fix matches_log_domain_allocated() and matches_log_domain_deallocated() to detect truncation with ">=" instead of ">". Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260402192608.1458252-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/audit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h index 44eb433e9666..1049a0582af5 100644 --- a/tools/testing/selftests/landlock/audit.h +++ b/tools/testing/selftests/landlock/audit.h @@ -309,7 +309,7 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, log_match_len = snprintf(log_match, sizeof(log_match), log_template, pid); - if (log_match_len > sizeof(log_match)) + if (log_match_len >= sizeof(log_match)) return -E2BIG; return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, @@ -326,7 +326,7 @@ static int __maybe_unused matches_log_domain_deallocated( log_match_len = snprintf(log_match, sizeof(log_match), log_template, num_denials); - if (log_match_len > sizeof(log_match)) + if (log_match_len >= sizeof(log_match)) return -E2BIG; return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, -- cgit v1.2.3 From 9143d790337a0d066c2d632c802f69b981e6c23a Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Thu, 2 Apr 2026 21:26:03 +0200 Subject: selftests/landlock: Fix socket file descriptor leaks in audit helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audit_init() opens a netlink socket and configures it, but leaks the file descriptor if audit_set_status() or setsockopt() fails. Fix this by jumping to an error path that closes the socket before returning. Apply the same fix to audit_init_with_exe_filter(), which leaks the file descriptor from audit_init() if audit_init_filter_exe() or audit_filter_exe() fails, and to audit_cleanup(), which leaks it if audit_init_filter_exe() fails in FIXTURE_TEARDOWN_PARENT(). Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260402192608.1458252-3-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/audit.h | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h index 1049a0582af5..6422943fc69e 100644 --- a/tools/testing/selftests/landlock/audit.h +++ b/tools/testing/selftests/landlock/audit.h @@ -379,19 +379,25 @@ static int audit_init(void) err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1); if (err) - return err; + goto err_close; err = audit_set_status(fd, AUDIT_STATUS_PID, getpid()); if (err) - return err; + goto err_close; /* Sets a timeout for negative tests. */ err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default, sizeof(audit_tv_default)); - if (err) - return -errno; + if (err) { + err = -errno; + goto err_close; + } return fd; + +err_close: + close(fd); + return err; } static int audit_init_filter_exe(struct audit_filter *filter, const char *path) @@ -441,8 +447,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter) filter = &new_filter; err = audit_init_filter_exe(filter, NULL); - if (err) + if (err) { + close(audit_fd); return err; + } } /* Filters might not be in place. */ @@ -468,11 +476,15 @@ static int audit_init_with_exe_filter(struct audit_filter *filter) err = audit_init_filter_exe(filter, NULL); if (err) - return err; + goto err_close; err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE); if (err) - return err; + goto err_close; return fd; + +err_close: + close(fd); + return err; } -- cgit v1.2.3 From 3647a4977fb73da385e5a29b9775a4749733470d Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Thu, 2 Apr 2026 21:26:04 +0200 Subject: selftests/landlock: Drain stale audit records on init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-audit Landlock tests generate audit records as side effects when audit_enabled is non-zero (e.g. from boot configuration). These records accumulate in the kernel audit backlog while no audit daemon socket is open. When the next test opens a new netlink socket and registers as the audit daemon, the stale backlog is delivered, causing baseline record count checks to fail spuriously. Fix this by draining all pending records in audit_init() right after setting the receive timeout. The 1-usec SO_RCVTIMEO causes audit_recv() to return -EAGAIN once the backlog is empty, naturally terminating the drain loop. Domain deallocation records are emitted asynchronously from a work queue, so they may still arrive after the drain. Remove records.domain == 0 checks that are not preceded by audit_match_record() calls, which would otherwise consume stale records before the count. Document this constraint above audit_count_records(). Increasing the drain timeout to catch in-flight deallocation records was considered but rejected: a longer timeout adds latency to every audit_init() call even when no stale record is pending, and any fixed timeout is still not guaranteed to catch all records under load. Removing the unprotected checks is simpler and avoids the spurious failures. Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260402192608.1458252-4-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/audit.h | 19 +++++++++++++++++++ tools/testing/selftests/landlock/audit_test.c | 2 -- tools/testing/selftests/landlock/ptrace_test.c | 1 - .../selftests/landlock/scoped_abstract_unix_test.c | 1 - 4 files changed, 19 insertions(+), 4 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h index 6422943fc69e..74e1c3d763be 100644 --- a/tools/testing/selftests/landlock/audit.h +++ b/tools/testing/selftests/landlock/audit.h @@ -338,6 +338,15 @@ struct audit_records { size_t domain; }; +/* + * 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. + */ static int audit_count_records(int audit_fd, struct audit_records *records) { struct audit_message msg; @@ -393,6 +402,16 @@ static int audit_init(void) goto err_close; } + /* + * Drains stale audit records that accumulated in the kernel backlog + * while no audit daemon socket was open. This happens when non-audit + * Landlock tests generate records while audit_enabled is non-zero (e.g. + * from boot configuration), or when domain deallocation records arrive + * asynchronously after a previous test's socket was closed. + */ + while (audit_recv(fd, NULL) == 0) + ; + return fd; err_close: diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c index 897596cd7c80..c697e22d8f68 100644 --- a/tools/testing/selftests/landlock/audit_test.c +++ b/tools/testing/selftests/landlock/audit_test.c @@ -733,7 +733,6 @@ 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); @@ -922,7 +921,6 @@ 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/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c index 4f64c90583cd..1b6c8b53bf33 100644 --- a/tools/testing/selftests/landlock/ptrace_test.c +++ b/tools/testing/selftests/landlock/ptrace_test.c @@ -342,7 +342,6 @@ 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 72f97648d4a7..c47491d2d1c1 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -312,7 +312,6 @@ 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)); -- cgit v1.2.3 From 07c2572a87573b2a2f0fd6b9f538cd1aeef2eee7 Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Thu, 2 Apr 2026 21:26:05 +0200 Subject: selftests/landlock: Skip stale records in audit_match_record() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Domain deallocation records are emitted asynchronously from kworker threads (via free_ruleset_work()). Stale deallocation records from a previous test can arrive during the current test's deallocation read loop and be picked up by audit_match_record() instead of the expected record, causing a domain ID mismatch. The audit.layers test (which creates 16 nested domains) is particularly vulnerable because it reads 16 deallocation records in sequence, providing a large window for stale records to interleave. The same issue affects audit_flags.signal, where deallocation records from a previous test (audit.layers) can leak into the next test and be picked up by audit_match_record() instead of the expected record. Fix this by continuing to read records when the type matches but the content pattern does not. Stale records are silently consumed, and the loop only stops when both type and pattern match (or the socket times out with -EAGAIN). Additionally, extend matches_log_domain_deallocated() with an expected_domain_id parameter. When set, the regex pattern includes the specific domain ID as a literal hex value, so that deallocation records for a different domain do not match the pattern at all. This handles the case where the stale record has the same denial count as the expected one (e.g. both have denials=1), which the type+pattern loop alone cannot distinguish. Callers that already know the expected domain ID (from a prior denial or allocation record) now pass it to filter precisely. When expected_domain_id is set, matches_log_domain_deallocated() also temporarily increases the socket timeout to audit_tv_dom_drop (1 second) to wait for the asynchronous kworker deallocation, and restores audit_tv_default afterward. This removes the need for callers to manage the timeout switch manually. Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs") Link: https://lore.kernel.org/r/20260402192608.1458252-5-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/audit.h | 82 ++++++++++++++++++++------- tools/testing/selftests/landlock/audit_test.c | 34 +++++------ 2 files changed, 77 insertions(+), 39 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h index 74e1c3d763be..834005b2b0f0 100644 --- a/tools/testing/selftests/landlock/audit.h +++ b/tools/testing/selftests/landlock/audit.h @@ -249,9 +249,9 @@ static __maybe_unused char *regex_escape(const char *const src, char *dst, static int audit_match_record(int audit_fd, const __u16 type, const char *const pattern, __u64 *domain_id) { - struct audit_message msg; + struct audit_message msg, last_mismatch = {}; int ret, err = 0; - bool matches_record = !type; + int num_type_match = 0; regmatch_t matches[2]; regex_t regex; @@ -259,21 +259,35 @@ static int audit_match_record(int audit_fd, const __u16 type, if (ret) return -EINVAL; - do { + /* + * Reads records until one matches both the expected type and the + * pattern. Type-matching records with non-matching content are + * silently consumed, which handles stale domain deallocation records + * from a previous test emitted asynchronously by kworker threads. + */ + while (true) { memset(&msg, 0, sizeof(msg)); err = audit_recv(audit_fd, &msg); - if (err) + if (err) { + if (num_type_match) { + printf("DATA: %s\n", last_mismatch.data); + printf("ERROR: %d record(s) matched type %u" + " but not pattern: %s\n", + num_type_match, type, pattern); + } goto out; + } - if (msg.header.nlmsg_type == type) - matches_record = true; - } while (!matches_record); + if (type && msg.header.nlmsg_type != type) + continue; - ret = regexec(®ex, msg.data, ARRAY_SIZE(matches), matches, 0); - if (ret) { - printf("DATA: %s\n", msg.data); - printf("ERROR: no match for pattern: %s\n", pattern); - err = -ENOENT; + ret = regexec(®ex, msg.data, ARRAY_SIZE(matches), matches, + 0); + if (!ret) + break; + + num_type_match++; + last_mismatch = msg; } if (domain_id) { @@ -316,21 +330,49 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, domain_id); } -static int __maybe_unused matches_log_domain_deallocated( - int audit_fd, unsigned int num_denials, __u64 *domain_id) +/* + * 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. + */ +static int __maybe_unused +matches_log_domain_deallocated(int audit_fd, unsigned int num_denials, + __u64 expected_domain_id, __u64 *domain_id) { static const char log_template[] = REGEX_LANDLOCK_PREFIX " status=deallocated denials=%u$"; - char log_match[sizeof(log_template) + 10]; - int log_match_len; + static const char log_template_with_id[] = + "^audit([0-9.:]\\+): domain=\\(%llx\\)" + " status=deallocated denials=%u$"; + char log_match[sizeof(log_template_with_id) + 32]; + int log_match_len, err; + + if (expected_domain_id) + log_match_len = snprintf(log_match, sizeof(log_match), + log_template_with_id, + (unsigned long long)expected_domain_id, + num_denials); + else + log_match_len = snprintf(log_match, sizeof(log_match), + log_template, num_denials); - log_match_len = snprintf(log_match, sizeof(log_match), log_template, - num_denials); if (log_match_len >= sizeof(log_match)) return -E2BIG; - return 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_dom_drop, sizeof(audit_tv_dom_drop)); + + 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)); + + return err; } struct audit_records { diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c index c697e22d8f68..93ae5bd0dcce 100644 --- a/tools/testing/selftests/landlock/audit_test.c +++ b/tools/testing/selftests/landlock/audit_test.c @@ -139,23 +139,24 @@ TEST_F(audit, layers) WEXITSTATUS(status) != EXIT_SUCCESS) _metadata->exit_code = KSFT_FAIL; - /* Purges log from deallocated domains. */ - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_dom_drop, sizeof(audit_tv_dom_drop))); + /* + * Purges log from deallocated domains. Records arrive in LIFO order + * (innermost domain first) because landlock_put_hierarchy() walks the + * chain sequentially in a single kworker context. + */ for (i = ARRAY_SIZE(*domain_stack) - 1; i >= 0; i--) { __u64 deallocated_dom = 2; EXPECT_EQ(0, matches_log_domain_deallocated(self->audit_fd, 1, + (*domain_stack)[i], &deallocated_dom)); EXPECT_EQ((*domain_stack)[i], deallocated_dom) { TH_LOG("Failed to match domain %llx (#%d)", - (*domain_stack)[i], i); + (unsigned long long)(*domain_stack)[i], i); } } EXPECT_EQ(0, munmap(domain_stack, sizeof(*domain_stack))); - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_default, sizeof(audit_tv_default))); EXPECT_EQ(0, close(ruleset_fd)); } @@ -271,13 +272,9 @@ TEST_F(audit, thread) EXPECT_EQ(0, close(pipe_parent[1])); ASSERT_EQ(0, pthread_join(thread, NULL)); - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_dom_drop, sizeof(audit_tv_dom_drop))); - EXPECT_EQ(0, matches_log_domain_deallocated(self->audit_fd, 1, - &deallocated_dom)); + EXPECT_EQ(0, matches_log_domain_deallocated( + self->audit_fd, 1, denial_dom, &deallocated_dom)); EXPECT_EQ(denial_dom, deallocated_dom); - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_default, sizeof(audit_tv_default))); } /* @@ -753,22 +750,21 @@ TEST_F(audit_flags, signal) if (variant->restrict_flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) { + /* + * No deallocation record: denials=0 never matches a real + * record. + */ EXPECT_EQ(-EAGAIN, - matches_log_domain_deallocated(self->audit_fd, 0, + matches_log_domain_deallocated(self->audit_fd, 0, 0, &deallocated_dom)); EXPECT_EQ(deallocated_dom, 2); } else { - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_dom_drop, - sizeof(audit_tv_dom_drop))); EXPECT_EQ(0, matches_log_domain_deallocated(self->audit_fd, 2, + *self->domain_id, &deallocated_dom)); EXPECT_NE(deallocated_dom, 2); EXPECT_NE(deallocated_dom, 0); EXPECT_EQ(deallocated_dom, *self->domain_id); - EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, - &audit_tv_default, - sizeof(audit_tv_default))); } } -- cgit v1.2.3 From a060ac0b8c3345639f5f4a01e2c435d34adf7e3d Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Thu, 2 Apr 2026 21:26:06 +0200 Subject: selftests/landlock: Fix format warning for __u64 in net_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On architectures where __u64 is unsigned long (e.g. powerpc64), using %llx to format a __u64 triggers a -Wformat warning because %llx expects unsigned long long. Cast the argument to unsigned long long. Cc: Günther Noack Cc: stable@vger.kernel.org Fixes: a549d055a22e ("selftests/landlock: Add network tests") Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202604020206.62zgOTeP-lkp@intel.com/ Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20260402192608.1458252-6-mic@digikod.net Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/net_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index b34b139b3f89..4c528154ea92 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -1356,7 +1356,7 @@ TEST_F(mini, network_access_rights) &net_port, 0)) { TH_LOG("Failed to add rule with access 0x%llx: %s", - access, strerror(errno)); + (unsigned long long)access, strerror(errno)); } } EXPECT_EQ(0, close(ruleset_fd)); -- cgit v1.2.3 From ae97330d1bd6a97646c2842d117577236cb40913 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:29 +0100 Subject: landlock: Control pathname UNIX domain socket resolution by path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which controls the lookup operations for named UNIX domain sockets. The resolution happens during connect() and sendmsg() (depending on socket type). * Change access_mask_t from u16 to u32 (see below) * Hook into the path lookup in unix_find_bsd() in af_unix.c, using a LSM hook. Make policy decisions based on the new access rights * Increment the Landlock ABI version. * Minor test adaptations to keep the tests working. * Document the design rationale for scoped access rights, and cross-reference it from the header documentation. With this access right, access is granted if either of the following conditions is met: * The target socket's filesystem path was allow-listed using a LANDLOCK_RULE_PATH_BENEATH rule, *or*: * The target socket was created in the same Landlock domain in which LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted. In case of a denial, connect() and sendmsg() return EACCES, which is the same error as it is returned if the user does not have the write bit in the traditional UNIX file system permissions of that file. The access_mask_t type grows from u16 to u32 to make space for the new access right. This also doubles the size of struct layer_access_masks from 32 byte to 64 byte. To avoid memory layout inconsistencies between architectures (especially m68k), pack and align struct access_masks [2]. Document the (possible future) interaction between scoped flags and other access rights in struct landlock_ruleset_attr, and summarize the rationale, as discussed in code review leading up to [3]. This feature was created with substantial discussion and input from Justin Suess, Tingmao Wang and Mickaël Salaün. Cc: Tingmao Wang Cc: Justin Suess Cc: Kuniyuki Iwashima Suggested-by: Jann Horn Link[1]: https://github.com/landlock-lsm/linux/issues/36 Link[2]: https://lore.kernel.org/all/20260401.Re1Eesu1Yaij@digikod.net/ Link[3]: https://lore.kernel.org/all/20260205.8531e4005118@gnoack.org/ Signed-off-by: Günther Noack Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260327164838.38231-5-gnoack3000@gmail.com [mic: Fix kernel-doc formatting, pack and align access_masks] Signed-off-by: Mickaël Salaün --- Documentation/security/landlock.rst | 42 ++++++++- include/uapi/linux/landlock.h | 21 +++++ security/landlock/access.h | 4 +- security/landlock/audit.c | 1 + security/landlock/fs.c | 130 ++++++++++++++++++++++++++- security/landlock/limits.h | 2 +- security/landlock/syscalls.c | 2 +- tools/testing/selftests/landlock/base_test.c | 2 +- tools/testing/selftests/landlock/fs_test.c | 5 +- 9 files changed, 200 insertions(+), 9 deletions(-) (limited to 'tools/testing') diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst index 3e4d4d04cfae..c5186526e76f 100644 --- a/Documentation/security/landlock.rst +++ b/Documentation/security/landlock.rst @@ -7,7 +7,7 @@ Landlock LSM: kernel documentation ================================== :Author: Mickaël Salaün -:Date: September 2025 +:Date: March 2026 Landlock's goal is to create scoped access-control (i.e. sandboxing). To harden a whole system, this feature should be available to any process, @@ -89,6 +89,46 @@ this is required to keep access controls consistent over the whole system, and this avoids unattended bypasses through file descriptor passing (i.e. confused deputy attack). +.. _scoped-flags-interaction: + +Interaction between scoped flags and other access rights +-------------------------------------------------------- + +The ``scoped`` flags in &struct landlock_ruleset_attr restrict the +use of *outgoing* IPC from the created Landlock domain, while they +permit reaching out to IPC endpoints *within* the created Landlock +domain. + +In the future, scoped flags *may* interact with other access rights, +e.g. so that abstract UNIX sockets can be allow-listed by name, or so +that signals can be allow-listed by signal number or target process. + +When introducing ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``, we defined it to +implicitly have the same scoping semantics as a +``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` flag would have: connecting to +UNIX sockets within the same domain (where +``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` is used) is unconditionally +allowed. + +The reasoning is: + +* Like other IPC mechanisms, connecting to named UNIX sockets in the + same domain should be expected and harmless. (If needed, users can + further refine their Landlock policies with nested domains or by + restricting ``LANDLOCK_ACCESS_FS_MAKE_SOCK``.) +* We reserve the option to still introduce + ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the future. (This would + be useful if we wanted to have a Landlock rule to permit IPC access + to other Landlock domains.) +* But we can postpone the point in time when users have to deal with + two interacting flags visible in the userspace API. (In particular, + it is possible that it won't be needed in practice, in which case we + can avoid the second flag altogether.) +* If we *do* introduce ``LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET`` in the + future, setting this scoped flag in a ruleset does *not reduce* the + restrictions, because access within the same scope is already + allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``. + Tests ===== diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index d37603efc273..10a346e55e95 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -250,6 +250,26 @@ struct landlock_net_port_attr { * * This access right is available since the fifth version of the Landlock * ABI. + * - %LANDLOCK_ACCESS_FS_RESOLVE_UNIX: Look up pathname UNIX domain sockets + * (:manpage:`unix(7)`). On UNIX domain sockets, this restricts both calls to + * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an + * explicit recipient address. + * + * This access right only applies to connections to UNIX server sockets which + * were created outside of the newly created Landlock domain (e.g. from within + * a parent domain or from an unrestricted process). Newly created UNIX + * servers within the same Landlock domain continue to be accessible. In this + * regard, %LANDLOCK_ACCESS_FS_RESOLVE_UNIX has the same semantics as the + * ``LANDLOCK_SCOPE_*`` flags. + * + * If a resolve attempt is denied, the operation returns an ``EACCES`` error, + * in line with other filesystem access rights (but different to denials for + * abstract UNIX domain sockets). + * + * This access right is available since the ninth version of the Landlock ABI. + * + * The rationale for this design is described in + * :ref:`Documentation/security/landlock.rst `. * * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as @@ -335,6 +355,7 @@ struct landlock_net_port_attr { #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) #define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15) +#define LANDLOCK_ACCESS_FS_RESOLVE_UNIX (1ULL << 16) /* clang-format on */ /** diff --git a/security/landlock/access.h b/security/landlock/access.h index 42c95747d7bd..c19d5bc13944 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -34,7 +34,7 @@ LANDLOCK_ACCESS_FS_IOCTL_DEV) /* clang-format on */ -typedef u16 access_mask_t; +typedef u32 access_mask_t; /* Makes sure all filesystem access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); @@ -50,7 +50,7 @@ struct access_masks { access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; access_mask_t net : LANDLOCK_NUM_ACCESS_NET; access_mask_t scope : LANDLOCK_NUM_SCOPE; -}; +} __packed __aligned(sizeof(u32)); union access_masks_all { struct access_masks masks; diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 60ff217ab95b..8d0edf94037d 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = { [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", + [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix", }; static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 97065d51685a..fcf69b3d734d 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include @@ -314,7 +316,8 @@ retry: LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_TRUNCATE | \ - LANDLOCK_ACCESS_FS_IOCTL_DEV) + LANDLOCK_ACCESS_FS_IOCTL_DEV | \ + LANDLOCK_ACCESS_FS_RESOLVE_UNIX) /* clang-format on */ /* @@ -1557,6 +1560,130 @@ static int hook_path_truncate(const struct path *const path) return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); } +/** + * unmask_scoped_access - Remove access right bits in @masks in all layers + * where @client and @server have the same domain + * + * This does the same as domain_is_scoped(), but unmasks bits in @masks. + * It can not return early as domain_is_scoped() does. + * + * A scoped access for a given access right bit is allowed iff, for all layer + * depths where the access bit is set, the client and server domain are the + * same. This function clears the access rights @access in @masks at all layer + * depths where the client and server domain are the same, so that, when they + * are all cleared, the access is allowed. + * + * @client: Client domain + * @server: Server domain + * @masks: Layer access masks to unmask + * @access: Access bits that control scoping + */ +static void unmask_scoped_access(const struct landlock_ruleset *const client, + const struct landlock_ruleset *const server, + struct layer_access_masks *const masks, + const access_mask_t access) +{ + int client_layer, server_layer; + const struct landlock_hierarchy *client_walker, *server_walker; + + /* This should not happen. */ + if (WARN_ON_ONCE(!client)) + return; + + /* Server has no Landlock domain; nothing to clear. */ + if (!server) + return; + + /* + * client_layer must be a signed integer with greater capacity + * than client->num_layers to ensure the following loop stops. + */ + BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); + + client_layer = client->num_layers - 1; + client_walker = client->hierarchy; + server_layer = server->num_layers - 1; + server_walker = server->hierarchy; + + /* + * Clears the access bits at all layers where the client domain is the + * same as the server domain. We start the walk at min(client_layer, + * server_layer). The layer bits until there can not be cleared because + * either the client or the server domain is missing. + */ + for (; client_layer > server_layer; client_layer--) + client_walker = client_walker->parent; + + for (; server_layer > client_layer; server_layer--) + server_walker = server_walker->parent; + + for (; client_layer >= 0; client_layer--) { + if (masks->access[client_layer] & access && + client_walker == server_walker) + masks->access[client_layer] &= ~access; + + client_walker = client_walker->parent; + server_walker = server_walker->parent; + } +} + +static int hook_unix_find(const struct path *const path, struct sock *other, + int flags) +{ + const struct landlock_ruleset *dom_other; + const struct landlock_cred_security *subject; + struct layer_access_masks layer_masks; + struct landlock_request request = {}; + static const struct access_masks fs_resolve_unix = { + .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + }; + + /* Lookup for the purpose of saving coredumps is OK. */ + if (unlikely(flags & SOCK_COREDUMP)) + return 0; + + subject = landlock_get_applicable_subject(current_cred(), + fs_resolve_unix, NULL); + + if (!subject) + return 0; + + /* + * Ignoring return value: that the domains apply was already checked in + * landlock_get_applicable_subject() above. + */ + landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs, + &layer_masks, LANDLOCK_KEY_INODE); + + /* Checks the layers in which we are connecting within the same domain. */ + unix_state_lock(other); + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket || + !other->sk_socket->file)) { + unix_state_unlock(other); + /* + * We rely on the caller to catch the (non-reversible) SOCK_DEAD + * condition and retry the lookup. If we returned an error + * here, the lookup would not get retried. + */ + return 0; + } + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; + + /* Access to the same (or a lower) domain is always allowed. */ + unmask_scoped_access(subject->domain, dom_other, &layer_masks, + fs_resolve_unix.fs); + unix_state_unlock(other); + + /* Checks the connections to allow-listed paths. */ + if (is_access_to_paths_allowed(subject->domain, path, + fs_resolve_unix.fs, &layer_masks, + &request, NULL, 0, NULL, NULL, NULL)) + return 0; + + landlock_log_denial(subject, &request); + return -EACCES; +} + /* File hooks */ /** @@ -1834,6 +1961,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(path_unlink, hook_path_unlink), LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), LSM_HOOK_INIT(path_truncate, hook_path_truncate), + LSM_HOOK_INIT(unix_find, hook_unix_find), LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security), LSM_HOOK_INIT(file_open, hook_file_open), diff --git a/security/landlock/limits.h b/security/landlock/limits.h index eb584f47288d..b454ad73b15e 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -19,7 +19,7 @@ #define LANDLOCK_MAX_NUM_LAYERS 16 #define LANDLOCK_MAX_NUM_RULES U32_MAX -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 5ebd606e84e6..accfd2e5a0cd 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = { * If the change involves a fix that requires userspace awareness, also update * the errata documentation in Documentation/userspace-api/landlock.rst . */ -const int landlock_abi_version = 8; +const int landlock_abi_version = 9; /** * sys_landlock_create_ruleset - Create a new ruleset diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 0fea236ef4bd..30d37234086c 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(8, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(9, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 968a91c927a4..b318627e7561 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -575,9 +575,10 @@ TEST_F_FORK(layout1, inval) LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_TRUNCATE | \ - LANDLOCK_ACCESS_FS_IOCTL_DEV) + LANDLOCK_ACCESS_FS_IOCTL_DEV | \ + LANDLOCK_ACCESS_FS_RESOLVE_UNIX) -#define ACCESS_LAST LANDLOCK_ACCESS_FS_IOCTL_DEV +#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX #define ACCESS_ALL ( \ ACCESS_FILE | \ -- cgit v1.2.3 From db8201a3fae2ca7a2865dbb9e8955289776783c7 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:32 +0100 Subject: selftests/landlock: Replace access_fs_16 with ACCESS_ALL in fs_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The access_fs_16 variable was originally intended to stay frozen at 16 access rights so that audit tests would not need updating when new access rights are added. Now that we have 17 access rights, the name is confusing. Replace all uses of access_fs_16 with ACCESS_ALL and delete the variable. Suggested-by: Mickaël Salaün Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260327164838.38231-8-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/fs_test.c | 54 ++++++++++-------------------- 1 file changed, 17 insertions(+), 37 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index b318627e7561..9fdd3b8f7b11 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -7161,26 +7161,6 @@ TEST_F(audit_layout1, execute_make) * only the blocked ones are logged. */ -/* clang-format off */ -static const __u64 access_fs_16 = - LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_WRITE_FILE | - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR | - LANDLOCK_ACCESS_FS_REMOVE_DIR | - LANDLOCK_ACCESS_FS_REMOVE_FILE | - LANDLOCK_ACCESS_FS_MAKE_CHAR | - LANDLOCK_ACCESS_FS_MAKE_DIR | - LANDLOCK_ACCESS_FS_MAKE_REG | - LANDLOCK_ACCESS_FS_MAKE_SOCK | - LANDLOCK_ACCESS_FS_MAKE_FIFO | - LANDLOCK_ACCESS_FS_MAKE_BLOCK | - LANDLOCK_ACCESS_FS_MAKE_SYM | - LANDLOCK_ACCESS_FS_REFER | - LANDLOCK_ACCESS_FS_TRUNCATE | - LANDLOCK_ACCESS_FS_IOCTL_DEV; -/* clang-format on */ - TEST_F(audit_layout1, execute_read) { struct audit_records records; @@ -7190,7 +7170,7 @@ TEST_F(audit_layout1, execute_read) test_check_exec(_metadata, 0, file1_s1d1); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); /* @@ -7214,7 +7194,7 @@ TEST_F(audit_layout1, write_file) struct audit_records records; drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); @@ -7231,7 +7211,7 @@ TEST_F(audit_layout1, read_file) struct audit_records records; drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -7248,7 +7228,7 @@ TEST_F(audit_layout1, read_dir) struct audit_records records; drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(EACCES, test_open(dir_s1d1, O_DIRECTORY)); @@ -7268,7 +7248,7 @@ TEST_F(audit_layout1, remove_dir) EXPECT_EQ(0, unlink(file2_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, rmdir(dir_s1d3)); @@ -7291,7 +7271,7 @@ TEST_F(audit_layout1, remove_file) struct audit_records records; drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, unlink(file1_s1d3)); @@ -7311,7 +7291,7 @@ TEST_F(audit_layout1, make_char) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0)); @@ -7331,7 +7311,7 @@ TEST_F(audit_layout1, make_dir) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mkdir(file1_s1d3, 0755)); @@ -7351,7 +7331,7 @@ TEST_F(audit_layout1, make_reg) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFREG | 0644, 0)); @@ -7371,7 +7351,7 @@ TEST_F(audit_layout1, make_sock) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFSOCK | 0644, 0)); @@ -7391,7 +7371,7 @@ TEST_F(audit_layout1, make_fifo) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFIFO | 0644, 0)); @@ -7411,7 +7391,7 @@ TEST_F(audit_layout1, make_block) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFBLK | 0644, 0)); @@ -7431,7 +7411,7 @@ TEST_F(audit_layout1, make_sym) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, symlink("target", file1_s1d3)); @@ -7501,7 +7481,7 @@ TEST_F(audit_layout1, refer_rename) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(EACCES, test_rename(file1_s1d2, file1_s2d3)); @@ -7523,7 +7503,7 @@ TEST_F(audit_layout1, refer_exchange) EXPECT_EQ(0, unlink(file1_s1d3)); drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); /* @@ -7586,7 +7566,7 @@ TEST_F(audit_layout1, truncate) struct audit_records records; drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = access_fs_16, + .handled_access_fs = ACCESS_ALL, }); EXPECT_EQ(-1, truncate(file1_s1d3, 0)); @@ -7607,7 +7587,7 @@ TEST_F(audit_layout1, ioctl_dev) drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ .handled_access_fs = - access_fs_16 & + ACCESS_ALL & ~LANDLOCK_ACCESS_FS_READ_FILE, }); -- cgit v1.2.3 From 9da41c65c907329a1848418cdc11fb10cc341217 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:33 +0100 Subject: selftests/landlock: Test LANDLOCK_ACCESS_FS_RESOLVE_UNIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Extract common helpers from an existing IOCTL test that also uses pathname unix(7) sockets. * These tests use the common scoped domains fixture which is also used in other Landlock scoping tests and which was used in Tingmao Wang's earlier patch set in [1]. These tests exercise the cross product of the following scenarios: * Stream connect(), Datagram connect(), Datagram sendmsg() and Seqpacket connect(). * Child-to-parent and parent-to-child communication * The Landlock policy configuration as listed in the scoped_domains fixture. * In the default variant, Landlock domains are only placed where prescribed in the fixture. * In the "ALL_DOMAINS" variant, Landlock domains are also placed in the places where the fixture says to omit them, but with a LANDLOCK_RULE_PATH_BENEATH that allows connection. Cc: Justin Suess Cc: Tingmao Wang Cc: Mickaël Salaün Link[1]: https://lore.kernel.org/all/53b9883648225d5a08e82d2636ab0b4fda003bc9.1767115163.git.m@maowtm.org/ Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260327164838.38231-9-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/fs_test.c | 390 +++++++++++++++++++++++++++-- 1 file changed, 374 insertions(+), 16 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 9fdd3b8f7b11..f8cfd31335e1 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4358,30 +4358,66 @@ TEST_F_FORK(layout1, named_pipe_ioctl) 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); @@ -4393,9 +4429,7 @@ TEST_F_FORK(layout1, named_unix_domain_socket_ioctl) 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)); @@ -4570,6 +4604,330 @@ TEST_F_FORK(ioctl, handle_file_access_file) 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 */ -- cgit v1.2.3 From 0f42f5be0b21c625ca52b9df96f452153aea05a8 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:34 +0100 Subject: selftests/landlock: Audit test for LANDLOCK_ACCESS_FS_RESOLVE_UNIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an audit test to check that Landlock denials from LANDLOCK_ACCESS_FS_RESOLVE_UNIX result in audit logs in the expected format. (There is one audit test for each filesystem access right, so we should add one for LANDLOCK_ACCESS_FS_RESOLVE_UNIX as well.) Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260327164838.38231-10-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/fs_test.c | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index f8cfd31335e1..3dad643741f7 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -7961,6 +7961,46 @@ TEST_F(audit_layout1, ioctl_dev) EXPECT_EQ(1, records.domain); } +TEST_F(audit_layout1, resolve_unix) +{ + struct audit_records records; + const char *const path = "sock"; + int srv_fd, cli_fd, status; + pid_t child_pid; + + srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path); + + child_pid = fork(); + ASSERT_LE(0, child_pid); + if (!child_pid) { + drop_access_rights(_metadata, + &(struct landlock_ruleset_attr){ + .handled_access_fs = ACCESS_ALL, + }); + + cli_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, cli_fd); + EXPECT_EQ(EACCES, + test_connect_named_unix(_metadata, cli_fd, path)); + + EXPECT_EQ(0, close(cli_fd)); + _exit(_metadata->exit_code); + } + + ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd, + "fs\\.resolve_unix", path, NULL)); + + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + EXPECT_EQ(1, records.domain); + + EXPECT_EQ(0, close(srv_fd)); +} + TEST_F(audit_layout1, mount) { struct audit_records records; -- cgit v1.2.3 From f433fd3fa275e52fc1c7107e8aa57f1d037ee341 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:35 +0100 Subject: selftests/landlock: Check that coredump sockets stay unrestricted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even when a process is restricted with the new LANDLOCK_ACCESS_FS_RESOLVE_UNIX right, the kernel can continue writing its coredump to the configured coredump socket. In the test, we create a local server and rewire the system to write coredumps into it. We then create a child process within a Landlock domain where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is restricted and make the process crash. The test uses SO_PEERCRED to check that the connecting client process is the expected one. Includes a fix by Mickaël Salaün for setting the EUID to 0 (see [1]). Link[1]: https://lore.kernel.org/all/20260218.ohth8theu8Yi@digikod.net/ Suggested-by: Mickaël Salaün Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260327164838.38231-11-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/fs_test.c | 143 +++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 3dad643741f7..af0f0b16129a 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -4928,6 +4929,148 @@ TEST_F(scoped_domains, unix_seqpacket_connect_to_child_full) #undef USE_SENDTO #undef ENFORCE_ALL +static void read_core_pattern(struct __test_metadata *const _metadata, + char *buf, size_t buf_size) +{ + int fd; + ssize_t ret; + + fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC); + ASSERT_LE(0, fd); + + ret = read(fd, buf, buf_size - 1); + ASSERT_LE(0, ret); + EXPECT_EQ(0, close(fd)); + + buf[ret] = '\0'; +} + +static void set_core_pattern(struct __test_metadata *const _metadata, + const char *pattern) +{ + int fd; + size_t len = strlen(pattern); + + /* + * Writing to /proc/sys/kernel/core_pattern requires EUID 0 because + * sysctl_perm() checks that, ignoring capabilities like + * CAP_SYS_ADMIN or CAP_DAC_OVERRIDE. + * + * Switching EUID clears the dumpable flag, which must be restored + * afterwards to allow coredumps. + */ + set_cap(_metadata, CAP_SETUID); + ASSERT_EQ(0, seteuid(0)); + clear_cap(_metadata, CAP_SETUID); + + fd = open("/proc/sys/kernel/core_pattern", O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, fd) + { + TH_LOG("Failed to open core_pattern for writing: %s", + strerror(errno)); + } + + ASSERT_EQ(len, write(fd, pattern, len)); + EXPECT_EQ(0, close(fd)); + + set_cap(_metadata, CAP_SETUID); + ASSERT_EQ(0, seteuid(getuid())); + clear_cap(_metadata, CAP_SETUID); + + /* Restore dumpable flag cleared by seteuid(). */ + ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)); +} + +FIXTURE(coredump) +{ + char original_core_pattern[256]; +}; + +FIXTURE_SETUP(coredump) +{ + disable_caps(_metadata); + read_core_pattern(_metadata, self->original_core_pattern, + sizeof(self->original_core_pattern)); +} + +FIXTURE_TEARDOWN_PARENT(coredump) +{ + set_core_pattern(_metadata, self->original_core_pattern); +} + +/* + * Test that even when a process is restricted with + * LANDLOCK_ACCESS_FS_RESOLVE_UNIX, the kernel can still initiate a connection + * to the coredump socket on the processes' behalf. + */ +TEST_F_FORK(coredump, socket_not_restricted) +{ + static const char core_pattern[] = "@/tmp/landlock_coredump_test.sock"; + const char *const sock_path = core_pattern + 1; + int srv_fd, conn_fd, status; + pid_t child_pid; + struct ucred cred; + socklen_t cred_len = sizeof(cred); + char buf[4096]; + + /* Set up the coredump server socket. */ + unlink(sock_path); + srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, sock_path); + + /* Point coredumps at our socket. */ + set_core_pattern(_metadata, core_pattern); + + /* Restrict LANDLOCK_ACCESS_FS_RESOLVE_UNIX. */ + drop_access_rights(_metadata, + &(struct landlock_ruleset_attr){ + .handled_access_fs = + LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + }); + + /* Fork a child that crashes. */ + child_pid = fork(); + ASSERT_LE(0, child_pid); + if (child_pid == 0) { + struct rlimit rl = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + + ASSERT_EQ(0, setrlimit(RLIMIT_CORE, &rl)); + + /* Crash on purpose. */ + kill(getpid(), SIGSEGV); + _exit(1); + } + + /* + * Accept the coredump connection. If Landlock incorrectly denies the + * kernel's coredump connect, accept() will block forever, so the test + * would time out. + */ + conn_fd = accept(srv_fd, NULL, NULL); + ASSERT_LE(0, conn_fd); + + /* Check that the connection came from the crashing child. */ + ASSERT_EQ(0, getsockopt(conn_fd, SOL_SOCKET, SO_PEERCRED, &cred, + &cred_len)); + EXPECT_EQ(child_pid, cred.pid); + + /* Drain the coredump data so the kernel can finish. */ + while (read(conn_fd, buf, sizeof(buf)) > 0) + ; + + EXPECT_EQ(0, close(conn_fd)); + + /* Wait for the child and verify it coredumped. */ + ASSERT_EQ(child_pid, waitpid(child_pid, &status, 0)); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_TRUE(WCOREDUMP(status)); + + EXPECT_EQ(0, close(srv_fd)); + EXPECT_EQ(0, unlink(sock_path)); +} + /* clang-format off */ FIXTURE(layout1_bind) {}; /* clang-format on */ -- cgit v1.2.3 From dc75f890469401816fc8c492e11885409b5efd12 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Fri, 27 Mar 2026 17:48:36 +0100 Subject: selftests/landlock: Simplify ruleset creation and enforcement in fs_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add enforce_fs() for defining and enforcing a ruleset in one step * In some places, dropped "ASSERT_LE(0, fd)" checks after create_ruleset() call -- create_ruleset() already checks that. * In some places, rename "file_fd" to "fd" if it is not needed to disambiguate any more. Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260327164838.38231-12-gnoack3000@gmail.com [mic: Tweak subjet] Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/fs_test.c | 821 ++++++++--------------------- 1 file changed, 210 insertions(+), 611 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index af0f0b16129a..cdb47fc1fc0a 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -767,15 +767,6 @@ static int create_ruleset(struct __test_metadata *const _metadata, .handled_access_fs = handled_access_fs, }; - ASSERT_NE(NULL, rules) - { - TH_LOG("No rule list"); - } - ASSERT_NE(NULL, rules[0].path) - { - TH_LOG("Empty rule list"); - } - ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd) @@ -783,16 +774,26 @@ static int create_ruleset(struct __test_metadata *const _metadata, TH_LOG("Failed to create a ruleset: %s", strerror(errno)); } - for (i = 0; rules[i].path; i++) { - if (!rules[i].access) - continue; + if (rules) + for (i = 0; rules[i].path; i++) { + if (!rules[i].access) + continue; - add_path_beneath(_metadata, ruleset_fd, rules[i].access, - rules[i].path); - } + add_path_beneath(_metadata, ruleset_fd, rules[i].access, + rules[i].path); + } return ruleset_fd; } +static void enforce_fs(struct __test_metadata *const _metadata, + const __u64 access_fs, const struct rule rules[]) +{ + const int ruleset_fd = create_ruleset(_metadata, access_fs, rules); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); +} + TEST_F_FORK(layout0, proc_nsfs) { const struct rule rules[] = { @@ -879,13 +880,10 @@ TEST_F_FORK(layout1, effective_access) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); char buf; int reg_fd; - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); /* Tests on a directory (with or without O_PATH). */ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); @@ -932,12 +930,9 @@ TEST_F_FORK(layout1, unhandled_access) }, {}, }; - /* Here, we only handle read accesses, not write accesses. */ - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RO, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + /* Here, we only handle read accesses, not write accesses. */ + enforce_fs(_metadata, ACCESS_RO, rules); /* * Because the policy does not handle LANDLOCK_ACCESS_FS_WRITE_FILE, @@ -966,11 +961,8 @@ TEST_F_FORK(layout1, ruleset_overlap) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); /* Checks s1d1 hierarchy. */ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1022,11 +1014,8 @@ TEST_F_FORK(layout1, layer_rule_unions) }, {}, }; - int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer1); /* Checks s1d1 hierarchy with layer1. */ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1048,10 +1037,7 @@ TEST_F_FORK(layout1, layer_rule_unions) ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); /* Doesn't change anything from layer1. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer2); /* Checks s1d1 hierarchy with layer2. */ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1073,10 +1059,7 @@ TEST_F_FORK(layout1, layer_rule_unions) ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY | O_DIRECTORY)); /* Only allows write (but not read) to dir_s1d3. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer3); /* Checks s1d1 hierarchy with layer3. */ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -1114,27 +1097,18 @@ TEST_F_FORK(layout1, non_overlapping_accesses) }, {}, }; - int ruleset_fd; ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); - ruleset_fd = - create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_REG, layer1); ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); ASSERT_EQ(EACCES, errno); ASSERT_EQ(0, mknod(file1_s1d2, S_IFREG | 0700, 0)); ASSERT_EQ(0, unlink(file1_s1d2)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, - layer2); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REMOVE_FILE, layer2); /* Unchanged accesses for file creation. */ ASSERT_EQ(-1, mknod(file1_s1d1, S_IFREG | 0700, 0)); @@ -1238,37 +1212,24 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) }, {}, }; - int ruleset_fd; - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer1_read); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer1_read); /* Checks that read access is granted for file1_s1d3 with layer 1. */ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); - ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, - layer2_read_write); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + layer2_read_write); /* Checks that previous access rights are unchanged with layer 2. */ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer3_read); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer3_read); /* Checks that previous access rights are unchanged with layer 3. */ ASSERT_EQ(0, test_open(file1_s1d3, O_RDWR)); @@ -1276,13 +1237,9 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(0, test_open(file2_s1d3, O_WRONLY)); /* This time, denies write access for the file hierarchy. */ - ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, - layer4_read_write); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + layer4_read_write); /* * Checks that the only change with layer 4 is that write access is @@ -1293,11 +1250,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer5_read); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer5_read); /* Checks that previous access rights are unchanged with layer 5. */ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); @@ -1305,11 +1258,7 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, - layer6_execute); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, layer6_execute); /* Checks that previous access rights are unchanged with layer 6. */ ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); @@ -1317,13 +1266,9 @@ TEST_F_FORK(layout1, interleaved_masked_accesses) ASSERT_EQ(EACCES, test_open(file2_s1d3, O_WRONLY)); ASSERT_EQ(EACCES, test_open(file2_s1d3, O_RDONLY)); - ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE, - layer7_read_write); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + layer7_read_write); /* Checks read access is now denied with layer 7. */ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); @@ -1344,7 +1289,6 @@ TEST_F_FORK(layout1, inherit_subset) }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); @@ -1460,7 +1404,6 @@ TEST_F_FORK(layout1, inherit_superset) }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); /* Readdir access is denied for dir_s1d2. */ @@ -1476,7 +1419,7 @@ TEST_F_FORK(layout1, inherit_superset) LANDLOCK_ACCESS_FS_READ_DIR, dir_s1d2); enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + EXPECT_EQ(0, close(ruleset_fd)); /* Readdir access is still denied for dir_s1d2. */ ASSERT_EQ(EACCES, test_open(dir_s1d2, O_RDONLY | O_DIRECTORY)); @@ -1498,7 +1441,6 @@ TEST_F_FORK(layout0, max_layers) }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); for (i = 0; i < 16; i++) enforce_ruleset(_metadata, ruleset_fd); @@ -1507,7 +1449,7 @@ TEST_F_FORK(layout0, max_layers) ASSERT_EQ(-1, err); ASSERT_EQ(E2BIG, errno); } - ASSERT_EQ(0, close(ruleset_fd)); + EXPECT_EQ(0, close(ruleset_fd)); } TEST_F_FORK(layout1, empty_or_same_ruleset) @@ -1521,20 +1463,15 @@ TEST_F_FORK(layout1, empty_or_same_ruleset) ASSERT_LE(-1, ruleset_fd); ASSERT_EQ(ENOMSG, errno); - /* Enforces policy which deny read access to all files. */ - ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE; - ruleset_fd = - landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); + /* Enforces policy which denies read access to all files. */ + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, NULL); + ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); - /* Nests a policy which deny read access to all directories. */ - ruleset_attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR; + /* Nests a policy which denies read access to all directories. */ ruleset_fd = - landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - ASSERT_LE(0, ruleset_fd); + create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, NULL); enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); ASSERT_EQ(EACCES, test_open(dir_s1d1, O_RDONLY)); @@ -1558,11 +1495,8 @@ TEST_F_FORK(layout1, rule_on_mountpoint) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); @@ -1587,11 +1521,8 @@ TEST_F_FORK(layout1, rule_over_mountpoint) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); @@ -1615,21 +1546,15 @@ TEST_F_FORK(layout1, rule_over_root_allow_then_deny) }, {}, }; - int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); /* Checks allowed access. */ ASSERT_EQ(0, test_open("/", O_RDONLY)); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); rules[0].access = LANDLOCK_ACCESS_FS_READ_FILE; - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); /* Checks denied access (on a directory). */ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); @@ -1645,11 +1570,8 @@ TEST_F_FORK(layout1, rule_over_root_deny) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); /* Checks denied access (on a directory). */ ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); @@ -1665,7 +1587,6 @@ TEST_F_FORK(layout1, rule_inside_mount_ns) }, {}, }; - int ruleset_fd; set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, syscall(__NR_pivot_root, dir_s3d2, dir_s3d3)) @@ -1675,10 +1596,7 @@ TEST_F_FORK(layout1, rule_inside_mount_ns) ASSERT_EQ(0, chdir("/")); clear_cap(_metadata, CAP_SYS_ADMIN); - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); ASSERT_EQ(0, test_open("s3d3", O_RDONLY)); ASSERT_EQ(EACCES, test_open("/", O_RDONLY)); @@ -1693,11 +1611,8 @@ TEST_F_FORK(layout1, mount_and_pivot) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); @@ -1716,9 +1631,6 @@ TEST_F_FORK(layout1, move_mount) }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - - ASSERT_LE(0, ruleset_fd); set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, @@ -1731,8 +1643,7 @@ TEST_F_FORK(layout1, move_mount) dir_s3d2, 0)); clear_cap(_metadata, CAP_SYS_ADMIN); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, rules); set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(-1, syscall(__NR_move_mount, AT_FDCWD, dir_s3d2, AT_FDCWD, @@ -1747,14 +1658,9 @@ TEST_F_FORK(layout1, topology_changes_with_net_only) .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, }; - int ruleset_fd; /* Add network restrictions. */ - ruleset_fd = - landlock_create_ruleset(&ruleset_net, sizeof(ruleset_net), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + drop_access_rights(_metadata, &ruleset_net); /* Mount, remount, move_mount, umount, and pivot_root checks. */ set_cap(_metadata, CAP_SYS_ADMIN); @@ -1775,14 +1681,9 @@ TEST_F_FORK(layout1, topology_changes_with_net_and_fs) LANDLOCK_ACCESS_NET_CONNECT_TCP, .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, }; - int ruleset_fd; /* Add network and filesystem restrictions. */ - ruleset_fd = landlock_create_ruleset(&ruleset_net_fs, - sizeof(ruleset_net_fs), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + drop_access_rights(_metadata, &ruleset_net_fs); /* Mount, remount, move_mount, umount, and pivot_root checks. */ set_cap(_metadata, CAP_SYS_ADMIN); @@ -1819,14 +1720,13 @@ TEST_F_FORK(layout1, release_inodes) }; const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); - ASSERT_LE(0, ruleset_fd); /* Unmount a file hierarchy while it is being used by a ruleset. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, umount(dir_s3d2)); clear_cap(_metadata, CAP_SYS_ADMIN); enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + EXPECT_EQ(0, close(ruleset_fd)); ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); ASSERT_EQ(EACCES, test_open(dir_s3d2, O_RDONLY)); @@ -1858,7 +1758,6 @@ TEST_F_FORK(layout1, covered_rule) /* Creates a ruleset with the future hidden directory. */ ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1); - ASSERT_LE(0, ruleset_fd); /* Covers with a new mount point. */ set_cap(_metadata, CAP_SYS_ADMIN); @@ -1908,10 +1807,7 @@ static void test_relative_path(struct __test_metadata *const _metadata, }; int dirfd, ruleset_fd; - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer1_base); ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_subs); @@ -2092,10 +1988,7 @@ TEST_F_FORK(layout1, execute) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - ASSERT_LE(0, ruleset_fd); copy_file(_metadata, bin_true, file1_s1d1); copy_file(_metadata, bin_true, file1_s1d2); copy_file(_metadata, bin_true, file1_s1d3); @@ -2104,8 +1997,7 @@ TEST_F_FORK(layout1, execute) test_execute(_metadata, 0, file1_s1d1); test_check_exec(_metadata, 0, file1_s1d1); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); @@ -2216,16 +2108,12 @@ TEST_F_FORK(layout1, link) }, {}, }; - int ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); ASSERT_EQ(0, unlink(file1_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, layer1[0].access, layer1); ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1)); ASSERT_EQ(EACCES, errno); @@ -2245,10 +2133,7 @@ TEST_F_FORK(layout1, link) ASSERT_EQ(0, unlink(file2_s1d2)); ASSERT_EQ(0, unlink(file2_s1d3)); - ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, layer2[0].access, layer2); /* Checks that linkind doesn't require the ability to delete a file. */ ASSERT_EQ(0, link(file1_s1d2, file2_s1d2)); @@ -2298,15 +2183,10 @@ TEST_F_FORK(layout1, rename_file) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d2)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); /* * Tries to replace a file, from a directory that allows file removal, @@ -2380,17 +2260,12 @@ TEST_F_FORK(layout1, rename_dir) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - - ASSERT_LE(0, ruleset_fd); /* Empties dir_s1d3 to allow renaming. */ ASSERT_EQ(0, unlink(file1_s1d3)); ASSERT_EQ(0, unlink(file2_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); /* Exchanges and renames directory to a different parent. */ ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3, @@ -2444,12 +2319,8 @@ TEST_F_FORK(layout1, reparent_refer) }, {}, }; - int ruleset_fd = - create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1)); ASSERT_EQ(EXDEV, errno); @@ -2479,14 +2350,9 @@ static void refer_denied_by_default(struct __test_metadata *const _metadata, const int layer1_err, const struct rule layer2[]) { - int ruleset_fd; - ASSERT_EQ(0, unlink(file1_s1d2)); - ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, layer1[0].access, layer1); /* * If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to @@ -2498,10 +2364,7 @@ static void refer_denied_by_default(struct __test_metadata *const _metadata, ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2)); ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1)); - ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, layer2[0].access, layer2); /* * Now, either the first or the second layer does not handle @@ -2587,10 +2450,7 @@ TEST_F_FORK(layout1, refer_denied_by_default4) */ TEST_F_FORK(layout1, refer_mount_root_deny) { - const struct landlock_ruleset_attr ruleset_attr = { - .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_DIR, - }; - int root_fd, ruleset_fd; + int root_fd; /* Creates a mount object from a non-mount point. */ set_cap(_metadata, CAP_SYS_ADMIN); @@ -2600,13 +2460,7 @@ TEST_F_FORK(layout1, refer_mount_root_deny) clear_cap(_metadata, CAP_SYS_ADMIN); ASSERT_LE(0, root_fd); - ruleset_fd = - landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - ASSERT_LE(0, ruleset_fd); - - ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); - ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); - EXPECT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, NULL); /* Link denied by Landlock: EACCES. */ EXPECT_EQ(-1, linkat(root_fd, ".", root_fd, "does_not_exist", 0)); @@ -2641,18 +2495,12 @@ TEST_F_FORK(layout1, refer_part_mount_tree_is_allowed) }, {}, }; - int ruleset_fd; ASSERT_EQ(0, unlink(file1_s3d3)); - ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_REFER | - LANDLOCK_ACCESS_FS_MAKE_REG | - LANDLOCK_ACCESS_FS_REMOVE_FILE, - layer1); - - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_MAKE_REG | + LANDLOCK_ACCESS_FS_REMOVE_FILE, + layer1); ASSERT_EQ(0, rename(file1_s3d4, file1_s3d3)); } @@ -2678,13 +2526,10 @@ TEST_F_FORK(layout1, reparent_link) }, {}, }; - const int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, + layer1); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); @@ -2756,13 +2601,10 @@ TEST_F_FORK(layout1, reparent_rename) }, {}, }; - const int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, + layer1); ASSERT_EQ(0, unlink(file1_s1d2)); ASSERT_EQ(0, unlink(file1_s1d3)); @@ -2902,13 +2744,9 @@ reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata) }, {}, }; - const int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1); - - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, + layer1); } static void @@ -2925,12 +2763,7 @@ reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata) * Same checks as before but with a second layer and a new MAKE_DIR * rule (and no explicit handling of REFER). */ - const int ruleset_fd = - create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2); - - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2); } TEST_F_FORK(layout1, reparent_exdev_layers_rename1) @@ -3199,15 +3032,11 @@ TEST_F_FORK(layout1, reparent_remove) }, {}, }; - const int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR | - LANDLOCK_ACCESS_FS_REMOVE_FILE, - layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR | + LANDLOCK_ACCESS_FS_REMOVE_FILE, + layer1); /* Access denied because of wrong/swapped remove file/dir. */ ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2)); @@ -3271,17 +3100,13 @@ TEST_F_FORK(layout1, reparent_dom_superset) }, {}, }; - int ruleset_fd = create_ruleset(_metadata, - LANDLOCK_ACCESS_FS_REFER | - LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_MAKE_SOCK | - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_MAKE_FIFO, - layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_SOCK | + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_MAKE_FIFO, + layer1); ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1)); ASSERT_EQ(EXDEV, errno); @@ -3344,18 +3169,13 @@ TEST_F_FORK(layout1, remove_dir) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); ASSERT_EQ(0, unlink(file1_s1d3)); ASSERT_EQ(0, unlink(file2_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); ASSERT_EQ(0, rmdir(dir_s1d3)); ASSERT_EQ(0, mkdir(dir_s1d3, 0700)); @@ -3381,12 +3201,8 @@ TEST_F_FORK(layout1, remove_file) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); ASSERT_EQ(-1, unlink(file1_s1d1)); ASSERT_EQ(EACCES, errno); @@ -3407,9 +3223,6 @@ static void test_make_file(struct __test_metadata *const _metadata, }, {}, }; - const int ruleset_fd = create_ruleset(_metadata, access, rules); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file2_s1d1)); @@ -3425,8 +3238,7 @@ static void test_make_file(struct __test_metadata *const _metadata, ASSERT_EQ(0, unlink(file1_s1d3)); ASSERT_EQ(0, unlink(file2_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, access, rules); ASSERT_EQ(-1, mknod(file1_s1d1, mode | 0400, dev)); ASSERT_EQ(EACCES, errno); @@ -3495,10 +3307,6 @@ TEST_F_FORK(layout1, make_sym) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file2_s1d1)); @@ -3510,8 +3318,7 @@ TEST_F_FORK(layout1, make_sym) ASSERT_EQ(0, unlink(file1_s1d3)); ASSERT_EQ(0, unlink(file2_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); ASSERT_EQ(-1, symlink("none", file1_s1d1)); ASSERT_EQ(EACCES, errno); @@ -3540,17 +3347,12 @@ TEST_F_FORK(layout1, make_dir) }, {}, }; - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - - ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, unlink(file1_s1d1)); ASSERT_EQ(0, unlink(file1_s1d2)); ASSERT_EQ(0, unlink(file1_s1d3)); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, rules[0].access, rules); /* Uses file_* as directory names. */ ASSERT_EQ(-1, mkdir(file1_s1d1, 0700)); @@ -3581,14 +3383,10 @@ TEST_F_FORK(layout1, proc_unlinked_file) {}, }; int reg_fd, proc_fd; - const int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, - rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + rules); ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDWR)); ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); @@ -3624,13 +3422,9 @@ TEST_F_FORK(layout1, proc_pipe) }, {}, }; - /* Limits read and write access to files tied to the filesystem. */ - const int ruleset_fd = - create_ruleset(_metadata, rules[0].access, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + /* Limits read and write access to files tied to the filesystem. */ + enforce_fs(_metadata, rules[0].access, rules); /* Checks enforcement for normal files. */ ASSERT_EQ(0, test_open(file1_s1d2, O_RDWR)); @@ -3720,16 +3514,10 @@ TEST_F_FORK(layout1, truncate_unhandled) {}, }; - const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE; - int ruleset_fd; - /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, handled, rules); - - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE, + rules); /* * Checks read right: truncate and open with O_TRUNC work, unless the @@ -3802,17 +3590,13 @@ TEST_F_FORK(layout1, truncate) }, {}, }; - const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_WRITE_FILE | - LANDLOCK_ACCESS_FS_TRUNCATE; - int ruleset_fd; /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, handled, rules); - - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + rules); /* Checks read, write and truncate rights: truncation works. */ EXPECT_EQ(0, test_truncate(file_rwt)); @@ -3912,34 +3696,25 @@ TEST_F_FORK(layout1, ftruncate) }, {}, }; - int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd; + int fd_layer0, fd_layer1, fd_layer2, fd_layer3; fd_layer0 = open(path, O_WRONLY); EXPECT_EQ(0, test_ftruncate(fd_layer0)); - ruleset_fd = create_ruleset(_metadata, handled1, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, handled1, layer1); fd_layer1 = open(path, O_WRONLY); EXPECT_EQ(0, test_ftruncate(fd_layer0)); EXPECT_EQ(0, test_ftruncate(fd_layer1)); - ruleset_fd = create_ruleset(_metadata, handled2, layer2); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, handled2, layer2); fd_layer2 = open(path, O_WRONLY); EXPECT_EQ(0, test_ftruncate(fd_layer0)); EXPECT_EQ(0, test_ftruncate(fd_layer1)); EXPECT_EQ(0, test_ftruncate(fd_layer2)); - ruleset_fd = create_ruleset(_metadata, handled3, layer3); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, handled3, layer3); fd_layer3 = open(path, O_WRONLY); EXPECT_EQ(0, test_ftruncate(fd_layer0)); @@ -4031,13 +3806,10 @@ TEST_F_FORK(ftruncate, open_and_ftruncate) }, {}, }; - int fd, ruleset_fd; + int fd; /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, variant->handled, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, variant->handled, rules); fd = open(path, O_WRONLY); EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); @@ -4072,12 +3844,9 @@ TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes) }, {}, }; - int fd, ruleset_fd; + int fd; - ruleset_fd = create_ruleset(_metadata, variant->handled, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, variant->handled, rules); fd = open(path, O_WRONLY); ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); @@ -4122,10 +3891,7 @@ static int test_fs_ioc_getflags_ioctl(int fd) TEST(memfd_ftruncate_and_ioctl) { - const struct landlock_ruleset_attr attr = { - .handled_access_fs = ACCESS_ALL, - }; - int ruleset_fd, fd, i; + int fd, i; /* * We exercise the same test both with and without Landlock enabled, to @@ -4147,10 +3913,7 @@ TEST(memfd_ftruncate_and_ioctl) ASSERT_EQ(0, close(fd)); /* Enables Landlock. */ - ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_ALL, NULL); } } @@ -4165,10 +3928,7 @@ static int test_fionread_ioctl(int fd) TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl) { - const struct landlock_ruleset_attr attr = { - .handled_access_fs = ACCESS_ALL, - }; - int ruleset_fd, fd; + int fd; /* * Checks that for files opened with O_PATH, both ioctl(2) and @@ -4184,10 +3944,7 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl) ASSERT_EQ(0, close(fd)); /* Enables Landlock. */ - ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_ALL, NULL); /* * Checks that after enabling Landlock, @@ -4261,16 +4018,10 @@ struct space_resv { */ TEST_F_FORK(layout1, blanket_permitted_ioctls) { - const struct landlock_ruleset_attr attr = { - .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV, - }; - int ruleset_fd, fd; + int fd; /* Enables Landlock. */ - ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL); fd = open("/dev/null", O_RDWR | O_CLOEXEC); ASSERT_LE(0, fd); @@ -4323,20 +4074,14 @@ TEST_F_FORK(layout1, blanket_permitted_ioctls) TEST_F_FORK(layout1, named_pipe_ioctl) { pid_t child_pid; - int fd, ruleset_fd; + int fd; const char *const path = file1_s1d1; - const struct landlock_ruleset_attr attr = { - .handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV, - }; ASSERT_EQ(0, unlink(path)); ASSERT_EQ(0, mkfifo(path, 0600)); /* Enables Landlock. */ - ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL); /* The child process opens the pipe for writing. */ child_pid = fork(); @@ -4411,20 +4156,14 @@ static int test_connect_named_unix(struct __test_metadata *const _metadata, 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, - }; + int srv_fd, cli_fd; /* Sets up a server */ ASSERT_EQ(0, unlink(path)); srv_fd = set_up_named_unix_server(_metadata, SOCK_STREAM, path); /* Enables Landlock. */ - ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_IOCTL_DEV, NULL); /* Sets up a client connection to it */ cli_fd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -4497,29 +4236,25 @@ TEST_F_FORK(ioctl, handle_dir_access_file) }, {}, }; - int file_fd, ruleset_fd; + int fd; /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, variant->handled, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, variant->handled, rules); - file_fd = open("/dev/zero", variant->open_mode); - ASSERT_LE(0, file_fd); + fd = open("/dev/zero", variant->open_mode); + ASSERT_LE(0, fd); /* Checks that IOCTL commands return the expected errors. */ - EXPECT_EQ(variant->expected_fionread_result, - test_fionread_ioctl(file_fd)); + EXPECT_EQ(variant->expected_fionread_result, test_fionread_ioctl(fd)); /* Checks that unrestrictable commands are unrestricted. */ - EXPECT_EQ(0, ioctl(file_fd, FIOCLEX)); - EXPECT_EQ(0, ioctl(file_fd, FIONCLEX)); - EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag)); - EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag)); - EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag)); + EXPECT_EQ(0, ioctl(fd, FIOCLEX)); + EXPECT_EQ(0, ioctl(fd, FIONCLEX)); + EXPECT_EQ(0, ioctl(fd, FIONBIO, &flag)); + EXPECT_EQ(0, ioctl(fd, FIOASYNC, &flag)); + EXPECT_EQ(0, ioctl(fd, FIGETBSZ, &flag)); - ASSERT_EQ(0, close(file_fd)); + ASSERT_EQ(0, close(fd)); } TEST_F_FORK(ioctl, handle_dir_access_dir) @@ -4532,13 +4267,10 @@ TEST_F_FORK(ioctl, handle_dir_access_dir) }, {}, }; - int dir_fd, ruleset_fd; + int dir_fd; /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, variant->handled, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, variant->handled, rules); /* * Ignore variant->open_mode for this test, as we intend to open a @@ -4577,32 +4309,28 @@ TEST_F_FORK(ioctl, handle_file_access_file) }, {}, }; - int file_fd, ruleset_fd; + int fd; /* Enables Landlock. */ - ruleset_fd = create_ruleset(_metadata, variant->handled, rules); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, variant->handled, rules); - file_fd = open("/dev/zero", variant->open_mode); - ASSERT_LE(0, file_fd) + fd = open("/dev/zero", variant->open_mode); + ASSERT_LE(0, fd) { TH_LOG("Failed to open /dev/zero: %s", strerror(errno)); } /* Checks that IOCTL commands return the expected errors. */ - EXPECT_EQ(variant->expected_fionread_result, - test_fionread_ioctl(file_fd)); + EXPECT_EQ(variant->expected_fionread_result, test_fionread_ioctl(fd)); /* Checks that unrestrictable commands are unrestricted. */ - EXPECT_EQ(0, ioctl(file_fd, FIOCLEX)); - EXPECT_EQ(0, ioctl(file_fd, FIONCLEX)); - EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag)); - EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag)); - EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag)); + EXPECT_EQ(0, ioctl(fd, FIOCLEX)); + EXPECT_EQ(0, ioctl(fd, FIONCLEX)); + EXPECT_EQ(0, ioctl(fd, FIONBIO, &flag)); + EXPECT_EQ(0, ioctl(fd, FIOASYNC, &flag)); + EXPECT_EQ(0, ioctl(fd, FIGETBSZ, &flag)); - ASSERT_EQ(0, close(file_fd)); + ASSERT_EQ(0, close(fd)); } /* @@ -4644,22 +4372,6 @@ 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: * @@ -4690,9 +4402,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata, char buf[1]; if (variant->domain_both) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules); unlink(path); ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC)); @@ -4702,9 +4414,11 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata, if (child_pid == 0) { if (variant->domain_child) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + rules); /* Wait for server to be available. */ EXPECT_EQ(0, close(readiness_pipe[1])); @@ -4730,9 +4444,9 @@ static void test_connect_to_parent(struct __test_metadata *const _metadata, } if (variant->domain_parent) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules); srv_fd = set_up_named_unix_server(_metadata, sock_type, path); @@ -4771,9 +4485,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata, char buf[1]; if (variant->domain_both) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules); unlink(path); ASSERT_EQ(0, pipe2(readiness_pipe, O_CLOEXEC)); @@ -4784,9 +4498,11 @@ static void test_connect_to_child(struct __test_metadata *const _metadata, if (child_pid == 0) { if (variant->domain_child) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + rules); srv_fd = set_up_named_unix_server(_metadata, sock_type, path); @@ -4810,9 +4526,9 @@ static void test_connect_to_child(struct __test_metadata *const _metadata, } if (variant->domain_parent) - enforce_fs_resolve_unix(_metadata, NULL); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL); else if (flags & ENFORCE_ALL) - enforce_fs_resolve_unix(_metadata, rules); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, rules); /* Wait for server to be available. */ EXPECT_EQ(0, close(readiness_pipe[1])); @@ -5021,11 +4737,7 @@ TEST_F_FORK(coredump, socket_not_restricted) set_core_pattern(_metadata, core_pattern); /* Restrict LANDLOCK_ACCESS_FS_RESOLVE_UNIX. */ - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = - LANDLOCK_ACCESS_FS_RESOLVE_UNIX, - }); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_RESOLVE_UNIX, NULL); /* Fork a child that crashes. */ child_pid = fork(); @@ -5212,13 +4924,9 @@ TEST_F_FORK(layout1_bind, same_content_same_file) }, {}, }; - int ruleset_fd; /* Sets rules for the parent directories. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_parent); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer1_parent); /* Checks source hierarchy. */ ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); @@ -5237,10 +4945,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) ASSERT_EQ(0, test_open(dir_s2d2, O_RDONLY | O_DIRECTORY)); /* Sets rules for the mount points. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_mount_point); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer2_mount_point); /* Checks source hierarchy. */ ASSERT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); @@ -5261,10 +4966,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) ASSERT_EQ(0, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); /* Sets a (shared) rule only on the source. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_source); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer3_source); /* Checks source hierarchy. */ ASSERT_EQ(EACCES, test_open(file1_s1d2, O_RDONLY)); @@ -5285,10 +4987,7 @@ TEST_F_FORK(layout1_bind, same_content_same_file) ASSERT_EQ(EACCES, test_open(bind_dir_s1d3, O_RDONLY | O_DIRECTORY)); /* Sets a (shared) rule only on the destination. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_destination); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer4_destination); /* Checks source hierarchy. */ ASSERT_EQ(EACCES, test_open(file1_s1d3, O_RDONLY)); @@ -5313,13 +5012,10 @@ TEST_F_FORK(layout1_bind, reparent_cross_mount) }, {}, }; - int ruleset_fd = create_ruleset( - _metadata, - LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, + layer1); /* Checks basic denied move. */ ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2)); @@ -5376,10 +5072,6 @@ TEST_F_FORK(layout1_bind, path_disconnected) create_ruleset(_metadata, ACCESS_RW, layer3_only_s1d2); int bind_s1d3_fd; - ASSERT_LE(0, ruleset_fd_l1); - ASSERT_LE(0, ruleset_fd_l2); - ASSERT_LE(0, ruleset_fd_l3); - enforce_ruleset(_metadata, ruleset_fd_l1); EXPECT_EQ(0, close(ruleset_fd_l1)); @@ -5483,8 +5175,6 @@ TEST_F_FORK(layout1_bind, path_disconnected_rename) ruleset_fd_l1 = create_ruleset(_metadata, ACCESS_ALL, layer1); ruleset_fd_l2 = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, layer2_only_s1d2); - ASSERT_LE(0, ruleset_fd_l1); - ASSERT_LE(0, ruleset_fd_l2); enforce_ruleset(_metadata, ruleset_fd_l1); EXPECT_EQ(0, close(ruleset_fd_l1)); @@ -5630,7 +5320,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_link) }, {} }; - int ruleset_fd, bind_s1d3_fd; + int bind_s1d3_fd; /* Removes unneeded files created by layout1, otherwise it will EEXIST. */ ASSERT_EQ(0, unlink(file1_s1d2)); @@ -5653,10 +5343,7 @@ TEST_F_FORK(layout1_bind, path_disconnected_link) TH_LOG("Failed to create %s: %s", dir_s4d2, strerror(errno)); } - ruleset_fd = create_ruleset(_metadata, ACCESS_ALL, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - EXPECT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_ALL, layer1); /* From disconnected to connected. */ ASSERT_EQ(0, linkat(bind_s1d3_fd, file1_name, AT_FDCWD, file1_s2d2, 0)) @@ -6194,7 +5881,6 @@ TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange) int ruleset_fd, s1d41_bind_fd, s1d42_bind_fd; ruleset_fd = create_ruleset(_metadata, handled_access, rules); - ASSERT_LE(0, ruleset_fd); /* Adds rule for the covered directory. */ if (variant->allowed_s2d2) { @@ -7127,7 +6813,6 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) }, {}, }; - int ruleset_fd; size_t i; const char *path_entry; @@ -7135,10 +6820,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) SKIP(return, "overlayfs is not supported (test)"); /* Sets rules on base directories (i.e. outside overlay scope). */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer1_base); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer1_base); /* Checks lower layer. */ for_each_path(lower_base_files, path_entry, i) { @@ -7183,10 +6865,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } /* Sets rules on data directories (i.e. inside overlay scope). */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer2_data); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer2_data); /* Checks merge. */ for_each_path(merge_base_files, path_entry, i) { @@ -7200,10 +6879,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } /* Same checks with tighter rules. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer3_subdirs); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer3_subdirs); /* Checks changes for lower layer. */ for_each_path(lower_base_files, path_entry, i) { @@ -7225,10 +6901,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } /* Sets rules directly on overlayed files. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer4_files); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer4_files); /* Checks unchanged accesses on lower layer. */ for_each_path(lower_sub_files, path_entry, i) { @@ -7253,10 +6926,7 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } /* Only allowes access to the merge hierarchy. */ - ruleset_fd = create_ruleset(_metadata, ACCESS_RW, layer5_merge_only); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, ACCESS_RW, layer5_merge_only); /* Checks new accesses on lower layer. */ for_each_path(lower_sub_files, path_entry, i) { @@ -7442,11 +7112,7 @@ static void layer3_fs_tag_inode(struct __test_metadata *const _metadata, }, {}, }; - const struct landlock_ruleset_attr layer2_deny_everything_attr = { - .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, - }; const char *const dev_null_path = "/dev/null"; - int ruleset_fd; if (self->skip_test) SKIP(return, "this filesystem is not supported (test)"); @@ -7455,22 +7121,14 @@ static void layer3_fs_tag_inode(struct __test_metadata *const _metadata, EXPECT_EQ(0, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); - ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, - layer1_allow_read_file); - EXPECT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - EXPECT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, + layer1_allow_read_file); EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); EXPECT_EQ(0, test_open(variant->file_path, O_RDONLY | O_CLOEXEC)); /* Forbids directory reading. */ - ruleset_fd = - landlock_create_ruleset(&layer2_deny_everything_attr, - sizeof(layer2_deny_everything_attr), 0); - EXPECT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - EXPECT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_READ_FILE, NULL); /* Checks with Landlock and forbidden access. */ EXPECT_EQ(EACCES, test_open(dev_null_path, O_RDONLY | O_CLOEXEC)); @@ -7532,7 +7190,6 @@ TEST_F_FORK(layout3_fs, release_inodes) ruleset_fd = create_ruleset(_metadata, LANDLOCK_ACCESS_FS_READ_DIR, layer1); - ASSERT_LE(0, ruleset_fd); /* Unmount the filesystem while it is being used by a ruleset. */ set_cap(_metadata, CAP_SYS_ADMIN); @@ -7639,11 +7296,7 @@ TEST_F(audit_layout1, execute_make) test_execute(_metadata, 0, file1_s1d1); test_check_exec(_metadata, 0, file1_s1d1); - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = - LANDLOCK_ACCESS_FS_EXECUTE, - }); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, NULL); test_execute(_metadata, EACCES, file1_s1d1); EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.execute", @@ -7670,9 +7323,7 @@ TEST_F(audit_layout1, execute_read) test_execute(_metadata, 0, file1_s1d1); test_check_exec(_metadata, 0, file1_s1d1); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); /* * The only difference with the previous audit_layout1.execute_read test is @@ -7694,9 +7345,7 @@ TEST_F(audit_layout1, write_file) { struct audit_records records; - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(EACCES, test_open(file1_s1d1, O_WRONLY)); EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, @@ -7711,9 +7360,7 @@ TEST_F(audit_layout1, read_file) { struct audit_records records; - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(EACCES, test_open(file1_s1d1, O_RDONLY)); EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_file", @@ -7728,9 +7375,7 @@ TEST_F(audit_layout1, read_dir) { struct audit_records records; - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(EACCES, test_open(dir_s1d1, O_DIRECTORY)); EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.read_dir", @@ -7748,9 +7393,7 @@ TEST_F(audit_layout1, remove_dir) EXPECT_EQ(0, unlink(file1_s1d3)); EXPECT_EQ(0, unlink(file2_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, rmdir(dir_s1d3)); EXPECT_EQ(EACCES, errno); @@ -7771,9 +7414,7 @@ TEST_F(audit_layout1, remove_file) { struct audit_records records; - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, unlink(file1_s1d3)); EXPECT_EQ(EACCES, errno); @@ -7791,9 +7432,7 @@ TEST_F(audit_layout1, make_char) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFCHR | 0644, 0)); EXPECT_EQ(EACCES, errno); @@ -7811,9 +7450,7 @@ TEST_F(audit_layout1, make_dir) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mkdir(file1_s1d3, 0755)); EXPECT_EQ(EACCES, errno); @@ -7831,9 +7468,7 @@ TEST_F(audit_layout1, make_reg) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFREG | 0644, 0)); EXPECT_EQ(EACCES, errno); @@ -7851,9 +7486,7 @@ TEST_F(audit_layout1, make_sock) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFSOCK | 0644, 0)); EXPECT_EQ(EACCES, errno); @@ -7871,9 +7504,7 @@ TEST_F(audit_layout1, make_fifo) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFIFO | 0644, 0)); EXPECT_EQ(EACCES, errno); @@ -7891,9 +7522,7 @@ TEST_F(audit_layout1, make_block) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, mknod(file1_s1d3, S_IFBLK | 0644, 0)); EXPECT_EQ(EACCES, errno); @@ -7911,9 +7540,7 @@ TEST_F(audit_layout1, make_sym) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, symlink("target", file1_s1d3)); EXPECT_EQ(EACCES, errno); @@ -7931,10 +7558,7 @@ TEST_F(audit_layout1, refer_handled) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = - LANDLOCK_ACCESS_FS_REFER, - }); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, NULL); EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3)); EXPECT_EQ(EXDEV, errno); @@ -7956,12 +7580,9 @@ TEST_F(audit_layout1, refer_make) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = - LANDLOCK_ACCESS_FS_MAKE_REG | - LANDLOCK_ACCESS_FS_REFER, - }); + enforce_fs(_metadata, + LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, + NULL); EXPECT_EQ(-1, link(file1_s1d1, file1_s1d3)); EXPECT_EQ(EACCES, errno); @@ -7981,9 +7602,7 @@ TEST_F(audit_layout1, refer_rename) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(EACCES, test_rename(file1_s1d2, file1_s2d3)); EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, @@ -8003,9 +7622,7 @@ TEST_F(audit_layout1, refer_exchange) EXPECT_EQ(0, unlink(file1_s1d3)); - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); /* * The only difference with the previous audit_layout1.refer_rename test is @@ -8043,12 +7660,8 @@ TEST_F(audit_layout1, refer_rename_half) }, {}, }; - int ruleset_fd = - create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); - ASSERT_LE(0, ruleset_fd); - enforce_ruleset(_metadata, ruleset_fd); - ASSERT_EQ(0, close(ruleset_fd)); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1); ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3)); ASSERT_EQ(EXDEV, errno); @@ -8066,9 +7679,7 @@ TEST_F(audit_layout1, truncate) { struct audit_records records; - drop_access_rights(_metadata, &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); EXPECT_EQ(-1, truncate(file1_s1d3, 0)); EXPECT_EQ(EACCES, errno); @@ -8085,12 +7696,7 @@ TEST_F(audit_layout1, ioctl_dev) struct audit_records records; int fd; - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = - ACCESS_ALL & - ~LANDLOCK_ACCESS_FS_READ_FILE, - }); + enforce_fs(_metadata, ACCESS_ALL & ~LANDLOCK_ACCESS_FS_READ_FILE, NULL); fd = open("/dev/null", O_RDONLY | O_CLOEXEC); ASSERT_LE(0, fd); @@ -8116,10 +7722,7 @@ TEST_F(audit_layout1, resolve_unix) child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = ACCESS_ALL, - }); + enforce_fs(_metadata, ACCESS_ALL, NULL); cli_fd = socket(AF_UNIX, SOCK_STREAM, 0); ASSERT_LE(0, cli_fd); @@ -8148,11 +7751,7 @@ TEST_F(audit_layout1, mount) { struct audit_records records; - drop_access_rights(_metadata, - &(struct landlock_ruleset_attr){ - .handled_access_fs = - LANDLOCK_ACCESS_FS_EXECUTE, - }); + enforce_fs(_metadata, LANDLOCK_ACCESS_FS_EXECUTE, NULL); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, mount(NULL, dir_s3d2, NULL, MS_RDONLY, NULL)); -- cgit v1.2.3