xref: /linux/tools/testing/selftests/landlock/trace_test.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
12651712aSMickaël Salaün // SPDX-License-Identifier: GPL-2.0
22651712aSMickaël Salaün /*
32651712aSMickaël Salaün  * Landlock tests - Tracepoints
42651712aSMickaël Salaün  *
52651712aSMickaël Salaün  * Copyright © 2026 Cloudflare, Inc.
62651712aSMickaël Salaün  */
72651712aSMickaël Salaün 
82651712aSMickaël Salaün #define _GNU_SOURCE
92651712aSMickaël Salaün #include <errno.h>
102651712aSMickaël Salaün #include <fcntl.h>
112651712aSMickaël Salaün #include <linux/landlock.h>
12*ac6d193dSMickaël Salaün #include <pthread.h>
132651712aSMickaël Salaün #include <sched.h>
142651712aSMickaël Salaün #include <stdio.h>
152651712aSMickaël Salaün #include <string.h>
162651712aSMickaël Salaün #include <sys/mount.h>
172651712aSMickaël Salaün #include <sys/stat.h>
182651712aSMickaël Salaün #include <sys/types.h>
192651712aSMickaël Salaün #include <sys/wait.h>
202651712aSMickaël Salaün #include <unistd.h>
212651712aSMickaël Salaün 
222651712aSMickaël Salaün #include "common.h"
232651712aSMickaël Salaün #include "trace.h"
242651712aSMickaël Salaün 
252651712aSMickaël Salaün #define TRACE_TASK "trace_test"
262651712aSMickaël Salaün 
272651712aSMickaël Salaün /* clang-format off */
282651712aSMickaël Salaün FIXTURE(trace) {
292651712aSMickaël Salaün 	/* clang-format on */
302651712aSMickaël Salaün 	int tracefs_ok;
312651712aSMickaël Salaün };
322651712aSMickaël Salaün 
332651712aSMickaël Salaün FIXTURE_SETUP(trace)
342651712aSMickaël Salaün {
352651712aSMickaël Salaün 	int ret;
362651712aSMickaël Salaün 
372651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
382651712aSMickaël Salaün 	ASSERT_EQ(0, unshare(CLONE_NEWNS));
392651712aSMickaël Salaün 	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
402651712aSMickaël Salaün 
412651712aSMickaël Salaün 	ret = tracefs_fixture_setup();
422651712aSMickaël Salaün 	if (ret) {
432651712aSMickaël Salaün 		clear_cap(_metadata, CAP_SYS_ADMIN);
442651712aSMickaël Salaün 		self->tracefs_ok = 0;
452651712aSMickaël Salaün 		SKIP(return, "tracefs not available");
462651712aSMickaël Salaün 	}
472651712aSMickaël Salaün 	self->tracefs_ok = 1;
482651712aSMickaël Salaün 
492651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
502651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
51*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
522651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
532651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true));
542651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
552651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true));
562651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
572651712aSMickaël Salaün 	ASSERT_EQ(0,
582651712aSMickaël Salaün 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, true));
592651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, true));
602651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, true));
612651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear());
622651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
632651712aSMickaël Salaün }
642651712aSMickaël Salaün 
652651712aSMickaël Salaün FIXTURE_TEARDOWN(trace)
662651712aSMickaël Salaün {
672651712aSMickaël Salaün 	if (!self->tracefs_ok)
682651712aSMickaël Salaün 		return;
692651712aSMickaël Salaün 
702651712aSMickaël Salaün 	/* Disables landlock events and clears PID filter. */
712651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
722651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
732651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
74*ac6d193dSMickaël Salaün 	tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
752651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
762651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false);
772651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
782651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);
792651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
802651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false);
812651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false);
822651712aSMickaël Salaün 	tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false);
832651712aSMickaël Salaün 	tracefs_clear_pid_filter();
842651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
852651712aSMickaël Salaün 
862651712aSMickaël Salaün 	/*
872651712aSMickaël Salaün 	 * The mount namespace is cleaned up automatically when the test process
882651712aSMickaël Salaün 	 * (harness child) exits.
892651712aSMickaël Salaün 	 */
902651712aSMickaël Salaün }
912651712aSMickaël Salaün 
922651712aSMickaël Salaün /*
932651712aSMickaël Salaün  * Verifies that no trace events are emitted when the tracepoints are disabled.
942651712aSMickaël Salaün  */
952651712aSMickaël Salaün TEST_F(trace, no_trace_when_disabled)
962651712aSMickaël Salaün {
972651712aSMickaël Salaün 	char *buf;
982651712aSMickaël Salaün 
992651712aSMickaël Salaün 	/* Disable all landlock events. */
1002651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
1012651712aSMickaël Salaün 	ASSERT_EQ(0,
1022651712aSMickaël Salaün 		  tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false));
1032651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));
104*ac6d193dSMickaël Salaün 	ASSERT_EQ(0,
105*ac6d193dSMickaël Salaün 		  tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));
1062651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false));
1072651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false));
1082651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));
1092651712aSMickaël Salaün 	ASSERT_EQ(0,
1102651712aSMickaël Salaün 		  tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false));
1112651712aSMickaël Salaün 	ASSERT_EQ(0,
1122651712aSMickaël Salaün 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false));
1132651712aSMickaël Salaün 	ASSERT_EQ(0,
1142651712aSMickaël Salaün 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false));
1152651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false));
1162651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE,
1172651712aSMickaël Salaün 					  false));
1182651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(
1192651712aSMickaël Salaün 			     TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
1202651712aSMickaël Salaün 			     false));
1212651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false));
1222651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false));
1232651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear());
1242651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
1252651712aSMickaël Salaün 
1262651712aSMickaël Salaün 	/*
1272651712aSMickaël Salaün 	 * Trigger both allowed and denied accesses to verify neither check_rule
1282651712aSMickaël Salaün 	 * nor check_access events fire when disabled.
1292651712aSMickaël Salaün 	 */
1302651712aSMickaël Salaün 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
1312651712aSMickaël Salaün 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
1322651712aSMickaël Salaün 
1332651712aSMickaël Salaün 	/* Read trace buffer and verify no landlock events at all. */
1342651712aSMickaël Salaün 	buf = tracefs_read_buf();
1352651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
1362651712aSMickaël Salaün 
1372651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_count_matches(buf, "landlock_"))
1382651712aSMickaël Salaün 	{
1392651712aSMickaël Salaün 		TH_LOG("Expected 0 landlock events when disabled\n%s", buf);
1402651712aSMickaël Salaün 	}
1412651712aSMickaël Salaün 
1422651712aSMickaël Salaün 	free(buf);
1432651712aSMickaël Salaün }
1442651712aSMickaël Salaün 
1452651712aSMickaël Salaün /*
1462651712aSMickaël Salaün  * Verifies that landlock_create_ruleset emits a trace event with the correct
1472651712aSMickaël Salaün  * handled access masks.
1482651712aSMickaël Salaün  */
1492651712aSMickaël Salaün TEST_F(trace, create_ruleset)
1502651712aSMickaël Salaün {
1512651712aSMickaël Salaün 	struct landlock_ruleset_attr ruleset_attr = {
1522651712aSMickaël Salaün 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
1532651712aSMickaël Salaün 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
1542651712aSMickaël Salaün 	};
1552651712aSMickaël Salaün 	int ruleset_fd;
1562651712aSMickaël Salaün 	char *buf, *dot;
1572651712aSMickaël Salaün 	char field[64];
1582651712aSMickaël Salaün 
1592651712aSMickaël Salaün 	ruleset_fd =
1602651712aSMickaël Salaün 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
1612651712aSMickaël Salaün 	ASSERT_LE(0, ruleset_fd);
1622651712aSMickaël Salaün 	ASSERT_EQ(0, close(ruleset_fd));
1632651712aSMickaël Salaün 
1642651712aSMickaël Salaün 	buf = tracefs_read_buf();
1652651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
1662651712aSMickaël Salaün 
1672651712aSMickaël Salaün 	EXPECT_EQ(1,
1682651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_RULESET(TRACE_TASK)))
1692651712aSMickaël Salaün 	{
1702651712aSMickaël Salaün 		TH_LOG("Expected 1 create_ruleset event\n%s", buf);
1712651712aSMickaël Salaün 	}
1722651712aSMickaël Salaün 
1732651712aSMickaël Salaün 	/* Verify handled_fs matches what we requested. */
1742651712aSMickaël Salaün 	EXPECT_EQ(0,
1752651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
1762651712aSMickaël Salaün 					"handled_fs", field, sizeof(field)));
1772651712aSMickaël Salaün 	EXPECT_STREQ("read_file", field);
1782651712aSMickaël Salaün 
1792651712aSMickaël Salaün 	/* Verify handled_net matches. */
1802651712aSMickaël Salaün 	EXPECT_EQ(0,
1812651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
1822651712aSMickaël Salaün 					"handled_net", field, sizeof(field)));
1832651712aSMickaël Salaün 	EXPECT_STREQ("bind_tcp", field);
1842651712aSMickaël Salaün 
1852651712aSMickaël Salaün 	/* Verify version is 0 at creation (no rules added yet). */
1862651712aSMickaël Salaün 	EXPECT_EQ(0,
1872651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
1882651712aSMickaël Salaün 					"ruleset", field, sizeof(field)));
1892651712aSMickaël Salaün 	/* Format is <hex>.<dec>; version is after the dot. */
1902651712aSMickaël Salaün 	dot = strchr(field, '.');
1912651712aSMickaël Salaün 	ASSERT_NE(0, !!dot);
1922651712aSMickaël Salaün 	EXPECT_STREQ("0", dot + 1);
1932651712aSMickaël Salaün 
1942651712aSMickaël Salaün 	free(buf);
1952651712aSMickaël Salaün }
1962651712aSMickaël Salaün 
1972651712aSMickaël Salaün /*
1982651712aSMickaël Salaün  * Verifies that the ruleset version increments with each add_rule call and that
1992651712aSMickaël Salaün  * create_domain records the correct version.
2002651712aSMickaël Salaün  */
2012651712aSMickaël Salaün TEST_F(trace, ruleset_version)
2022651712aSMickaël Salaün {
2032651712aSMickaël Salaün 	pid_t pid;
2042651712aSMickaël Salaün 	int status;
2052651712aSMickaël Salaün 	char *buf;
2062651712aSMickaël Salaün 	const char *dot;
2072651712aSMickaël Salaün 	char field[64];
2082651712aSMickaël Salaün 
2092651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
2102651712aSMickaël Salaün 
2112651712aSMickaël Salaün 	pid = fork();
2122651712aSMickaël Salaün 	ASSERT_LE(0, pid);
2132651712aSMickaël Salaün 
2142651712aSMickaël Salaün 	if (pid == 0) {
2152651712aSMickaël Salaün 		struct landlock_ruleset_attr ruleset_attr = {
2162651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
2172651712aSMickaël Salaün 		};
2182651712aSMickaël Salaün 		struct landlock_path_beneath_attr path_beneath = {
2192651712aSMickaël Salaün 			.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
2202651712aSMickaël Salaün 		};
2212651712aSMickaël Salaün 		int ruleset_fd;
2222651712aSMickaël Salaün 
2232651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
2242651712aSMickaël Salaün 						     sizeof(ruleset_attr), 0);
2252651712aSMickaël Salaün 		if (ruleset_fd < 0)
2262651712aSMickaël Salaün 			_exit(1);
2272651712aSMickaël Salaün 
2282651712aSMickaël Salaün 		/* First rule: version becomes 1. */
2292651712aSMickaël Salaün 		path_beneath.parent_fd =
2302651712aSMickaël Salaün 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
2312651712aSMickaël Salaün 		if (path_beneath.parent_fd < 0)
2322651712aSMickaël Salaün 			_exit(1);
2332651712aSMickaël Salaün 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
2342651712aSMickaël Salaün 				  &path_beneath, 0);
2352651712aSMickaël Salaün 		close(path_beneath.parent_fd);
2362651712aSMickaël Salaün 
2372651712aSMickaël Salaün 		/* Second rule: version becomes 2. */
2382651712aSMickaël Salaün 		path_beneath.parent_fd =
2392651712aSMickaël Salaün 			open("/tmp", O_PATH | O_DIRECTORY | O_CLOEXEC);
2402651712aSMickaël Salaün 		if (path_beneath.parent_fd < 0)
2412651712aSMickaël Salaün 			_exit(1);
2422651712aSMickaël Salaün 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
2432651712aSMickaël Salaün 				  &path_beneath, 0);
2442651712aSMickaël Salaün 		close(path_beneath.parent_fd);
2452651712aSMickaël Salaün 
2462651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
2472651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0))
2482651712aSMickaël Salaün 			_exit(1);
2492651712aSMickaël Salaün 		close(ruleset_fd);
2502651712aSMickaël Salaün 		_exit(0);
2512651712aSMickaël Salaün 	}
2522651712aSMickaël Salaün 
2532651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
2542651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
2552651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
2562651712aSMickaël Salaün 
2572651712aSMickaël Salaün 	buf = tracefs_read_buf();
2582651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
2592651712aSMickaël Salaün 
2602651712aSMickaël Salaün 	/* Verify create_ruleset has version=0. */
2612651712aSMickaël Salaün 	ASSERT_EQ(0,
2622651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
2632651712aSMickaël Salaün 					"ruleset", field, sizeof(field)));
2642651712aSMickaël Salaün 	dot = strchr(field, '.');
2652651712aSMickaël Salaün 	ASSERT_NE(0, !!dot);
2662651712aSMickaël Salaün 	EXPECT_STREQ("0", dot + 1);
2672651712aSMickaël Salaün 
2682651712aSMickaël Salaün 	/* Verify 2 add_rule_fs events were emitted. */
2692651712aSMickaël Salaün 	EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
2702651712aSMickaël Salaün 	{
2712651712aSMickaël Salaün 		TH_LOG("Expected 2 add_rule_fs events\n%s", buf);
2722651712aSMickaël Salaün 	}
2732651712aSMickaël Salaün 
2742651712aSMickaël Salaün 	/*
2752651712aSMickaël Salaün 	 * Verify create_domain records version=2 (after 2 add_rule calls).  The
2762651712aSMickaël Salaün 	 * ruleset field format is <hex_id>.<dec_version>.
2772651712aSMickaël Salaün 	 */
2782651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
2792651712aSMickaël Salaün 					   "ruleset", field, sizeof(field)));
2802651712aSMickaël Salaün 	dot = strchr(field, '.');
2812651712aSMickaël Salaün 	ASSERT_NE(0, !!dot);
2822651712aSMickaël Salaün 	EXPECT_STREQ("2", dot + 1);
2832651712aSMickaël Salaün 
2842651712aSMickaël Salaün 	free(buf);
2852651712aSMickaël Salaün }
2862651712aSMickaël Salaün 
2872651712aSMickaël Salaün /*
2882651712aSMickaël Salaün  * Verifies that landlock_create_domain emits a trace event linking the ruleset
2892651712aSMickaël Salaün  * ID to the new domain ID.
2902651712aSMickaël Salaün  */
2912651712aSMickaël Salaün TEST_F(trace, create_domain)
2922651712aSMickaël Salaün {
2932651712aSMickaël Salaün 	pid_t pid;
2942651712aSMickaël Salaün 	int status, check_count;
2952651712aSMickaël Salaün 	char *buf;
2962651712aSMickaël Salaün 	char parent_id[64], domain_id[64], check_domain[64];
2972651712aSMickaël Salaün 
2982651712aSMickaël Salaün 	/* Clear before the sandboxed child. */
2992651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
3002651712aSMickaël Salaün 
3012651712aSMickaël Salaün 	pid = fork();
3022651712aSMickaël Salaün 	ASSERT_LE(0, pid);
3032651712aSMickaël Salaün 
3042651712aSMickaël Salaün 	if (pid == 0) {
3052651712aSMickaël Salaün 		struct landlock_ruleset_attr ruleset_attr = {
3062651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
3072651712aSMickaël Salaün 		};
3082651712aSMickaël Salaün 		struct landlock_path_beneath_attr path_beneath = {
3092651712aSMickaël Salaün 			.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
3102651712aSMickaël Salaün 		};
3112651712aSMickaël Salaün 		int ruleset_fd, fd;
3122651712aSMickaël Salaün 
3132651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
3142651712aSMickaël Salaün 						     sizeof(ruleset_attr), 0);
3152651712aSMickaël Salaün 		if (ruleset_fd < 0)
3162651712aSMickaël Salaün 			_exit(1);
3172651712aSMickaël Salaün 
3182651712aSMickaël Salaün 		path_beneath.parent_fd =
3192651712aSMickaël Salaün 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
3202651712aSMickaël Salaün 		if (path_beneath.parent_fd < 0)
3212651712aSMickaël Salaün 			_exit(1);
3222651712aSMickaël Salaün 
3232651712aSMickaël Salaün 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
3242651712aSMickaël Salaün 				  &path_beneath, 0);
3252651712aSMickaël Salaün 		close(path_beneath.parent_fd);
3262651712aSMickaël Salaün 
3272651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
3282651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0))
3292651712aSMickaël Salaün 			_exit(1);
3302651712aSMickaël Salaün 		close(ruleset_fd);
3312651712aSMickaël Salaün 
3322651712aSMickaël Salaün 		/* Trigger a check_rule to verify domain_id correlation. */
3332651712aSMickaël Salaün 		fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
3342651712aSMickaël Salaün 		if (fd >= 0)
3352651712aSMickaël Salaün 			close(fd);
3362651712aSMickaël Salaün 
3372651712aSMickaël Salaün 		_exit(0);
3382651712aSMickaël Salaün 	}
3392651712aSMickaël Salaün 
3402651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
3412651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
3422651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
3432651712aSMickaël Salaün 
3442651712aSMickaël Salaün 	buf = tracefs_read_buf();
3452651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
3462651712aSMickaël Salaün 
3472651712aSMickaël Salaün 	/* Verify create_domain event exists. */
3482651712aSMickaël Salaün 	EXPECT_EQ(1,
3492651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
3502651712aSMickaël Salaün 	{
3512651712aSMickaël Salaün 		TH_LOG("Expected 1 create_domain event\n%s", buf);
3522651712aSMickaël Salaün 	}
3532651712aSMickaël Salaün 
3542651712aSMickaël Salaün 	/* Extract the domain ID from create_domain. */
3552651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
3562651712aSMickaël Salaün 					   "domain", domain_id,
3572651712aSMickaël Salaün 					   sizeof(domain_id)));
3582651712aSMickaël Salaün 
3592651712aSMickaël Salaün 	/* Verify domain ID is non-zero. */
3602651712aSMickaël Salaün 	EXPECT_NE(0, strcmp(domain_id, "0"));
3612651712aSMickaël Salaün 
3622651712aSMickaël Salaün 	/* Verify parent=0 (first restriction, no prior domain). */
3632651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
3642651712aSMickaël Salaün 					   "parent", parent_id,
3652651712aSMickaël Salaün 					   sizeof(parent_id)));
3662651712aSMickaël Salaün 	EXPECT_STREQ("0", parent_id);
3672651712aSMickaël Salaün 
3682651712aSMickaël Salaün 	/*
3692651712aSMickaël Salaün 	 * Verify the same domain ID appears in the check_rule event, confirming
3702651712aSMickaël Salaün 	 * end-to-end correlation.
3712651712aSMickaël Salaün 	 */
3722651712aSMickaël Salaün 	check_count =
3732651712aSMickaël Salaün 		tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
3742651712aSMickaël Salaün 	ASSERT_LE(1, check_count)
3752651712aSMickaël Salaün 	{
3762651712aSMickaël Salaün 		TH_LOG("Expected check_rule_fs events\n%s", buf);
3772651712aSMickaël Salaün 	}
3782651712aSMickaël Salaün 
3792651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
3802651712aSMickaël Salaün 					   "domain", check_domain,
3812651712aSMickaël Salaün 					   sizeof(check_domain)));
3822651712aSMickaël Salaün 	EXPECT_STREQ(domain_id, check_domain);
3832651712aSMickaël Salaün 
3842651712aSMickaël Salaün 	free(buf);
3852651712aSMickaël Salaün }
3862651712aSMickaël Salaün 
3872651712aSMickaël Salaün /* Builds a rule-less scope-based ruleset; returns the fd or -1. */
3882651712aSMickaël Salaün static int build_enforce_ruleset(void)
3892651712aSMickaël Salaün {
3902651712aSMickaël Salaün 	const struct landlock_ruleset_attr attr = {
3912651712aSMickaël Salaün 		.scoped = LANDLOCK_SCOPE_SIGNAL,
3922651712aSMickaël Salaün 	};
3932651712aSMickaël Salaün 
3942651712aSMickaël Salaün 	return landlock_create_ruleset(&attr, sizeof(attr), 0);
3952651712aSMickaël Salaün }
3962651712aSMickaël Salaün 
3972651712aSMickaël Salaün /*
3982651712aSMickaël Salaün  * Verifies that nested landlock_restrict_self calls produce trace events with
3992651712aSMickaël Salaün  * correct parent domain IDs: the second create_domain's parent should be the
4002651712aSMickaël Salaün  * first domain's ID.
4012651712aSMickaël Salaün  */
4022651712aSMickaël Salaün TEST_F(trace, create_domain_nested)
4032651712aSMickaël Salaün {
4042651712aSMickaël Salaün 	pid_t pid;
4052651712aSMickaël Salaün 	int status;
4062651712aSMickaël Salaün 	char *buf;
4072651712aSMickaël Salaün 	const char *after_first;
4082651712aSMickaël Salaün 	char first_domain[64], first_parent[64], second_parent[64];
4092651712aSMickaël Salaün 
4102651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
4112651712aSMickaël Salaün 
4122651712aSMickaël Salaün 	pid = fork();
4132651712aSMickaël Salaün 	ASSERT_LE(0, pid);
4142651712aSMickaël Salaün 
4152651712aSMickaël Salaün 	if (pid == 0) {
4162651712aSMickaël Salaün 		int ruleset_fd;
4172651712aSMickaël Salaün 
4182651712aSMickaël Salaün 		/* First restriction. */
4192651712aSMickaël Salaün 		ruleset_fd = build_enforce_ruleset();
4202651712aSMickaël Salaün 		if (ruleset_fd < 0)
4212651712aSMickaël Salaün 			_exit(1);
4222651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
4232651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0))
4242651712aSMickaël Salaün 			_exit(1);
4252651712aSMickaël Salaün 		close(ruleset_fd);
4262651712aSMickaël Salaün 
4272651712aSMickaël Salaün 		/* Second restriction (nested). */
4282651712aSMickaël Salaün 		ruleset_fd = build_enforce_ruleset();
4292651712aSMickaël Salaün 		if (ruleset_fd < 0)
4302651712aSMickaël Salaün 			_exit(1);
4312651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0))
4322651712aSMickaël Salaün 			_exit(1);
4332651712aSMickaël Salaün 		close(ruleset_fd);
4342651712aSMickaël Salaün 
4352651712aSMickaël Salaün 		_exit(0);
4362651712aSMickaël Salaün 	}
4372651712aSMickaël Salaün 
4382651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
4392651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
4402651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
4412651712aSMickaël Salaün 
4422651712aSMickaël Salaün 	buf = tracefs_read_buf();
4432651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
4442651712aSMickaël Salaün 
4452651712aSMickaël Salaün 	/* Should have 2 create_domain events. */
4462651712aSMickaël Salaün 	EXPECT_EQ(2,
4472651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
4482651712aSMickaël Salaün 	{
4492651712aSMickaël Salaün 		TH_LOG("Expected 2 create_domain events\n%s", buf);
4502651712aSMickaël Salaün 	}
4512651712aSMickaël Salaün 
4522651712aSMickaël Salaün 	/*
4532651712aSMickaël Salaün 	 * Extract domain and parent from each create_domain event.  The first
4542651712aSMickaël Salaün 	 * event (parent=0) is the outer domain; the second (parent!=0) is the
4552651712aSMickaël Salaün 	 * nested domain whose parent should match the first domain's ID.
4562651712aSMickaël Salaün 	 */
4572651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
4582651712aSMickaël Salaün 					   "domain", first_domain,
4592651712aSMickaël Salaün 					   sizeof(first_domain)));
4602651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
4612651712aSMickaël Salaün 					   "parent", first_parent,
4622651712aSMickaël Salaün 					   sizeof(first_parent)));
4632651712aSMickaël Salaün 	EXPECT_STREQ("0", first_parent);
4642651712aSMickaël Salaün 
4652651712aSMickaël Salaün 	/*
4662651712aSMickaël Salaün 	 * Find the second create_domain by scanning past the first.
4672651712aSMickaël Salaün 	 * tracefs_extract_field returns the first match, so search in the
4682651712aSMickaël Salaün 	 * buffer after the first event.
4692651712aSMickaël Salaün 	 *
4702651712aSMickaël Salaün 	 * Skip past the first create_domain line. tracefs_extract_field matches
4712651712aSMickaël Salaün 	 * the first line that matches the regex, so passing the buffer after
4722651712aSMickaël Salaün 	 * the first matching line gives us the second event.
4732651712aSMickaël Salaün 	 */
4742651712aSMickaël Salaün 	after_first = strstr(buf, "landlock_create_domain:");
4752651712aSMickaël Salaün 	ASSERT_NE(NULL, after_first);
4762651712aSMickaël Salaün 	after_first = strchr(after_first, '\n');
4772651712aSMickaël Salaün 	ASSERT_NE(NULL, after_first);
4782651712aSMickaël Salaün 
4792651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(
4802651712aSMickaël Salaün 			     after_first + 1, REGEX_CREATE_DOMAIN(TRACE_TASK),
4812651712aSMickaël Salaün 			     "parent", second_parent, sizeof(second_parent)));
4822651712aSMickaël Salaün 
4832651712aSMickaël Salaün 	/* The second domain's parent should be the first domain's ID. */
4842651712aSMickaël Salaün 	EXPECT_STREQ(first_domain, second_parent);
4852651712aSMickaël Salaün 
4862651712aSMickaël Salaün 	free(buf);
4872651712aSMickaël Salaün }
4882651712aSMickaël Salaün 
4892651712aSMickaël Salaün /*
4902651712aSMickaël Salaün  * Verifies that landlock_add_rule does not emit a trace event when the syscall
4912651712aSMickaël Salaün  * fails (e.g., invalid ruleset fd).
4922651712aSMickaël Salaün  */
4932651712aSMickaël Salaün TEST_F(trace, add_rule_invalid_fd)
4942651712aSMickaël Salaün {
4952651712aSMickaël Salaün 	struct landlock_path_beneath_attr path_beneath = {
4962651712aSMickaël Salaün 		.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
4972651712aSMickaël Salaün 	};
4982651712aSMickaël Salaün 	char *buf;
4992651712aSMickaël Salaün 
5002651712aSMickaël Salaün 	path_beneath.parent_fd = open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
5012651712aSMickaël Salaün 	ASSERT_LE(0, path_beneath.parent_fd);
5022651712aSMickaël Salaün 
5032651712aSMickaël Salaün 	/* Invalid ruleset fd (-1). */
5042651712aSMickaël Salaün 	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH,
5052651712aSMickaël Salaün 					&path_beneath, 0));
5062651712aSMickaël Salaün 	ASSERT_EQ(0, close(path_beneath.parent_fd));
5072651712aSMickaël Salaün 
5082651712aSMickaël Salaün 	buf = tracefs_read_buf();
5092651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
5102651712aSMickaël Salaün 
5112651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
5122651712aSMickaël Salaün 	{
5132651712aSMickaël Salaün 		TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf);
5142651712aSMickaël Salaün 	}
5152651712aSMickaël Salaün 
5162651712aSMickaël Salaün 	free(buf);
5172651712aSMickaël Salaün }
5182651712aSMickaël Salaün 
5192651712aSMickaël Salaün /*
5202651712aSMickaël Salaün  * Verifies that landlock_create_domain does not emit a trace event when the
5212651712aSMickaël Salaün  * syscall fails (e.g., invalid ruleset fd or unknown flags).
5222651712aSMickaël Salaün  */
5232651712aSMickaël Salaün TEST_F(trace, create_domain_invalid)
5242651712aSMickaël Salaün {
5252651712aSMickaël Salaün 	int ruleset_fd;
5262651712aSMickaël Salaün 	char *buf;
5272651712aSMickaël Salaün 
5282651712aSMickaël Salaün 	ruleset_fd = build_enforce_ruleset();
5292651712aSMickaël Salaün 	ASSERT_LE(0, ruleset_fd);
5302651712aSMickaël Salaün 
5312651712aSMickaël Salaün 	/* Clear the trace buffer after create_ruleset event. */
5322651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
5332651712aSMickaël Salaün 
5342651712aSMickaël Salaün 	/* Invalid fd. */
5352651712aSMickaël Salaün 	ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
5362651712aSMickaël Salaün 
5372651712aSMickaël Salaün 	/* Unknown flags. */
5382651712aSMickaël Salaün 	ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, -1));
5392651712aSMickaël Salaün 
5402651712aSMickaël Salaün 	ASSERT_EQ(0, close(ruleset_fd));
5412651712aSMickaël Salaün 
5422651712aSMickaël Salaün 	buf = tracefs_read_buf();
5432651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
5442651712aSMickaël Salaün 
5452651712aSMickaël Salaün 	EXPECT_EQ(0,
5462651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
5472651712aSMickaël Salaün 	{
5482651712aSMickaël Salaün 		TH_LOG("No create_domain event expected on error\n%s", buf);
5492651712aSMickaël Salaün 	}
5502651712aSMickaël Salaün 
5512651712aSMickaël Salaün 	free(buf);
5522651712aSMickaël Salaün }
5532651712aSMickaël Salaün 
5542651712aSMickaël Salaün /*
5552651712aSMickaël Salaün  * Verifies that trace_landlock_free_domain fires when a domain is deallocated,
5562651712aSMickaël Salaün  * with the correct denials count.
5572651712aSMickaël Salaün  */
5582651712aSMickaël Salaün TEST_F(trace, free_domain)
5592651712aSMickaël Salaün {
5602651712aSMickaël Salaün 	char *buf;
5612651712aSMickaël Salaün 	int count;
5622651712aSMickaël Salaün 	char denials_field[32];
5632651712aSMickaël Salaün 
5642651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
5652651712aSMickaël Salaün 
5662651712aSMickaël Salaün 	/*
5672651712aSMickaël Salaün 	 * The domain is freed via a work queue (kworker), so the free_domain
5682651712aSMickaël Salaün 	 * trace event is emitted from a different PID.  Clear the PID filter
5692651712aSMickaël Salaün 	 * BEFORE the child exits, so the kworker event passes the filter when
5702651712aSMickaël Salaün 	 * it fires.
5712651712aSMickaël Salaün 	 */
5722651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
5732651712aSMickaël Salaün 	tracefs_clear_pid_filter();
5742651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
5752651712aSMickaël Salaün 
5762651712aSMickaël Salaün 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
5772651712aSMickaël Salaün 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
5782651712aSMickaël Salaün 
5792651712aSMickaël Salaün 	/*
5802651712aSMickaël Salaün 	 * Wait for the deferred deallocation work to run.  The domain is freed
5812651712aSMickaël Salaün 	 * asynchronously from a kworker; poll until the event appears or a
5822651712aSMickaël Salaün 	 * timeout is reached.
5832651712aSMickaël Salaün 	 */
5842651712aSMickaël Salaün 	for (int retry = 0; retry < 10; retry++) {
5852651712aSMickaël Salaün 		usleep(100000);
5862651712aSMickaël Salaün 
5872651712aSMickaël Salaün 		set_cap(_metadata, CAP_SYS_ADMIN);
5882651712aSMickaël Salaün 		buf = tracefs_read_trace();
5892651712aSMickaël Salaün 		clear_cap(_metadata, CAP_SYS_ADMIN);
5902651712aSMickaël Salaün 		ASSERT_NE(NULL, buf);
5912651712aSMickaël Salaün 
5922651712aSMickaël Salaün 		count = tracefs_count_matches(buf,
5932651712aSMickaël Salaün 					      REGEX_FREE_DOMAIN(KWORKER_TASK));
5942651712aSMickaël Salaün 		if (count >= 1)
5952651712aSMickaël Salaün 			break;
5962651712aSMickaël Salaün 		free(buf);
5972651712aSMickaël Salaün 		buf = NULL;
5982651712aSMickaël Salaün 	}
5992651712aSMickaël Salaün 
6002651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
6012651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
6022651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
6032651712aSMickaël Salaün 
6042651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
6052651712aSMickaël Salaün 	EXPECT_LE(1, count)
6062651712aSMickaël Salaün 	{
6072651712aSMickaël Salaün 		TH_LOG("Expected free_domain event, got %d\n%s", count, buf);
6082651712aSMickaël Salaün 	}
6092651712aSMickaël Salaün 
6102651712aSMickaël Salaün 	/* Verify denials count matches the single denial we triggered. */
6112651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK),
6122651712aSMickaël Salaün 					   "denials", denials_field,
6132651712aSMickaël Salaün 					   sizeof(denials_field)));
6142651712aSMickaël Salaün 	EXPECT_STREQ("1", denials_field);
6152651712aSMickaël Salaün 
6162651712aSMickaël Salaün 	free(buf);
6172651712aSMickaël Salaün }
6182651712aSMickaël Salaün 
6192651712aSMickaël Salaün /*
6202651712aSMickaël Salaün  * Verifies that deny_access_fs includes the enriched fields: same_exec and
6212651712aSMickaël Salaün  * logged.
6222651712aSMickaël Salaün  */
6232651712aSMickaël Salaün TEST_F(trace, deny_access_fs_fields)
6242651712aSMickaël Salaün {
6252651712aSMickaël Salaün 	char *buf;
6262651712aSMickaël Salaün 	char field_buf[64];
6272651712aSMickaël Salaün 
6282651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
6292651712aSMickaël Salaün 
6302651712aSMickaël Salaün 	/* Trigger a denial: rule for /usr, access /tmp. */
6312651712aSMickaël Salaün 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
6322651712aSMickaël Salaün 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
6332651712aSMickaël Salaün 
6342651712aSMickaël Salaün 	buf = tracefs_read_buf();
6352651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
6362651712aSMickaël Salaün 
6372651712aSMickaël Salaün 	/* Verify the enriched fields are present and have valid values. */
6382651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(
6392651712aSMickaël Salaün 			     buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "same_exec",
6402651712aSMickaël Salaün 			     field_buf, sizeof(field_buf)));
6412651712aSMickaël Salaün 	/* Child is the same exec that restricted itself. */
6422651712aSMickaël Salaün 	EXPECT_STREQ("1", field_buf);
6432651712aSMickaël Salaün 
6442651712aSMickaël Salaün 	/* Same exec with default flags: audit would log this denial. */
6452651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(
6462651712aSMickaël Salaün 			     buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "logged",
6472651712aSMickaël Salaün 			     field_buf, sizeof(field_buf)));
6482651712aSMickaël Salaün 	EXPECT_STREQ("1", field_buf);
6492651712aSMickaël Salaün 
6502651712aSMickaël Salaün 	free(buf);
6512651712aSMickaël Salaün }
6522651712aSMickaël Salaün 
6532651712aSMickaël Salaün /*
6542651712aSMickaël Salaün  * Verifies that same_exec is 1 (true) for denials from the same executable that
6552651712aSMickaël Salaün  * called landlock_restrict_self().
6562651712aSMickaël Salaün  */
6572651712aSMickaël Salaün TEST_F(trace, same_exec_before_exec)
6582651712aSMickaël Salaün {
6592651712aSMickaël Salaün 	pid_t pid;
6602651712aSMickaël Salaün 	int status;
6612651712aSMickaël Salaün 	char *buf;
6622651712aSMickaël Salaün 	char field[64];
6632651712aSMickaël Salaün 
6642651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
6652651712aSMickaël Salaün 
6662651712aSMickaël Salaün 	pid = fork();
6672651712aSMickaël Salaün 	ASSERT_LE(0, pid);
6682651712aSMickaël Salaün 
6692651712aSMickaël Salaün 	if (pid == 0) {
6702651712aSMickaël Salaün 		struct landlock_ruleset_attr attr = {
6712651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
6722651712aSMickaël Salaün 		};
6732651712aSMickaël Salaün 		int ruleset_fd, dir_fd;
6742651712aSMickaël Salaün 
6752651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
6762651712aSMickaël Salaün 		if (ruleset_fd < 0)
6772651712aSMickaël Salaün 			_exit(1);
6782651712aSMickaël Salaün 
6792651712aSMickaël Salaün 		/* No rules: all read_dir access is denied. */
6802651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
6812651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0))
6822651712aSMickaël Salaün 			_exit(1);
6832651712aSMickaël Salaün 		close(ruleset_fd);
6842651712aSMickaël Salaün 
6852651712aSMickaël Salaün 		/* Trigger denial without exec (same executable). */
6862651712aSMickaël Salaün 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
6872651712aSMickaël Salaün 		if (dir_fd >= 0)
6882651712aSMickaël Salaün 			close(dir_fd);
6892651712aSMickaël Salaün 		_exit(0);
6902651712aSMickaël Salaün 	}
6912651712aSMickaël Salaün 
6922651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
6932651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
6942651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
6952651712aSMickaël Salaün 
6962651712aSMickaël Salaün 	buf = tracefs_read_buf();
6972651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
6982651712aSMickaël Salaün 
6992651712aSMickaël Salaün 	/* Should have at least one deny_access_fs denial. */
7002651712aSMickaël Salaün 	EXPECT_LE(1,
7012651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)));
7022651712aSMickaël Salaün 
7032651712aSMickaël Salaün 	/* Verify same_exec=1 (same executable, no exec). */
7042651712aSMickaël Salaün 	ASSERT_EQ(0,
7052651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
7062651712aSMickaël Salaün 					"same_exec", field, sizeof(field)));
7072651712aSMickaël Salaün 	EXPECT_STREQ("1", field);
7082651712aSMickaël Salaün 
7092651712aSMickaël Salaün 	/* Same exec with default flags: audit would log this denial. */
7102651712aSMickaël Salaün 	ASSERT_EQ(0,
7112651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
7122651712aSMickaël Salaün 					"logged", field, sizeof(field)));
7132651712aSMickaël Salaün 	EXPECT_STREQ("1", field);
7142651712aSMickaël Salaün 
7152651712aSMickaël Salaün 	free(buf);
7162651712aSMickaël Salaün }
7172651712aSMickaël Salaün 
7182651712aSMickaël Salaün /*
7192651712aSMickaël Salaün  * Verifies that same_exec is 0 (false) for denials from a process that has
7202651712aSMickaël Salaün  * exec'd a new binary after landlock_restrict_self().  The sandboxed child
7212651712aSMickaël Salaün  * exec's true which opens "." and triggers a read_dir denial.  Covers the
7222651712aSMickaël Salaün  * "trace-only" visibility condition: with same_exec=0 and the default
7232651712aSMickaël Salaün  * log_new_exec=0, audit suppresses the denial (logged=0) but the trace event
7242651712aSMickaël Salaün  * still fires.
7252651712aSMickaël Salaün  */
7262651712aSMickaël Salaün TEST_F(trace, same_exec_after_exec)
7272651712aSMickaël Salaün {
7282651712aSMickaël Salaün 	char *buf;
7292651712aSMickaël Salaün 	char field[64];
7302651712aSMickaël Salaün 
7312651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
7322651712aSMickaël Salaün 
7332651712aSMickaël Salaün 	sandbox_child_exec_true(_metadata, 0);
7342651712aSMickaël Salaün 
7352651712aSMickaël Salaün 	buf = tracefs_read_buf();
7362651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
7372651712aSMickaël Salaün 
7382651712aSMickaël Salaün 	EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true")));
7392651712aSMickaël Salaün 
7402651712aSMickaël Salaün 	/* Verify same_exec=0 (different executable after exec). */
7412651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
7422651712aSMickaël Salaün 					   "same_exec", field, sizeof(field)));
7432651712aSMickaël Salaün 	EXPECT_STREQ("0", field);
7442651712aSMickaël Salaün 
7452651712aSMickaël Salaün 	/*
7462651712aSMickaël Salaün 	 * same_exec=0 with default log_new_exec=0: audit suppresses (logged=0).
7472651712aSMickaël Salaün 	 */
7482651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
7492651712aSMickaël Salaün 					   "logged", field, sizeof(field)));
7502651712aSMickaël Salaün 	EXPECT_STREQ("0", field);
7512651712aSMickaël Salaün 
7522651712aSMickaël Salaün 	free(buf);
7532651712aSMickaël Salaün }
7542651712aSMickaël Salaün 
7552651712aSMickaël Salaün /*
7562651712aSMickaël Salaün  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF suppresses logging
7572651712aSMickaël Salaün  * (logged=0) for a denial from the same executable.
7582651712aSMickaël Salaün  */
7592651712aSMickaël Salaün TEST_F(trace, log_flags_same_exec_off)
7602651712aSMickaël Salaün {
7612651712aSMickaël Salaün 	pid_t pid;
7622651712aSMickaël Salaün 	int status;
7632651712aSMickaël Salaün 	char *buf;
7642651712aSMickaël Salaün 	char field[64];
7652651712aSMickaël Salaün 
7662651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
7672651712aSMickaël Salaün 
7682651712aSMickaël Salaün 	pid = fork();
7692651712aSMickaël Salaün 	ASSERT_LE(0, pid);
7702651712aSMickaël Salaün 
7712651712aSMickaël Salaün 	if (pid == 0) {
7722651712aSMickaël Salaün 		struct landlock_ruleset_attr attr = {
7732651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
7742651712aSMickaël Salaün 		};
7752651712aSMickaël Salaün 		int ruleset_fd, dir_fd;
7762651712aSMickaël Salaün 
7772651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
7782651712aSMickaël Salaün 		if (ruleset_fd < 0)
7792651712aSMickaël Salaün 			_exit(1);
7802651712aSMickaël Salaün 
7812651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
7822651712aSMickaël Salaün 		if (landlock_restrict_self(
7832651712aSMickaël Salaün 			    ruleset_fd,
7842651712aSMickaël Salaün 			    LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF))
7852651712aSMickaël Salaün 			_exit(1);
7862651712aSMickaël Salaün 		close(ruleset_fd);
7872651712aSMickaël Salaün 
7882651712aSMickaël Salaün 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
7892651712aSMickaël Salaün 		if (dir_fd >= 0)
7902651712aSMickaël Salaün 			close(dir_fd);
7912651712aSMickaël Salaün 		_exit(0);
7922651712aSMickaël Salaün 	}
7932651712aSMickaël Salaün 
7942651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
7952651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
7962651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
7972651712aSMickaël Salaün 
7982651712aSMickaël Salaün 	buf = tracefs_read_buf();
7992651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
8002651712aSMickaël Salaün 
8012651712aSMickaël Salaün 	EXPECT_LE(1,
8022651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)));
8032651712aSMickaël Salaün 
8042651712aSMickaël Salaün 	/* Same-exec denial with LOG_SAME_EXEC_OFF: audit suppresses it. */
8052651712aSMickaël Salaün 	ASSERT_EQ(0,
8062651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
8072651712aSMickaël Salaün 					"logged", field, sizeof(field)));
8082651712aSMickaël Salaün 	EXPECT_STREQ("0", field);
8092651712aSMickaël Salaün 
8102651712aSMickaël Salaün 	free(buf);
8112651712aSMickaël Salaün }
8122651712aSMickaël Salaün 
8132651712aSMickaël Salaün /*
8142651712aSMickaël Salaün  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON causes a post-exec
8152651712aSMickaël Salaün  * denial to be logged (logged=1).  The child exec's true so that the denial
8162651712aSMickaël Salaün  * comes from a new executable (same_exec=0).
8172651712aSMickaël Salaün  */
8182651712aSMickaël Salaün TEST_F(trace, log_flags_new_exec_on)
8192651712aSMickaël Salaün {
8202651712aSMickaël Salaün 	char *buf;
8212651712aSMickaël Salaün 	char field[64];
8222651712aSMickaël Salaün 
8232651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
8242651712aSMickaël Salaün 
8252651712aSMickaël Salaün 	sandbox_child_exec_true(_metadata,
8262651712aSMickaël Salaün 				LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
8272651712aSMickaël Salaün 
8282651712aSMickaël Salaün 	buf = tracefs_read_buf();
8292651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
8302651712aSMickaël Salaün 
8312651712aSMickaël Salaün 	EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true")));
8322651712aSMickaël Salaün 
8332651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
8342651712aSMickaël Salaün 					   "same_exec", field, sizeof(field)));
8352651712aSMickaël Salaün 	EXPECT_STREQ("0", field);
8362651712aSMickaël Salaün 
8372651712aSMickaël Salaün 	/* LOG_NEW_EXEC_ON: the post-exec denial (same_exec=0) is logged. */
8382651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
8392651712aSMickaël Salaün 					   "logged", field, sizeof(field)));
8402651712aSMickaël Salaün 	EXPECT_STREQ("1", field);
8412651712aSMickaël Salaün 
8422651712aSMickaël Salaün 	free(buf);
8432651712aSMickaël Salaün }
8442651712aSMickaël Salaün 
8452651712aSMickaël Salaün /*
8462651712aSMickaël Salaün  * Verifies that denials suppressed by audit log flags are still counted in
8472651712aSMickaël Salaün  * num_denials.  The child restricts itself with default flags (log_same_exec=1,
8482651712aSMickaël Salaün  * log_new_exec=0), then execs true which attempts to read a denied directory.
8492651712aSMickaël Salaün  * After exec, same_exec=0 and log_new_exec=0, so audit suppresses the denial.
8502651712aSMickaël Salaün  * But the trace event fires unconditionally and free_domain must report the
8512651712aSMickaël Salaün  * correct denials count.
8522651712aSMickaël Salaün  */
8532651712aSMickaël Salaün TEST_F(trace, non_audit_visible_denial_counting)
8542651712aSMickaël Salaün {
8552651712aSMickaël Salaün 	char *buf = NULL;
8562651712aSMickaël Salaün 	char denials_field[32];
8572651712aSMickaël Salaün 	int count;
8582651712aSMickaël Salaün 
8592651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
8602651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear());
8612651712aSMickaël Salaün 	tracefs_clear_pid_filter();
8622651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
8632651712aSMickaël Salaün 
8642651712aSMickaël Salaün 	sandbox_child_exec_true(_metadata, 0);
8652651712aSMickaël Salaün 
8662651712aSMickaël Salaün 	/* Wait for free_domain event with retry. */
8672651712aSMickaël Salaün 	for (int retry = 0; retry < 10; retry++) {
8682651712aSMickaël Salaün 		usleep(100000);
8692651712aSMickaël Salaün 
8702651712aSMickaël Salaün 		set_cap(_metadata, CAP_SYS_ADMIN);
8712651712aSMickaël Salaün 		buf = tracefs_read_trace();
8722651712aSMickaël Salaün 		clear_cap(_metadata, CAP_SYS_ADMIN);
8732651712aSMickaël Salaün 		if (!buf)
8742651712aSMickaël Salaün 			break;
8752651712aSMickaël Salaün 
8762651712aSMickaël Salaün 		count = tracefs_count_matches(buf,
8772651712aSMickaël Salaün 					      REGEX_FREE_DOMAIN(KWORKER_TASK));
8782651712aSMickaël Salaün 		if (count >= 1)
8792651712aSMickaël Salaün 			break;
8802651712aSMickaël Salaün 		free(buf);
8812651712aSMickaël Salaün 		buf = NULL;
8822651712aSMickaël Salaün 	}
8832651712aSMickaël Salaün 
8842651712aSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
8852651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
8862651712aSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
8872651712aSMickaël Salaün 
8882651712aSMickaël Salaün 	/*
8892651712aSMickaël Salaün 	 * The denial happened after exec (same_exec=0), so audit would suppress
8902651712aSMickaël Salaün 	 * it.  But num_denials counts all denials regardless.
8912651712aSMickaël Salaün 	 */
8922651712aSMickaël Salaün 	ASSERT_NE(NULL, buf)
8932651712aSMickaël Salaün 	{
8942651712aSMickaël Salaün 		TH_LOG("free_domain event not found after 10 retries");
8952651712aSMickaël Salaün 	}
8962651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK),
8972651712aSMickaël Salaün 					   "denials", denials_field,
8982651712aSMickaël Salaün 					   sizeof(denials_field)));
8992651712aSMickaël Salaün 	EXPECT_STREQ("1", denials_field);
9002651712aSMickaël Salaün 
9012651712aSMickaël Salaün 	free(buf);
9022651712aSMickaël Salaün }
9032651712aSMickaël Salaün 
9042651712aSMickaël Salaün /*
9052651712aSMickaël Salaün  * Verifies that landlock_add_rule_net emits a trace event with the correct port
9062651712aSMickaël Salaün  * and allowed access mask fields.
9072651712aSMickaël Salaün  */
9082651712aSMickaël Salaün TEST_F(trace, add_rule_net_fields)
9092651712aSMickaël Salaün {
9102651712aSMickaël Salaün 	struct landlock_ruleset_attr ruleset_attr = {
9112651712aSMickaël Salaün 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
9122651712aSMickaël Salaün 	};
9132651712aSMickaël Salaün 	struct landlock_net_port_attr net_port = {
9142651712aSMickaël Salaün 		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
9152651712aSMickaël Salaün 		.port = 8080,
9162651712aSMickaël Salaün 	};
9172651712aSMickaël Salaün 	int ruleset_fd;
9182651712aSMickaël Salaün 	char *buf;
9192651712aSMickaël Salaün 	char field[64];
9202651712aSMickaël Salaün 
9212651712aSMickaël Salaün 	ruleset_fd =
9222651712aSMickaël Salaün 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
9232651712aSMickaël Salaün 	ASSERT_LE(0, ruleset_fd);
9242651712aSMickaël Salaün 
9252651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
9262651712aSMickaël Salaün 
9272651712aSMickaël Salaün 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
9282651712aSMickaël Salaün 				       &net_port, 0));
9292651712aSMickaël Salaün 	close(ruleset_fd);
9302651712aSMickaël Salaün 
9312651712aSMickaël Salaün 	buf = tracefs_read_buf();
9322651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
9332651712aSMickaël Salaün 
9342651712aSMickaël Salaün 	EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK)))
9352651712aSMickaël Salaün 	{
9362651712aSMickaël Salaün 		TH_LOG("Expected 1 add_rule_net event\n%s", buf);
9372651712aSMickaël Salaün 	}
9382651712aSMickaël Salaün 
9392651712aSMickaël Salaün 	/*
9402651712aSMickaël Salaün 	 * Verify the port is in host endianness, matching the UAPI convention
9412651712aSMickaël Salaün 	 * (landlock_net_port_attr.port).  On little-endian, htons(8080) is
9422651712aSMickaël Salaün 	 * 36895, so this comparison catches byte-order bugs.
9432651712aSMickaël Salaün 	 */
9442651712aSMickaël Salaün 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
9452651712aSMickaël Salaün 					   "port", field, sizeof(field)));
9462651712aSMickaël Salaün 	EXPECT_STREQ("8080", field);
9472651712aSMickaël Salaün 	/*
9482651712aSMickaël Salaün 	 * The allowed mask is the absolute value after transformation: the
9492651712aSMickaël Salaün 	 * user-requested BIND_TCP plus all unhandled access rights (the other
9502651712aSMickaël Salaün 	 * net access bits are unhandled because the ruleset only handles
9512651712aSMickaël Salaün 	 * BIND_TCP).
9522651712aSMickaël Salaün 	 */
9532651712aSMickaël Salaün 	EXPECT_EQ(0,
9542651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
9552651712aSMickaël Salaün 					"access_rights", field, sizeof(field)));
9562651712aSMickaël Salaün 	EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field);
9572651712aSMickaël Salaün 
9582651712aSMickaël Salaün 	free(buf);
9592651712aSMickaël Salaün }
9602651712aSMickaël Salaün 
9612651712aSMickaël Salaün /*
9622651712aSMickaël Salaün  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF suppresses audit
9632651712aSMickaël Salaün  * logging for child domains (logged=0) even though the child's own
9642651712aSMickaël Salaün  * per-execution flags are the defaults, while the trace event still fires
9652651712aSMickaël Salaün  * (tracing is unconditional).  The parent creates a domain with
9662651712aSMickaël Salaün  * LOG_SUBDOMAINS_OFF, then the child creates a sub-domain and triggers a
9672651712aSMickaël Salaün  * denial.
9682651712aSMickaël Salaün  */
9692651712aSMickaël Salaün TEST_F(trace, log_flags_subdomains_off)
9702651712aSMickaël Salaün {
9712651712aSMickaël Salaün 	pid_t pid;
9722651712aSMickaël Salaün 	int status;
9732651712aSMickaël Salaün 	char *buf;
9742651712aSMickaël Salaün 	char field[64];
9752651712aSMickaël Salaün 
9762651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
9772651712aSMickaël Salaün 
9782651712aSMickaël Salaün 	pid = fork();
9792651712aSMickaël Salaün 	ASSERT_LE(0, pid);
9802651712aSMickaël Salaün 
9812651712aSMickaël Salaün 	if (pid == 0) {
9822651712aSMickaël Salaün 		struct landlock_ruleset_attr attr = {
9832651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
9842651712aSMickaël Salaün 		};
9852651712aSMickaël Salaün 		int parent_fd, child_fd, dir_fd;
9862651712aSMickaël Salaün 
9872651712aSMickaël Salaün 		/* Parent domain with LOG_SUBDOMAINS_OFF. */
9882651712aSMickaël Salaün 		parent_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
9892651712aSMickaël Salaün 		if (parent_fd < 0)
9902651712aSMickaël Salaün 			_exit(1);
9912651712aSMickaël Salaün 
9922651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
9932651712aSMickaël Salaün 		if (landlock_restrict_self(
9942651712aSMickaël Salaün 			    parent_fd,
9952651712aSMickaël Salaün 			    LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF))
9962651712aSMickaël Salaün 			_exit(1);
9972651712aSMickaël Salaün 		close(parent_fd);
9982651712aSMickaël Salaün 
9992651712aSMickaël Salaün 		/* Child sub-domain with default flags. */
10002651712aSMickaël Salaün 		child_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
10012651712aSMickaël Salaün 		if (child_fd < 0)
10022651712aSMickaël Salaün 			_exit(1);
10032651712aSMickaël Salaün 
10042651712aSMickaël Salaün 		if (landlock_restrict_self(child_fd, 0))
10052651712aSMickaël Salaün 			_exit(1);
10062651712aSMickaël Salaün 		close(child_fd);
10072651712aSMickaël Salaün 
10082651712aSMickaël Salaün 		/* Trigger a denial from the child domain. */
10092651712aSMickaël Salaün 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
10102651712aSMickaël Salaün 		if (dir_fd >= 0)
10112651712aSMickaël Salaün 			close(dir_fd);
10122651712aSMickaël Salaün 		_exit(0);
10132651712aSMickaël Salaün 	}
10142651712aSMickaël Salaün 
10152651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
10162651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
10172651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
10182651712aSMickaël Salaün 
10192651712aSMickaël Salaün 	buf = tracefs_read_buf();
10202651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
10212651712aSMickaël Salaün 
10222651712aSMickaël Salaün 	/*
10232651712aSMickaël Salaün 	 * Trace fires unconditionally even though audit is disabled for the
10242651712aSMickaël Salaün 	 * child domain (parent had LOG_SUBDOMAINS_OFF).
10252651712aSMickaël Salaün 	 */
10262651712aSMickaël Salaün 	EXPECT_LE(1,
10272651712aSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)))
10282651712aSMickaël Salaün 	{
10292651712aSMickaël Salaün 		TH_LOG("Expected deny_access_fs event despite "
10302651712aSMickaël Salaün 		       "LOG_SUBDOMAINS_OFF\n%s",
10312651712aSMickaël Salaün 		       buf);
10322651712aSMickaël Salaün 	}
10332651712aSMickaël Salaün 
10342651712aSMickaël Salaün 	/*
10352651712aSMickaël Salaün 	 * The child's per-execution flags default to logging, but the
10362651712aSMickaël Salaün 	 * ancestor's LOG_SUBDOMAINS_OFF disables it, so audit suppresses this
10372651712aSMickaël Salaün 	 * denial (logged=0).  This is exactly the case the single logged field
10382651712aSMickaël Salaün 	 * captures and the raw per-execution flags could not.
10392651712aSMickaël Salaün 	 */
10402651712aSMickaël Salaün 	ASSERT_EQ(0,
10412651712aSMickaël Salaün 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
10422651712aSMickaël Salaün 					"logged", field, sizeof(field)));
10432651712aSMickaël Salaün 	EXPECT_STREQ("0", field);
10442651712aSMickaël Salaün 
10452651712aSMickaël Salaün 	free(buf);
10462651712aSMickaël Salaün }
10472651712aSMickaël Salaün 
10482651712aSMickaël Salaün /* Verifies that landlock_free_ruleset fires when a ruleset FD is closed. */
10492651712aSMickaël Salaün TEST_F(trace, free_ruleset_on_close)
10502651712aSMickaël Salaün {
10512651712aSMickaël Salaün 	struct landlock_ruleset_attr ruleset_attr = {
10522651712aSMickaël Salaün 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
10532651712aSMickaël Salaün 	};
10542651712aSMickaël Salaün 	int ruleset_fd;
10552651712aSMickaël Salaün 	char *buf;
10562651712aSMickaël Salaün 
10572651712aSMickaël Salaün 	ruleset_fd =
10582651712aSMickaël Salaün 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
10592651712aSMickaël Salaün 	ASSERT_LE(0, ruleset_fd);
10602651712aSMickaël Salaün 
10612651712aSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
10622651712aSMickaël Salaün 
10632651712aSMickaël Salaün 	/* Closing the FD should trigger free_ruleset. */
10642651712aSMickaël Salaün 	close(ruleset_fd);
10652651712aSMickaël Salaün 
10662651712aSMickaël Salaün 	buf = tracefs_read_buf();
10672651712aSMickaël Salaün 	ASSERT_NE(NULL, buf);
10682651712aSMickaël Salaün 
10692651712aSMickaël Salaün 	EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_FREE_RULESET(TRACE_TASK)))
10702651712aSMickaël Salaün 	{
10712651712aSMickaël Salaün 		TH_LOG("Expected 1 free_ruleset event\n%s", buf);
10722651712aSMickaël Salaün 	}
10732651712aSMickaël Salaün 
10742651712aSMickaël Salaün 	free(buf);
10752651712aSMickaël Salaün }
10762651712aSMickaël Salaün 
10772651712aSMickaël Salaün /*
1078*ac6d193dSMickaël Salaün  * Counts landlock_enforce_domain lines, filtered by @domain (NULL matches any),
1079*ac6d193dSMickaël Salaün  * @complete and @process_wide (a negative value matches any).  Builds the
1080*ac6d193dSMickaël Salaün  * anchored regex dynamically so a single helper covers every field assertion.
1081*ac6d193dSMickaël Salaün  */
1082*ac6d193dSMickaël Salaün static int count_enforce_matches(const char *buf, const char *domain,
1083*ac6d193dSMickaël Salaün 				 int complete, int process_wide,
1084*ac6d193dSMickaël Salaün 				 int no_new_privs)
1085*ac6d193dSMickaël Salaün {
1086*ac6d193dSMickaël Salaün 	char pattern[512], dom[80], comp[8], pw[8], nnp[8];
1087*ac6d193dSMickaël Salaün 
1088*ac6d193dSMickaël Salaün 	if (domain)
1089*ac6d193dSMickaël Salaün 		snprintf(dom, sizeof(dom), "%s", domain);
1090*ac6d193dSMickaël Salaün 	else
1091*ac6d193dSMickaël Salaün 		snprintf(dom, sizeof(dom), "[0-9a-f]\\+");
1092*ac6d193dSMickaël Salaün 	if (complete < 0)
1093*ac6d193dSMickaël Salaün 		snprintf(comp, sizeof(comp), "[01]");
1094*ac6d193dSMickaël Salaün 	else
1095*ac6d193dSMickaël Salaün 		snprintf(comp, sizeof(comp), "%d", complete);
1096*ac6d193dSMickaël Salaün 	if (process_wide < 0)
1097*ac6d193dSMickaël Salaün 		snprintf(pw, sizeof(pw), "[01]");
1098*ac6d193dSMickaël Salaün 	else
1099*ac6d193dSMickaël Salaün 		snprintf(pw, sizeof(pw), "%d", process_wide);
1100*ac6d193dSMickaël Salaün 	if (no_new_privs < 0)
1101*ac6d193dSMickaël Salaün 		snprintf(nnp, sizeof(nnp), "[01]");
1102*ac6d193dSMickaël Salaün 	else
1103*ac6d193dSMickaël Salaün 		snprintf(nnp, sizeof(nnp), "%d", no_new_privs);
1104*ac6d193dSMickaël Salaün 
1105*ac6d193dSMickaël Salaün 	snprintf(pattern, sizeof(pattern),
1106*ac6d193dSMickaël Salaün 		 TRACE_PREFIX(TRACE_TASK) "landlock_enforce_domain: "
1107*ac6d193dSMickaël Salaün 					  "domain=%s "
1108*ac6d193dSMickaël Salaün 					  "complete=%s process_wide=%s "
1109*ac6d193dSMickaël Salaün 					  "no_new_privs=%s$",
1110*ac6d193dSMickaël Salaün 		 dom, comp, pw, nnp);
1111*ac6d193dSMickaël Salaün 	return tracefs_count_matches(buf, pattern);
1112*ac6d193dSMickaël Salaün }
1113*ac6d193dSMickaël Salaün 
1114*ac6d193dSMickaël Salaün /* Idle sibling: waits on the barrier so it is a live thread, then sleeps. */
1115*ac6d193dSMickaël Salaün static void *enforce_idle(void *arg)
1116*ac6d193dSMickaël Salaün {
1117*ac6d193dSMickaël Salaün 	pthread_barrier_t *barrier = arg;
1118*ac6d193dSMickaël Salaün 
1119*ac6d193dSMickaël Salaün 	pthread_barrier_wait(barrier);
1120*ac6d193dSMickaël Salaün 	while (true)
1121*ac6d193dSMickaël Salaün 		sleep(1);
1122*ac6d193dSMickaël Salaün 	return NULL;
1123*ac6d193dSMickaël Salaün }
1124*ac6d193dSMickaël Salaün 
1125*ac6d193dSMickaël Salaün /*
1126*ac6d193dSMickaël Salaün  * Child body: spawns @nthreads idle siblings (barrier-synchronized so they are
1127*ac6d193dSMickaël Salaün  * live when the syscall runs), then enforces a domain with @flags.  Returns 0
1128*ac6d193dSMickaël Salaün  * on success; the process exits afterwards, reaping the siblings.
1129*ac6d193dSMickaël Salaün  */
1130*ac6d193dSMickaël Salaün static int child_enforce(int nthreads, __u32 flags)
1131*ac6d193dSMickaël Salaün {
1132*ac6d193dSMickaël Salaün 	pthread_t threads[8];
1133*ac6d193dSMickaël Salaün 	pthread_barrier_t barrier;
1134*ac6d193dSMickaël Salaün 	int ruleset_fd, i;
1135*ac6d193dSMickaël Salaün 
1136*ac6d193dSMickaël Salaün 	if (nthreads > 0) {
1137*ac6d193dSMickaël Salaün 		if (pthread_barrier_init(&barrier, NULL, nthreads + 1))
1138*ac6d193dSMickaël Salaün 			return 1;
1139*ac6d193dSMickaël Salaün 		for (i = 0; i < nthreads; i++)
1140*ac6d193dSMickaël Salaün 			if (pthread_create(&threads[i], NULL, enforce_idle,
1141*ac6d193dSMickaël Salaün 					   &barrier))
1142*ac6d193dSMickaël Salaün 				return 1;
1143*ac6d193dSMickaël Salaün 		pthread_barrier_wait(&barrier);
1144*ac6d193dSMickaël Salaün 	}
1145*ac6d193dSMickaël Salaün 
1146*ac6d193dSMickaël Salaün 	ruleset_fd = build_enforce_ruleset();
1147*ac6d193dSMickaël Salaün 	if (ruleset_fd < 0)
1148*ac6d193dSMickaël Salaün 		return 1;
1149*ac6d193dSMickaël Salaün 
1150*ac6d193dSMickaël Salaün 	/*
1151*ac6d193dSMickaël Salaün 	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs itself, so skip
1152*ac6d193dSMickaël Salaün 	 * the prctl() to exercise that path; otherwise Landlock requires
1153*ac6d193dSMickaël Salaün 	 * no_new_privs up front.
1154*ac6d193dSMickaël Salaün 	 */
1155*ac6d193dSMickaël Salaün 	if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS))
1156*ac6d193dSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1157*ac6d193dSMickaël Salaün 	if (landlock_restrict_self(ruleset_fd, flags))
1158*ac6d193dSMickaël Salaün 		return 1;
1159*ac6d193dSMickaël Salaün 	close(ruleset_fd);
1160*ac6d193dSMickaël Salaün 	return 0;
1161*ac6d193dSMickaël Salaün }
1162*ac6d193dSMickaël Salaün 
1163*ac6d193dSMickaël Salaün /*
1164*ac6d193dSMickaël Salaün  * Runs in a spawned thread after the group leader called pthread_exit().  The
1165*ac6d193dSMickaël Salaün  * leader lingers as an un-reaped zombie, so get_nr_threads() still counts it
1166*ac6d193dSMickaël Salaün  * and this non-leader is not the only thread; enforcing here therefore reports
1167*ac6d193dSMickaël Salaün  * process_wide=0.
1168*ac6d193dSMickaël Salaün  */
1169*ac6d193dSMickaël Salaün static void *enforce_nonleader(void *arg)
1170*ac6d193dSMickaël Salaün {
1171*ac6d193dSMickaël Salaün 	int ruleset_fd;
1172*ac6d193dSMickaël Salaün 
1173*ac6d193dSMickaël Salaün 	ruleset_fd = build_enforce_ruleset();
1174*ac6d193dSMickaël Salaün 	if (ruleset_fd < 0)
1175*ac6d193dSMickaël Salaün 		_exit(1);
1176*ac6d193dSMickaël Salaün 	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1177*ac6d193dSMickaël Salaün 	if (landlock_restrict_self(ruleset_fd, 0))
1178*ac6d193dSMickaël Salaün 		_exit(1);
1179*ac6d193dSMickaël Salaün 	_exit(0);
1180*ac6d193dSMickaël Salaün }
1181*ac6d193dSMickaël Salaün 
1182*ac6d193dSMickaël Salaün /*
1183*ac6d193dSMickaël Salaün  * Collapses the enforce_domain field cases into one parametrized test.  Each
1184*ac6d193dSMickaël Salaün  * variant runs child_enforce(nthreads, flags) and checks the resulting
1185*ac6d193dSMickaël Salaün  * enforce_domain events.  The flags column also selects how no_new_privs is
1186*ac6d193dSMickaël Salaün  * set: with LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS child_enforce() skips the
1187*ac6d193dSMickaël Salaün  * prctl() so the flag sets it (and, with TSYNC, propagates to the siblings);
1188*ac6d193dSMickaël Salaün  * otherwise a prior prctl() sets it on the caller (and TSYNC propagates that).
1189*ac6d193dSMickaël Salaün  */
1190*ac6d193dSMickaël Salaün 
1191*ac6d193dSMickaël Salaün /* clang-format off */
1192*ac6d193dSMickaël Salaün FIXTURE(trace_enforce) {
1193*ac6d193dSMickaël Salaün 	/* clang-format on */
1194*ac6d193dSMickaël Salaün 	int tracefs_ok;
1195*ac6d193dSMickaël Salaün };
1196*ac6d193dSMickaël Salaün 
1197*ac6d193dSMickaël Salaün FIXTURE_SETUP(trace_enforce)
1198*ac6d193dSMickaël Salaün {
1199*ac6d193dSMickaël Salaün 	int ret;
1200*ac6d193dSMickaël Salaün 
1201*ac6d193dSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
1202*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, unshare(CLONE_NEWNS));
1203*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
1204*ac6d193dSMickaël Salaün 
1205*ac6d193dSMickaël Salaün 	ret = tracefs_fixture_setup();
1206*ac6d193dSMickaël Salaün 	if (ret) {
1207*ac6d193dSMickaël Salaün 		clear_cap(_metadata, CAP_SYS_ADMIN);
1208*ac6d193dSMickaël Salaün 		self->tracefs_ok = 0;
1209*ac6d193dSMickaël Salaün 		SKIP(return, "tracefs not available");
1210*ac6d193dSMickaël Salaün 	}
1211*ac6d193dSMickaël Salaün 	self->tracefs_ok = 1;
1212*ac6d193dSMickaël Salaün 
1213*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
1214*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
1215*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
1216*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear());
1217*ac6d193dSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
1218*ac6d193dSMickaël Salaün }
1219*ac6d193dSMickaël Salaün 
1220*ac6d193dSMickaël Salaün FIXTURE_TEARDOWN(trace_enforce)
1221*ac6d193dSMickaël Salaün {
1222*ac6d193dSMickaël Salaün 	if (!self->tracefs_ok)
1223*ac6d193dSMickaël Salaün 		return;
1224*ac6d193dSMickaël Salaün 
1225*ac6d193dSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
1226*ac6d193dSMickaël Salaün 	tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
1227*ac6d193dSMickaël Salaün 	tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
1228*ac6d193dSMickaël Salaün 	tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
1229*ac6d193dSMickaël Salaün 	tracefs_fixture_teardown();
1230*ac6d193dSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
1231*ac6d193dSMickaël Salaün }
1232*ac6d193dSMickaël Salaün 
1233*ac6d193dSMickaël Salaün /* clang-format off */
1234*ac6d193dSMickaël Salaün FIXTURE_VARIANT(trace_enforce) {
1235*ac6d193dSMickaël Salaün 	/* clang-format on */
1236*ac6d193dSMickaël Salaün 	/* Inputs to child_enforce(). */
1237*ac6d193dSMickaël Salaün 	int nthreads;
1238*ac6d193dSMickaël Salaün 	__u32 flags;
1239*ac6d193dSMickaël Salaün 	/* Expected enforce_domain event counts. */
1240*ac6d193dSMickaël Salaün 	int total;
1241*ac6d193dSMickaël Salaün 	int complete;
1242*ac6d193dSMickaël Salaün 	int process_wide;
1243*ac6d193dSMickaël Salaün 	int no_new_privs;
1244*ac6d193dSMickaël Salaün };
1245*ac6d193dSMickaël Salaün 
1246*ac6d193dSMickaël Salaün /* clang-format off */
1247*ac6d193dSMickaël Salaün 
1248*ac6d193dSMickaël Salaün /* Single thread, no flags: prctl-backed no_new_privs. */
1249*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, single) {
1250*ac6d193dSMickaël Salaün 	.nthreads = 0, .flags = 0,
1251*ac6d193dSMickaël Salaün 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1252*ac6d193dSMickaël Salaün };
1253*ac6d193dSMickaël Salaün 
1254*ac6d193dSMickaël Salaün /* Single thread: the NO_NEW_PRIVS flag sets no_new_privs (no prctl). */
1255*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, no_new_privs) {
1256*ac6d193dSMickaël Salaün 	.nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
1257*ac6d193dSMickaël Salaün 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1258*ac6d193dSMickaël Salaün };
1259*ac6d193dSMickaël Salaün 
1260*ac6d193dSMickaël Salaün /* TSYNC on a lone thread still concludes, process-wide. */
1261*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_single) {
1262*ac6d193dSMickaël Salaün 	.nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_TSYNC,
1263*ac6d193dSMickaël Salaün 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1264*ac6d193dSMickaël Salaün };
1265*ac6d193dSMickaël Salaün 
1266*ac6d193dSMickaël Salaün /* TSYNC sweeps N siblings; the caller's prctl-backed nnp propagates to all. */
1267*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_multithread) {
1268*ac6d193dSMickaël Salaün 	.nthreads = 3, .flags = LANDLOCK_RESTRICT_SELF_TSYNC,
1269*ac6d193dSMickaël Salaün 	.total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4,
1270*ac6d193dSMickaël Salaün };
1271*ac6d193dSMickaël Salaün 
1272*ac6d193dSMickaël Salaün /* TSYNC + NO_NEW_PRIVS flag sets nnp on the caller and every swept sibling. */
1273*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_no_new_privs) {
1274*ac6d193dSMickaël Salaün 	.nthreads = 3,
1275*ac6d193dSMickaël Salaün 	.flags = LANDLOCK_RESTRICT_SELF_TSYNC | LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
1276*ac6d193dSMickaël Salaün 	.total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4,
1277*ac6d193dSMickaël Salaün };
1278*ac6d193dSMickaël Salaün 
1279*ac6d193dSMickaël Salaün /* Non-TSYNC on a multi-threaded process enforces only the caller. */
1280*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, multithread_non_tsync) {
1281*ac6d193dSMickaël Salaün 	.nthreads = 3, .flags = 0,
1282*ac6d193dSMickaël Salaün 	.total = 1, .complete = 1, .process_wide = 0, .no_new_privs = 1,
1283*ac6d193dSMickaël Salaün };
1284*ac6d193dSMickaël Salaün 
1285*ac6d193dSMickaël Salaün /* clang-format on */
1286*ac6d193dSMickaël Salaün 
1287*ac6d193dSMickaël Salaün /*
1288*ac6d193dSMickaël Salaün  * One create_domain and variant->total enforce_domain events sharing that
1289*ac6d193dSMickaël Salaün  * domain ID; complete=1 marks the single concluding event, and the process_wide
1290*ac6d193dSMickaël Salaün  * / no_new_privs counts match the variant.  Counts are order-independent,
1291*ac6d193dSMickaël Salaün  * evaluated after the syscall returns.
1292*ac6d193dSMickaël Salaün  */
1293*ac6d193dSMickaël Salaün TEST_F(trace_enforce, enforce)
1294*ac6d193dSMickaël Salaün {
1295*ac6d193dSMickaël Salaün 	pid_t pid;
1296*ac6d193dSMickaël Salaün 	int status;
1297*ac6d193dSMickaël Salaün 	char *buf;
1298*ac6d193dSMickaël Salaün 	char domain[64];
1299*ac6d193dSMickaël Salaün 
1300*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
1301*ac6d193dSMickaël Salaün 
1302*ac6d193dSMickaël Salaün 	pid = fork();
1303*ac6d193dSMickaël Salaün 	ASSERT_LE(0, pid);
1304*ac6d193dSMickaël Salaün 	if (pid == 0)
1305*ac6d193dSMickaël Salaün 		_exit(child_enforce(variant->nthreads, variant->flags));
1306*ac6d193dSMickaël Salaün 
1307*ac6d193dSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1308*ac6d193dSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
1309*ac6d193dSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
1310*ac6d193dSMickaël Salaün 
1311*ac6d193dSMickaël Salaün 	buf = tracefs_read_buf();
1312*ac6d193dSMickaël Salaün 	ASSERT_NE(NULL, buf);
1313*ac6d193dSMickaël Salaün 
1314*ac6d193dSMickaël Salaün 	EXPECT_EQ(1,
1315*ac6d193dSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1316*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->total, count_enforce_matches(buf, NULL, -1, -1, -1))
1317*ac6d193dSMickaël Salaün 	{
1318*ac6d193dSMickaël Salaün 		TH_LOG("Expected %d enforce_domain events\n%s", variant->total,
1319*ac6d193dSMickaël Salaün 		       buf);
1320*ac6d193dSMickaël Salaün 	}
1321*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->complete,
1322*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, NULL, 1, -1, -1));
1323*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->total - variant->complete,
1324*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, NULL, 0, -1, -1));
1325*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->process_wide,
1326*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, NULL, -1, 1, -1));
1327*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->total - variant->process_wide,
1328*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, NULL, -1, 0, -1));
1329*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->no_new_privs,
1330*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, NULL, -1, -1, 1));
1331*ac6d193dSMickaël Salaün 
1332*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
1333*ac6d193dSMickaël Salaün 					   "domain", domain, sizeof(domain)));
1334*ac6d193dSMickaël Salaün 	EXPECT_EQ(variant->total,
1335*ac6d193dSMickaël Salaün 		  count_enforce_matches(buf, domain, -1, -1, -1));
1336*ac6d193dSMickaël Salaün 
1337*ac6d193dSMickaël Salaün 	free(buf);
1338*ac6d193dSMickaël Salaün }
1339*ac6d193dSMickaël Salaün 
1340*ac6d193dSMickaël Salaün /*
1341*ac6d193dSMickaël Salaün  * A non-leader thread enforcing a domain while the group leader lingers as an
1342*ac6d193dSMickaël Salaün  * un-reaped zombie reports process_wide=0: get_nr_threads() counts the zombie
1343*ac6d193dSMickaël Salaün  * leader, so the group is not single-threaded.  This is the reachable half of
1344*ac6d193dSMickaël Salaün  * the caveat that process_wide==0 never proves the process is multi-threaded
1345*ac6d193dSMickaël Salaün  * (get_nr_threads(), unlike the leader-relative thread_group_empty(), counts
1346*ac6d193dSMickaël Salaün  * the zombie leader).
1347*ac6d193dSMickaël Salaün  */
1348*ac6d193dSMickaël Salaün TEST_F(trace, enforce_single_non_leader)
1349*ac6d193dSMickaël Salaün {
1350*ac6d193dSMickaël Salaün 	pid_t pid;
1351*ac6d193dSMickaël Salaün 	int status;
1352*ac6d193dSMickaël Salaün 	char *buf;
1353*ac6d193dSMickaël Salaün 
1354*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
1355*ac6d193dSMickaël Salaün 
1356*ac6d193dSMickaël Salaün 	pid = fork();
1357*ac6d193dSMickaël Salaün 	ASSERT_LE(0, pid);
1358*ac6d193dSMickaël Salaün 	if (pid == 0) {
1359*ac6d193dSMickaël Salaün 		pthread_t worker;
1360*ac6d193dSMickaël Salaün 
1361*ac6d193dSMickaël Salaün 		if (pthread_create(&worker, NULL, enforce_nonleader, NULL))
1362*ac6d193dSMickaël Salaün 			_exit(1);
1363*ac6d193dSMickaël Salaün 		/* Leader leaves; the worker enforces as a non-leader. */
1364*ac6d193dSMickaël Salaün 		pthread_exit(NULL);
1365*ac6d193dSMickaël Salaün 	}
1366*ac6d193dSMickaël Salaün 
1367*ac6d193dSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1368*ac6d193dSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
1369*ac6d193dSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
1370*ac6d193dSMickaël Salaün 
1371*ac6d193dSMickaël Salaün 	buf = tracefs_read_buf();
1372*ac6d193dSMickaël Salaün 	ASSERT_NE(NULL, buf);
1373*ac6d193dSMickaël Salaün 
1374*ac6d193dSMickaël Salaün 	EXPECT_EQ(1,
1375*ac6d193dSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1376*ac6d193dSMickaël Salaün 	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 0, -1))
1377*ac6d193dSMickaël Salaün 	{
1378*ac6d193dSMickaël Salaün 		TH_LOG("Expected complete=1 process_wide=0 for non-leader\n%s",
1379*ac6d193dSMickaël Salaün 		       buf);
1380*ac6d193dSMickaël Salaün 	}
1381*ac6d193dSMickaël Salaün 
1382*ac6d193dSMickaël Salaün 	free(buf);
1383*ac6d193dSMickaël Salaün }
1384*ac6d193dSMickaël Salaün 
1385*ac6d193dSMickaël Salaün /*
1386*ac6d193dSMickaël Salaün  * Verifies the flags-only path (ruleset_fd == -1) creates no domain and emits
1387*ac6d193dSMickaël Salaün  * neither create_domain nor enforce_domain, with and without TSYNC.
1388*ac6d193dSMickaël Salaün  */
1389*ac6d193dSMickaël Salaün TEST_F(trace, enforce_flags_only)
1390*ac6d193dSMickaël Salaün {
1391*ac6d193dSMickaël Salaün 	pid_t pid;
1392*ac6d193dSMickaël Salaün 	int status;
1393*ac6d193dSMickaël Salaün 	char *buf;
1394*ac6d193dSMickaël Salaün 
1395*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
1396*ac6d193dSMickaël Salaün 
1397*ac6d193dSMickaël Salaün 	pid = fork();
1398*ac6d193dSMickaël Salaün 	ASSERT_LE(0, pid);
1399*ac6d193dSMickaël Salaün 	if (pid == 0) {
1400*ac6d193dSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1401*ac6d193dSMickaël Salaün 		if (landlock_restrict_self(
1402*ac6d193dSMickaël Salaün 			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF))
1403*ac6d193dSMickaël Salaün 			_exit(1);
1404*ac6d193dSMickaël Salaün 		if (landlock_restrict_self(
1405*ac6d193dSMickaël Salaün 			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
1406*ac6d193dSMickaël Salaün 					LANDLOCK_RESTRICT_SELF_TSYNC))
1407*ac6d193dSMickaël Salaün 			_exit(1);
1408*ac6d193dSMickaël Salaün 		_exit(0);
1409*ac6d193dSMickaël Salaün 	}
1410*ac6d193dSMickaël Salaün 
1411*ac6d193dSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1412*ac6d193dSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
1413*ac6d193dSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
1414*ac6d193dSMickaël Salaün 
1415*ac6d193dSMickaël Salaün 	buf = tracefs_read_buf();
1416*ac6d193dSMickaël Salaün 	ASSERT_NE(NULL, buf);
1417*ac6d193dSMickaël Salaün 
1418*ac6d193dSMickaël Salaün 	EXPECT_EQ(0,
1419*ac6d193dSMickaël Salaün 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1420*ac6d193dSMickaël Salaün 	EXPECT_EQ(0, count_enforce_matches(buf, NULL, -1, -1, -1))
1421*ac6d193dSMickaël Salaün 	{
1422*ac6d193dSMickaël Salaün 		TH_LOG("No enforce_domain expected on flags-only path\n%s",
1423*ac6d193dSMickaël Salaün 		       buf);
1424*ac6d193dSMickaël Salaün 	}
1425*ac6d193dSMickaël Salaün 
1426*ac6d193dSMickaël Salaün 	free(buf);
1427*ac6d193dSMickaël Salaün }
1428*ac6d193dSMickaël Salaün 
1429*ac6d193dSMickaël Salaün static void enforce_nop_handler(int sig)
1430*ac6d193dSMickaël Salaün {
1431*ac6d193dSMickaël Salaün }
1432*ac6d193dSMickaël Salaün 
1433*ac6d193dSMickaël Salaün struct abort_signaler_data {
1434*ac6d193dSMickaël Salaün 	pthread_t target;
1435*ac6d193dSMickaël Salaün 	volatile bool stop;
1436*ac6d193dSMickaël Salaün };
1437*ac6d193dSMickaël Salaün 
1438*ac6d193dSMickaël Salaün /*
1439*ac6d193dSMickaël Salaün  * Hammers the target thread with SIGUSR1 to interrupt the TSYNC prepare wait.
1440*ac6d193dSMickaël Salaün  */
1441*ac6d193dSMickaël Salaün static void *abort_signaler(void *arg)
1442*ac6d193dSMickaël Salaün {
1443*ac6d193dSMickaël Salaün 	struct abort_signaler_data *data = arg;
1444*ac6d193dSMickaël Salaün 
1445*ac6d193dSMickaël Salaün 	while (!data->stop)
1446*ac6d193dSMickaël Salaün 		pthread_kill(data->target, SIGUSR1);
1447*ac6d193dSMickaël Salaün 	return NULL;
1448*ac6d193dSMickaël Salaün }
1449*ac6d193dSMickaël Salaün 
1450*ac6d193dSMickaël Salaün /*
1451*ac6d193dSMickaël Salaün  * Child body for the abort test: with idle siblings and a signaler interrupting
1452*ac6d193dSMickaël Salaün  * it, repeatedly enforces under TSYNC.  An interrupted attempt aborts its
1453*ac6d193dSMickaël Salaün  * just-created domain (create_domain + free_domain, zero enforce_domain) while
1454*ac6d193dSMickaël Salaün  * -ERESTARTNOINTR transparently restarts the syscall, so a successful retry may
1455*ac6d193dSMickaël Salaün  * add its own full lifecycle.
1456*ac6d193dSMickaël Salaün  */
1457*ac6d193dSMickaël Salaün static int child_abort(int nsiblings, int attempts)
1458*ac6d193dSMickaël Salaün {
1459*ac6d193dSMickaël Salaün 	pthread_t threads[200];
1460*ac6d193dSMickaël Salaün 	pthread_t signaler;
1461*ac6d193dSMickaël Salaün 	pthread_barrier_t barrier;
1462*ac6d193dSMickaël Salaün 	struct abort_signaler_data data = {};
1463*ac6d193dSMickaël Salaün 	struct sigaction sa = {};
1464*ac6d193dSMickaël Salaün 	int i;
1465*ac6d193dSMickaël Salaün 
1466*ac6d193dSMickaël Salaün 	sa.sa_handler = enforce_nop_handler;
1467*ac6d193dSMickaël Salaün 	if (sigaction(SIGUSR1, &sa, NULL))
1468*ac6d193dSMickaël Salaün 		return 1;
1469*ac6d193dSMickaël Salaün 
1470*ac6d193dSMickaël Salaün 	if (pthread_barrier_init(&barrier, NULL, nsiblings + 1))
1471*ac6d193dSMickaël Salaün 		return 1;
1472*ac6d193dSMickaël Salaün 	for (i = 0; i < nsiblings; i++)
1473*ac6d193dSMickaël Salaün 		if (pthread_create(&threads[i], NULL, enforce_idle, &barrier))
1474*ac6d193dSMickaël Salaün 			return 1;
1475*ac6d193dSMickaël Salaün 	pthread_barrier_wait(&barrier);
1476*ac6d193dSMickaël Salaün 
1477*ac6d193dSMickaël Salaün 	data.target = pthread_self();
1478*ac6d193dSMickaël Salaün 	if (pthread_create(&signaler, NULL, abort_signaler, &data))
1479*ac6d193dSMickaël Salaün 		return 1;
1480*ac6d193dSMickaël Salaün 
1481*ac6d193dSMickaël Salaün 	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1482*ac6d193dSMickaël Salaün 	for (i = 0; i < attempts; i++) {
1483*ac6d193dSMickaël Salaün 		int ruleset_fd = build_enforce_ruleset();
1484*ac6d193dSMickaël Salaün 
1485*ac6d193dSMickaël Salaün 		if (ruleset_fd < 0)
1486*ac6d193dSMickaël Salaün 			break;
1487*ac6d193dSMickaël Salaün 		/*
1488*ac6d193dSMickaël Salaün 		 * Ignore the result: an abort returns an error, that is fine.
1489*ac6d193dSMickaël Salaün 		 */
1490*ac6d193dSMickaël Salaün 		landlock_restrict_self(ruleset_fd,
1491*ac6d193dSMickaël Salaün 				       LANDLOCK_RESTRICT_SELF_TSYNC);
1492*ac6d193dSMickaël Salaün 		close(ruleset_fd);
1493*ac6d193dSMickaël Salaün 	}
1494*ac6d193dSMickaël Salaün 
1495*ac6d193dSMickaël Salaün 	data.stop = true;
1496*ac6d193dSMickaël Salaün 	pthread_join(signaler, NULL);
1497*ac6d193dSMickaël Salaün 	return 0;
1498*ac6d193dSMickaël Salaün }
1499*ac6d193dSMickaël Salaün 
1500*ac6d193dSMickaël Salaün /*
1501*ac6d193dSMickaël Salaün  * Verifies the abort contract: a domain aborted by a thread-sync failure emits
1502*ac6d193dSMickaël Salaün  * create_domain and free_domain but zero enforce_domain.  The signal race is
1503*ac6d193dSMickaël Salaün  * probabilistic and -ERESTARTNOINTR may add a successful retry's lifecycle, so
1504*ac6d193dSMickaël Salaün  * events are grouped by domain ID and the test SKIPs if no abort occurred.
1505*ac6d193dSMickaël Salaün  */
1506*ac6d193dSMickaël Salaün TEST_F(trace, enforce_abort)
1507*ac6d193dSMickaël Salaün {
1508*ac6d193dSMickaël Salaün 	pid_t pid;
1509*ac6d193dSMickaël Salaün 	int status, retry;
1510*ac6d193dSMickaël Salaün 	char *buf = NULL;
1511*ac6d193dSMickaël Salaün 	const char *cursor;
1512*ac6d193dSMickaël Salaün 	char domain[64];
1513*ac6d193dSMickaël Salaün 	bool abort_found = false;
1514*ac6d193dSMickaël Salaün 
1515*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_clear_buf());
1516*ac6d193dSMickaël Salaün 
1517*ac6d193dSMickaël Salaün 	/* free_domain fires from a kworker, so widen the filter first. */
1518*ac6d193dSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
1519*ac6d193dSMickaël Salaün 	tracefs_clear_pid_filter();
1520*ac6d193dSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
1521*ac6d193dSMickaël Salaün 
1522*ac6d193dSMickaël Salaün 	pid = fork();
1523*ac6d193dSMickaël Salaün 	ASSERT_LE(0, pid);
1524*ac6d193dSMickaël Salaün 	if (pid == 0)
1525*ac6d193dSMickaël Salaün 		/*
1526*ac6d193dSMickaël Salaün 		 * Match tsync_test's NUM_IDLE_THREADS: enough siblings that
1527*ac6d193dSMickaël Salaün 		 * credential preparation runs in several serialized waves,
1528*ac6d193dSMickaël Salaün 		 * giving the signaler a window to interrupt the thread-sync
1529*ac6d193dSMickaël Salaün 		 * wait and abort the operation.  A handful of threads finishes
1530*ac6d193dSMickaël Salaün 		 * in a single wave, leaving no window (the abort never fires).
1531*ac6d193dSMickaël Salaün 		 */
1532*ac6d193dSMickaël Salaün 		_exit(child_abort(200, 8));
1533*ac6d193dSMickaël Salaün 
1534*ac6d193dSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1535*ac6d193dSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
1536*ac6d193dSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
1537*ac6d193dSMickaël Salaün 
1538*ac6d193dSMickaël Salaün 	/* Poll for the asynchronous free_domain events. */
1539*ac6d193dSMickaël Salaün 	for (retry = 0; retry < 10; retry++) {
1540*ac6d193dSMickaël Salaün 		usleep(100000);
1541*ac6d193dSMickaël Salaün 		set_cap(_metadata, CAP_SYS_ADMIN);
1542*ac6d193dSMickaël Salaün 		free(buf);
1543*ac6d193dSMickaël Salaün 		buf = tracefs_read_trace();
1544*ac6d193dSMickaël Salaün 		clear_cap(_metadata, CAP_SYS_ADMIN);
1545*ac6d193dSMickaël Salaün 		ASSERT_NE(NULL, buf);
1546*ac6d193dSMickaël Salaün 	}
1547*ac6d193dSMickaël Salaün 
1548*ac6d193dSMickaël Salaün 	set_cap(_metadata, CAP_SYS_ADMIN);
1549*ac6d193dSMickaël Salaün 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
1550*ac6d193dSMickaël Salaün 	clear_cap(_metadata, CAP_SYS_ADMIN);
1551*ac6d193dSMickaël Salaün 
1552*ac6d193dSMickaël Salaün 	/*
1553*ac6d193dSMickaël Salaün 	 * Walk every create_domain and look for one whose domain ID has zero
1554*ac6d193dSMickaël Salaün 	 * enforce_domain events but a matching free_domain: that is an aborted
1555*ac6d193dSMickaël Salaün 	 * domain (created, never enforced, freed).
1556*ac6d193dSMickaël Salaün 	 */
1557*ac6d193dSMickaël Salaün 	cursor = buf;
1558*ac6d193dSMickaël Salaün 	while (tracefs_extract_field(cursor, REGEX_CREATE_DOMAIN(TRACE_TASK),
1559*ac6d193dSMickaël Salaün 				     "domain", domain, sizeof(domain)) == 0) {
1560*ac6d193dSMickaël Salaün 		const char *cd, *nl;
1561*ac6d193dSMickaël Salaün 		char free_pattern[256];
1562*ac6d193dSMickaël Salaün 
1563*ac6d193dSMickaël Salaün 		if (count_enforce_matches(buf, domain, -1, -1, -1) == 0) {
1564*ac6d193dSMickaël Salaün 			snprintf(
1565*ac6d193dSMickaël Salaün 				free_pattern, sizeof(free_pattern),
1566*ac6d193dSMickaël Salaün 				TRACE_PREFIX(
1567*ac6d193dSMickaël Salaün 					KWORKER_TASK) "landlock_free_domain: "
1568*ac6d193dSMickaël Salaün 						      "domain=%s denials=[0-9]\\+$",
1569*ac6d193dSMickaël Salaün 				domain);
1570*ac6d193dSMickaël Salaün 			if (tracefs_count_matches(buf, free_pattern) >= 1)
1571*ac6d193dSMickaël Salaün 				abort_found = true;
1572*ac6d193dSMickaël Salaün 		}
1573*ac6d193dSMickaël Salaün 
1574*ac6d193dSMickaël Salaün 		cd = strstr(cursor, "landlock_create_domain:");
1575*ac6d193dSMickaël Salaün 		if (!cd)
1576*ac6d193dSMickaël Salaün 			break;
1577*ac6d193dSMickaël Salaün 		nl = strchr(cd, '\n');
1578*ac6d193dSMickaël Salaün 		if (!nl)
1579*ac6d193dSMickaël Salaün 			break;
1580*ac6d193dSMickaël Salaün 		cursor = nl + 1;
1581*ac6d193dSMickaël Salaün 	}
1582*ac6d193dSMickaël Salaün 
1583*ac6d193dSMickaël Salaün 	if (!abort_found) {
1584*ac6d193dSMickaël Salaün 		free(buf);
1585*ac6d193dSMickaël Salaün 		SKIP(return, "signal race did not produce a thread-sync abort");
1586*ac6d193dSMickaël Salaün 	}
1587*ac6d193dSMickaël Salaün 
1588*ac6d193dSMickaël Salaün 	free(buf);
1589*ac6d193dSMickaël Salaün }
1590*ac6d193dSMickaël Salaün 
1591*ac6d193dSMickaël Salaün /*
15922651712aSMickaël Salaün  * The following tests are intentionally elided because the underlying kernel
15932651712aSMickaël Salaün  * mechanisms are already validated by audit tests:
15942651712aSMickaël Salaün  *
15952651712aSMickaël Salaün  * - Domain ID monotonicity: validated by audit_test.c:layers.  The same
15962651712aSMickaël Salaün  *   landlock_get_id_range() function serves both audit and trace.
15972651712aSMickaël Salaün  *
15982651712aSMickaël Salaün  * - Domain deallocation order (LIFO): validated by audit_test.c:layers.  Trace
15992651712aSMickaël Salaün  *   events fire from the same free_domain_work() code path.
16002651712aSMickaël Salaün  *
16012651712aSMickaël Salaün  * - Max-layer stacking (16 domains): validated by audit_test.c:layers.
16022651712aSMickaël Salaün  *
16032651712aSMickaël Salaün  * - IPv6 network tests: IPv6 hook dispatch uses the same
16042651712aSMickaël Salaün  *   current_check_access_socket() as IPv4, validated by net_test.c:audit tests.
16052651712aSMickaël Salaün  *
16062651712aSMickaël Salaün  * - Per-access-right full matrix (all 16 FS rights): hook dispatch is validated
16072651712aSMickaël Salaün  *   by fs_test.c:audit tests.  Trace tests verify representative samples to
16082651712aSMickaël Salaün  *   ensure bitmask encoding is correct.
16092651712aSMickaël Salaün  *
16102651712aSMickaël Salaün  * - Combined log flag variants (e.g., LOG_SUBDOMAINS_OFF + LOG_NEW_EXEC_ON):
16112651712aSMickaël Salaün  *   individual flag tests above cover each flag's effect on trace fields.  Flag
16122651712aSMickaël Salaün  *   combination logic is validated by audit_test.c:audit_flags tests.
16132651712aSMickaël Salaün  *
16142651712aSMickaël Salaün  * - fs.refer multi-record denials and fs.change_topology (mount):
16152651712aSMickaël Salaün  *   trace_denial() uses the same code path for all FS request types.  The
16162651712aSMickaël Salaün  *   DENTRY union member is validated by the deny_access_fs_fields
16172651712aSMickaël Salaün  *   test.  Audit tests in fs_test.c cover refer and mount denial specifics.
16182651712aSMickaël Salaün  */
16192651712aSMickaël Salaün 
16202651712aSMickaël Salaün TEST_HARNESS_MAIN
1621