[PATCH v1 7/9] selftests/landlock: Test filesystem denial blockers

Mickaël Salaün mic at digikod.net
Fri Sep 18 18:50:30 UTC 2026


Filesystem denial traces identify the policy change needed to allow a
request, so require exact blocker values rather than merely nonempty
output. Pin a READ_DIR denial to exactly one event with
blockers=read_dir. Pin a REFER-only mount denial to EPERM and exactly
one event with blockers=change_topology.

The mount child retains CAP_SYS_ADMIN so Landlock is the only expected
source of EPERM.  This prevents a later capability failure from masking
a Landlock regression; the trace-collecting parent remains unsandboxed.

Cc: Günther Noack <gnoack at google.com>
Cc: Steven Rostedt <rostedt at goodmis.org>
Signed-off-by: Mickaël Salaün <mic at digikod.net>
---
 .../selftests/landlock/trace_fs_test.c        | 76 ++++++++++++++++++-
 1 file changed, 73 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c
index 6666d4746cb1..64014ade3a0e 100644
--- a/tools/testing/selftests/landlock/trace_fs_test.c
+++ b/tools/testing/selftests/landlock/trace_fs_test.c
@@ -548,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested)
  */
 TEST_F(trace_fs, deny_access_fs_denied)
 {
-	char *buf;
+	const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+	char *buf, blockers[64];
 	int count;
 
 	ASSERT_EQ(0, tracefs_clear_buf());
@@ -564,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied)
 	buf = tracefs_read_buf();
 	ASSERT_NE(NULL, buf);
 
-	count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
-	EXPECT_LE(1, count);
+	count = tracefs_count_matches(buf, event_regex);
+	EXPECT_EQ(1, count)
+	{
+		TH_LOG("Expected 1 access denial, got %d\n%s", count, buf);
+	}
+	ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+					   blockers, sizeof(blockers)));
+	EXPECT_STREQ("read_dir", blockers);
+
+	free(buf);
+}
+
+/*
+ * Verifies that a denied mount reports the singleton topology blocker rather
+ * than an empty access mask.
+ */
+TEST_F(trace_fs, deny_change_topology)
+{
+	const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_REFER,
+	};
+	char *buf, blockers[64];
+	int count, ruleset_fd, status;
+	pid_t pid;
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	/* Ensure that Landlock is the only expected mount denial. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0) {
+		if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+			close(ruleset_fd);
+			_exit(1);
+		}
+		if (landlock_restrict_self(ruleset_fd, 0)) {
+			close(ruleset_fd);
+			_exit(2);
+		}
+		close(ruleset_fd);
+
+		if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1)
+			_exit(3);
+
+		if (errno != EPERM)
+			_exit(4);
+
+		_exit(0);
+	}
+	close(ruleset_fd);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+	count = tracefs_count_matches(buf, event_regex);
+	EXPECT_EQ(1, count)
+	{
+		TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf);
+	}
+	ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+					   blockers, sizeof(blockers)));
+	EXPECT_STREQ("change_topology", blockers);
 
 	free(buf);
 }
-- 
2.55.0




More information about the Linux-security-module-archive mailing list