Thread (22 messages) 22 messages, 2 authors, 3d ago
WARM3d

[PATCH v3 19/20] selftests/landlock: Add landlock_enforce_domain trace tests

From: Mickaël Salaün <mic@digikod.net>
Date: 2026-07-22 17:12:47
Also in: linux-trace-kernel
Subsystem: kernel selftest framework, landlock security module, the rest · Maintainers: Shuah Khan, Mickaël Salaün, Linus Torvalds

Add trace tests for the landlock_enforce_domain event in trace_test.c,
asserting field counts after the syscall returns rather than line
ordering across per-CPU buffers.  They cover single-threaded and TSYNC
enforcement (complete and process_wide set), a multi-threaded non-TSYNC
process (process_wide clear), the single-threaded non-leader edge case,
the flags-only path that creates no domain, and a thread-sync abort that
emits create_domain and free_domain but no enforce_domain.

landlock_enforce_domain is added to the fixture enable path and every
disable list so its zero-events assertions cannot be tripped by a stray
enforcement event.

Test coverage for security/landlock is 91.6% of 2552 lines according to
LLVM 22.

Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <redacted>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---

Changes since v2:
- New patch.
---
 tools/testing/selftests/landlock/trace.h      |   9 +
 tools/testing/selftests/landlock/trace_test.c | 522 ++++++++++++++++++
 2 files changed, 531 insertions(+)
diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h
index e03c689ef398..9e901823ddaf 100644
--- a/tools/testing/selftests/landlock/trace.h
+++ b/tools/testing/selftests/landlock/trace.h
@@ -25,6 +25,8 @@
 	TRACEFS_LANDLOCK_DIR "/landlock_create_ruleset/enable"
 #define TRACEFS_CREATE_DOMAIN_ENABLE \
 	TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable"
+#define TRACEFS_ENFORCE_DOMAIN_ENABLE \
+	TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable"
 #define TRACEFS_ADD_RULE_FS_ENABLE \
 	TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable"
 #define TRACEFS_ADD_RULE_NET_ENABLE \
@@ -108,6 +110,13 @@
 	"parent=[0-9a-f]\\+ "      \
 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+$"
 
+#define REGEX_ENFORCE_DOMAIN(task)  \
+	TRACE_PREFIX(task)          \
+	"landlock_enforce_domain: " \
+	"domain=[0-9a-f]\\+ "       \
+	"complete=[01] "            \
+	"process_wide=[01]$"
+
 #define REGEX_CHECK_RULE_FS(task)  \
 	TRACE_PREFIX(task)         \
 	"landlock_check_rule_fs: " \
diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c
index a141f22ad98f..e2415b98868d 100644
--- a/tools/testing/selftests/landlock/trace_test.c
+++ b/tools/testing/selftests/landlock/trace_test.c
@@ -9,6 +9,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/landlock.h>
+#include <pthread.h>
 #include <sched.h>
 #include <stdio.h>
 #include <string.h>
@@ -47,6 +48,7 @@ FIXTURE_SETUP(trace)
 
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
+	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
@@ -69,6 +71,7 @@ FIXTURE_TEARDOWN(trace)
 	set_cap(_metadata, CAP_SYS_ADMIN);
 	tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
 	tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
+	tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
 	tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
 	tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false);
 	tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
@@ -98,6 +101,8 @@ TEST_F(trace, no_trace_when_disabled)
 	ASSERT_EQ(0,
 		  tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));
+	ASSERT_EQ(0,
+		  tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false));
 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));
@@ -1069,6 +1074,523 @@ TEST_F(trace, free_ruleset_on_close)
 	free(buf);
 }
 
+/*
+ * Counts landlock_enforce_domain lines, filtered by @domain (NULL matches any),
+ * @complete and @process_wide (a negative value matches any).  Builds the
+ * anchored regex dynamically so a single helper covers every field assertion.
+ */
+static int count_enforce_matches(const char *buf, const char *domain,
+				 int complete, int process_wide)
+{
+	char pattern[512], dom[80], comp[8], pw[8];
+
+	if (domain)
+		snprintf(dom, sizeof(dom), "%s", domain);
+	else
+		snprintf(dom, sizeof(dom), "[0-9a-f]\\+");
+	if (complete < 0)
+		snprintf(comp, sizeof(comp), "[01]");
+	else
+		snprintf(comp, sizeof(comp), "%d", complete);
+	if (process_wide < 0)
+		snprintf(pw, sizeof(pw), "[01]");
+	else
+		snprintf(pw, sizeof(pw), "%d", process_wide);
+
+	snprintf(pattern, sizeof(pattern),
+		 TRACE_PREFIX(TRACE_TASK) "landlock_enforce_domain: "
+					  "domain=%s "
+					  "complete=%s process_wide=%s$",
+		 dom, comp, pw);
+	return tracefs_count_matches(buf, pattern);
+}
+
+/* Idle sibling: waits on the barrier so it is a live thread, then sleeps. */
+static void *enforce_idle(void *arg)
+{
+	pthread_barrier_t *barrier = arg;
+
+	pthread_barrier_wait(barrier);
+	while (true)
+		sleep(1);
+	return NULL;
+}
+
+/*
+ * Child body: spawns @nthreads idle siblings (barrier-synchronized so they are
+ * live when the syscall runs), then enforces a domain with @flags.  Returns 0
+ * on success; the process exits afterwards, reaping the siblings.
+ */
+static int child_enforce(int nthreads, __u32 flags)
+{
+	pthread_t threads[8];
+	pthread_barrier_t barrier;
+	int ruleset_fd, i;
+
+	if (nthreads > 0) {
+		if (pthread_barrier_init(&barrier, NULL, nthreads + 1))
+			return 1;
+		for (i = 0; i < nthreads; i++)
+			if (pthread_create(&threads[i], NULL, enforce_idle,
+					   &barrier))
+				return 1;
+		pthread_barrier_wait(&barrier);
+	}
+
+	ruleset_fd = build_enforce_ruleset();
+	if (ruleset_fd < 0)
+		return 1;
+
+	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+	if (landlock_restrict_self(ruleset_fd, flags))
+		return 1;
+	close(ruleset_fd);
+	return 0;
+}
+
+/* Reads the group's live thread count from /proc/self/status (get_nr_threads). */
+static int read_thread_count(void)
+{
+	char line[128];
+	FILE *f;
+	int n = -1;
+
+	f = fopen("/proc/self/status", "re");
+	if (!f)
+		return -1;
+	while (fgets(line, sizeof(line), f))
+		if (sscanf(line, "Threads: %d", &n) == 1)
+			break;
+	fclose(f);
+	return n;
+}
+
+/* Exit code the non-leader child uses to signal "could not go single-threaded". */
+#define ENFORCE_SKIP_EXIT 2
+
+/*
+ * Runs in a spawned thread after the group leader called pthread_exit().  Waits
+ * until it is the only live thread (get_nr_threads() == 1 despite not being the
+ * leader), then enforces a domain.
+ */
+static void *enforce_nonleader(void *arg)
+{
+	int ruleset_fd, i;
+
+	for (i = 0; i < 200; i++) {
+		if (read_thread_count() == 1)
+			break;
+		usleep(10000);
+	}
+	if (read_thread_count() != 1)
+		_exit(ENFORCE_SKIP_EXIT);
+
+	ruleset_fd = build_enforce_ruleset();
+	if (ruleset_fd < 0)
+		_exit(1);
+	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+	if (landlock_restrict_self(ruleset_fd, 0))
+		_exit(1);
+	_exit(0);
+}
+
+/*
+ * Verifies the single-threaded non-TSYNC case: one create_domain and exactly
+ * one enforce_domain, with complete=1 (the operation concludes) and
+ * process_wide=1 (get_nr_threads() == 1), sharing the created domain ID.
+ */
+TEST_F(trace, enforce_single)
+{
+	pid_t pid;
+	int status;
+	char *buf;
+	char domain[64];
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0)
+		_exit(child_enforce(0, 0));
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(1,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, -1, -1))
+	{
+		TH_LOG("Expected 1 enforce_domain event\n%s", buf);
+	}
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 1));
+
+	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
+					   "domain", domain, sizeof(domain)));
+	EXPECT_EQ(1, count_enforce_matches(buf, domain, 1, 1));
+
+	free(buf);
+}
+
+/*
+ * Verifies that TSYNC on a single-threaded process still concludes: one
+ * enforce_domain with complete=1 and process_wide=1.
+ */
+TEST_F(trace, enforce_tsync_single)
+{
+	pid_t pid;
+	int status;
+	char *buf;
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0)
+		_exit(child_enforce(0, LANDLOCK_RESTRICT_SELF_TSYNC));
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(1,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, -1, -1));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 1));
+
+	free(buf);
+}
+
+/*
+ * Verifies TSYNC across a multi-threaded process: one create_domain and
+ * exactly N+1 enforce_domain events (caller plus N siblings), of which exactly
+ * one is complete=1, all are process_wide=1, and all carry the same domain ID.
+ * These are order-independent field counts, evaluated after the syscall
+ * returns (the caller has waited on all siblings by then).
+ */
+TEST_F(trace, enforce_tsync_multithread)
+{
+	const int nsiblings = 3;
+	pid_t pid;
+	int status;
+	char *buf;
+	char domain[64];
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0)
+		_exit(child_enforce(nsiblings, LANDLOCK_RESTRICT_SELF_TSYNC));
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(1,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(nsiblings + 1, count_enforce_matches(buf, NULL, -1, -1))
+	{
+		TH_LOG("Expected %d enforce_domain events\n%s", nsiblings + 1,
+		       buf);
+	}
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, -1));
+	EXPECT_EQ(nsiblings, count_enforce_matches(buf, NULL, 0, -1));
+	EXPECT_EQ(nsiblings + 1, count_enforce_matches(buf, NULL, -1, 1));
+
+	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
+					   "domain", domain, sizeof(domain)));
+	EXPECT_EQ(nsiblings + 1, count_enforce_matches(buf, domain, -1, -1));
+
+	free(buf);
+}
+
+/*
+ * Verifies the disambiguation case: a non-TSYNC restrict_self on a
+ * multi-threaded process enforces only the caller, so the single enforce_domain
+ * has process_wide=0 (the siblings stay unconfined).
+ */
+TEST_F(trace, enforce_multithread_non_tsync)
+{
+	const int nsiblings = 3;
+	pid_t pid;
+	int status;
+	char *buf;
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0)
+		_exit(child_enforce(nsiblings, 0));
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(1,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, -1, -1));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 0))
+	{
+		TH_LOG("Expected process_wide=0 enforce_domain event\n%s", buf);
+	}
+
+	free(buf);
+}
+
+/*
+ * Verifies that a single-threaded caller that is not the group leader (the
+ * leader called pthread_exit()) still reports process_wide=1, the case
+ * get_nr_threads() handles and the leader-relative thread_group_empty() would
+ * not.  SKIPs if the group cannot be reduced to one live thread.
+ */
+TEST_F(trace, enforce_single_non_leader)
+{
+	pid_t pid;
+	int status;
+	char *buf;
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0) {
+		pthread_t worker;
+
+		if (pthread_create(&worker, NULL, enforce_nonleader, NULL))
+			_exit(1);
+		/* Leader leaves; the worker becomes the only live thread. */
+		pthread_exit(NULL);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	if (WEXITSTATUS(status) == ENFORCE_SKIP_EXIT)
+		SKIP(return, "could not reduce group to a single thread");
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(1,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 1))
+	{
+		TH_LOG("Expected process_wide=1 for non-leader caller\n%s",
+		       buf);
+	}
+
+	free(buf);
+}
+
+/*
+ * Verifies the flags-only path (ruleset_fd == -1) creates no domain and emits
+ * neither create_domain nor enforce_domain, with and without TSYNC.
+ */
+TEST_F(trace, enforce_flags_only)
+{
+	pid_t pid;
+	int status;
+	char *buf;
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0) {
+		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+		if (landlock_restrict_self(
+			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF))
+			_exit(1);
+		if (landlock_restrict_self(
+			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
+					LANDLOCK_RESTRICT_SELF_TSYNC))
+			_exit(1);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	EXPECT_EQ(0,
+		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
+	EXPECT_EQ(0, count_enforce_matches(buf, NULL, -1, -1))
+	{
+		TH_LOG("No enforce_domain expected on flags-only path\n%s",
+		       buf);
+	}
+
+	free(buf);
+}
+
+static void enforce_nop_handler(int sig)
+{
+}
+
+struct abort_signaler_data {
+	pthread_t target;
+	volatile bool stop;
+};
+
+/* Hammers the target thread with SIGUSR1 to interrupt the TSYNC prepare wait. */
+static void *abort_signaler(void *arg)
+{
+	struct abort_signaler_data *data = arg;
+
+	while (!data->stop)
+		pthread_kill(data->target, SIGUSR1);
+	return NULL;
+}
+
+/*
+ * Child body for the abort test: with idle siblings and a signaler interrupting
+ * it, repeatedly enforces under TSYNC.  An interrupted attempt aborts its
+ * just-created domain (create_domain + free_domain, zero enforce_domain) while
+ * -ERESTARTNOINTR transparently restarts the syscall, so a successful retry may
+ * add its own full lifecycle.
+ */
+static int child_abort(int nsiblings, int attempts)
+{
+	pthread_t threads[16];
+	pthread_t signaler;
+	pthread_barrier_t barrier;
+	struct abort_signaler_data data = {};
+	struct sigaction sa = {};
+	int i;
+
+	sa.sa_handler = enforce_nop_handler;
+	if (sigaction(SIGUSR1, &sa, NULL))
+		return 1;
+
+	if (pthread_barrier_init(&barrier, NULL, nsiblings + 1))
+		return 1;
+	for (i = 0; i < nsiblings; i++)
+		if (pthread_create(&threads[i], NULL, enforce_idle, &barrier))
+			return 1;
+	pthread_barrier_wait(&barrier);
+
+	data.target = pthread_self();
+	if (pthread_create(&signaler, NULL, abort_signaler, &data))
+		return 1;
+
+	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+	for (i = 0; i < attempts; i++) {
+		int ruleset_fd = build_enforce_ruleset();
+
+		if (ruleset_fd < 0)
+			break;
+		/* Ignore the result: an abort returns an error, that is fine. */
+		landlock_restrict_self(ruleset_fd,
+				       LANDLOCK_RESTRICT_SELF_TSYNC);
+		close(ruleset_fd);
+	}
+
+	data.stop = true;
+	pthread_join(signaler, NULL);
+	return 0;
+}
+
+/*
+ * Verifies the abort contract: a domain aborted by a thread-sync failure emits
+ * create_domain and free_domain but zero enforce_domain.  The signal race is
+ * probabilistic and -ERESTARTNOINTR may add a successful retry's lifecycle, so
+ * events are grouped by domain ID and the test SKIPs if no abort occurred.
+ */
+TEST_F(trace, enforce_abort)
+{
+	pid_t pid;
+	int status, retry;
+	char *buf = NULL;
+	const char *cursor;
+	char domain[64];
+	bool abort_found = false;
+
+	ASSERT_EQ(0, tracefs_clear_buf());
+
+	/* free_domain fires from a kworker, so widen the filter first. */
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_clear_pid_filter();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	pid = fork();
+	ASSERT_LE(0, pid);
+	if (pid == 0)
+		_exit(child_abort(8, 8));
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	/* Poll for the asynchronous free_domain events. */
+	for (retry = 0; retry < 10; retry++) {
+		usleep(100000);
+		set_cap(_metadata, CAP_SYS_ADMIN);
+		free(buf);
+		buf = tracefs_read_trace();
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		ASSERT_NE(NULL, buf);
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	/*
+	 * Walk every create_domain and look for one whose domain ID has zero
+	 * enforce_domain events but a matching free_domain: that is an aborted
+	 * domain (created, never enforced, freed).
+	 */
+	cursor = buf;
+	while (tracefs_extract_field(cursor, REGEX_CREATE_DOMAIN(TRACE_TASK),
+				     "domain", domain, sizeof(domain)) == 0) {
+		const char *cd, *nl;
+		char free_pattern[256];
+
+		if (count_enforce_matches(buf, domain, -1, -1) == 0) {
+			snprintf(
+				free_pattern, sizeof(free_pattern),
+				TRACE_PREFIX(
+					KWORKER_TASK) "landlock_free_domain: "
+						      "domain=%s denials=[0-9]\\+$",
+				domain);
+			if (tracefs_count_matches(buf, free_pattern) >= 1)
+				abort_found = true;
+		}
+
+		cd = strstr(cursor, "landlock_create_domain:");
+		if (!cd)
+			break;
+		nl = strchr(cd, '\n');
+		if (!nl)
+			break;
+		cursor = nl + 1;
+	}
+
+	if (!abort_found) {
+		free(buf);
+		SKIP(return, "signal race did not produce a thread-sync abort");
+	}
+
+	free(buf);
+}
+
 /*
  * The following tests are intentionally elided because the underlying kernel
  * mechanisms are already validated by audit tests:
-- 
2.54.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help