Linux Security Modules development
 help / color / mirror / Atom feed
From: "Mickaël Salaün" <mic@digikod.net>
To: "Günther Noack" <gnoack@google.com>,
	"Steven Rostedt" <rostedt@goodmis.org>
Cc: "Mickaël Salaün" <mic@digikod.net>,
	"Christian Brauner" <brauner@kernel.org>,
	"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@google.com>,
	"Justin Suess" <utilityemal77@gmail.com>,
	"Kees Cook" <kees@kernel.org>,
	"Masami Hiramatsu" <mhiramat@kernel.org>,
	"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
	"Matthieu Buffet" <matthieu@buffet.re>,
	"Mikhail Ivanov" <ivanov.mikhail1@huawei-partners.com>,
	"Tingmao Wang" <m@maowtm.org>,
	kernel-team@cloudflare.com,
	linux-security-module@vger.kernel.org,
	linux-trace-kernel@vger.kernel.org
Subject: [PATCH v4 17/19] selftests/landlock: Add scope and ptrace tracepoint tests
Date: Tue, 11 Aug 2026 11:43:31 +0200	[thread overview]
Message-ID: <20260811094338.288094-18-mic@digikod.net> (raw)
In-Reply-To: <20260811094338.288094-1-mic@digikod.net>

Add trace tests for the landlock_deny_ptrace,
landlock_deny_scope_signal, and landlock_deny_scope_abstract_unix_socket
tracepoints, each placed alongside the functional tests for its
subsystem, mirroring the audit test layout.

Each tracepoint is exercised by a fixture with three variants that pin
both branches of the other-party domain field: denied against an
unsandboxed other party (other-party domain ID 0), denied against a
sandboxed other party (non-zero ID), and an allowed baseline that
records no event.  A second fixture per type exercises an alternate LSM
hook that reaches the same tracepoint with the same other-party domain
ID, since each denial type can be reached through more than one hook.

The datagram abstract-unix variant does not assert peer_pid, which is 0
for a datagram peer (no SO_PEERCRED); sun_path is the reliable peer
identifier.  The ptrace fixtures install a plain domain-creating ruleset
rather than a dedicated flag, since ptrace denial relies on domain
ancestry, not on a specific scoped flag.  The fixtures unshare the mount
namespace and remount / as MS_PRIVATE before mounting tracefs so the
helper instance is visible only to the test process.

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

Changes since v3:
https://patch.msgid.link/20260722171159.2776765-19-mic@digikod.net
- Bind the abstract-unix-socket trace test (deny_scope_unix) with a
  name containing a space and an embedded NUL followed by an "END"
  marker, and assert the marker survives: a regression test for the
  __trace_untrusted_str() escaping (a raw space would break the
  sun_path field) and its explicit length (strlen() would truncate
  at the NUL and drop the marker).
- Merge the trace_unix and trace_unix_dgram fixtures into one
  trace_unix fixture with a SOCK_STREAM/SOCK_DGRAM variant dimension
  (variants stream_* and dgram_*), so the stream connect(2) and
  datagram sendto(2) denial paths share one parametrized
  deny_scope_unix test (same six cases); peer_pid is asserted only
  for the stream peer (a datagram peer has no SO_PEERCRED).

Changes since v2:
https://patch.msgid.link/20260406143717.1815792-17-mic@digikod.net
- Tighten the denied-variant event-count assertion from EXPECT_LE to
  EXPECT_EQ (a single denied operation against a single-layer domain
  emits exactly one event), for the signal, abstract-unix-socket, and
  ptrace fixtures.
- Follow the deny_ptrace printk label rename comm= to the role-prefixed
  tracee_comm= (the label now matches its sibling tracee_pid= field) in
  the parsed field name of the ptrace comm assertion (ptrace_test.c).
- Assert the other party's domain ID in the scope and ptrace trace tests
  (add the per-layer tracee_domain=/target_domain=/peer_domain= fields
  to REGEX_DENY_PTRACE, REGEX_DENY_SCOPE_SIGNAL, and
  REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET, then check them) with three
  variants per fixture: denied against an unsandboxed other party
  (domain ID 0),
  denied against a sandboxed other party (non-zero ID), and an allowed
  baseline that records no event.
- Cover the alternate LSM hook that reaches each denial tracepoint and
  sets the same other-party domain ID: hook_file_send_sigiotask via
  fcntl(F_SETOWN)+SIGIO (trace_fown), hook_unix_may_send via a datagram
  sendto (trace_unix_dgram), and a denied hook_ptrace_traceme
  (trace_ptrace_traceme).

Changes since v1:
- New patch.
---
 .../testing/selftests/landlock/ptrace_test.c  | 402 +++++++++++++++++
 .../landlock/scoped_abstract_unix_test.c      | 264 ++++++++++++
 .../selftests/landlock/scoped_signal_test.c   | 404 ++++++++++++++++++
 tools/testing/selftests/landlock/trace.h      |  17 +-
 4 files changed, 1080 insertions(+), 7 deletions(-)

diff --git a/tools/testing/selftests/landlock/ptrace_test.c b/tools/testing/selftests/landlock/ptrace_test.c
index 4f64c90583cd..2644445a9d02 100644
--- a/tools/testing/selftests/landlock/ptrace_test.c
+++ b/tools/testing/selftests/landlock/ptrace_test.c
@@ -11,7 +11,9 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/landlock.h>
+#include <sched.h>
 #include <signal.h>
+#include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/ptrace.h>
 #include <sys/types.h>
@@ -20,6 +22,7 @@
 
 #include "audit.h"
 #include "common.h"
+#include "trace.h"
 
 /* Copied from security/yama/yama_lsm.c */
 #define YAMA_SCOPE_DISABLED 0
@@ -430,4 +433,403 @@ TEST_F(audit, trace)
 	EXPECT_EQ(0, records.domain);
 }
 
+/* Trace tests */
+
+/* clang-format off */
+FIXTURE(trace_ptrace) {
+	/* clang-format on */
+	int tracefs_ok;
+};
+
+FIXTURE_SETUP(trace_ptrace)
+{
+	int ret;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	ret = tracefs_fixture_setup();
+	if (ret) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		self->tracefs_ok = 0;
+		SKIP(return, "tracefs not available");
+	}
+	self->tracefs_ok = 1;
+
+	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, true));
+	ASSERT_EQ(0, tracefs_clear());
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(trace_ptrace)
+{
+	if (!self->tracefs_ok)
+		return;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false);
+	tracefs_fixture_teardown();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE_VARIANT(trace_ptrace)
+{
+	/* clang-format on */
+	bool sandbox;
+	bool sandbox_target;
+	int expect_denied;
+};
+
+/* Denied: sandboxed child ptraces unsandboxed parent (tracee_domain=0). */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace, denied) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = false,
+	.expect_denied = 1,
+};
+
+/*
+ * Denied: sandboxed child ptraces a sandboxed parent, so the tracee is in a
+ * domain and tracee_domain= is non-zero.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace, denied_scoped_target) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = true,
+	.expect_denied = 1,
+};
+
+/* Allowed: unsandboxed child uses PTRACE_TRACEME. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace, allowed) {
+	/* clang-format on */
+	.sandbox = false,
+	.sandbox_target = false,
+	.expect_denied = 0,
+};
+
+TEST_F(trace_ptrace, deny_ptrace)
+{
+	char *buf, field[64], expected_pid[16];
+	int count, status;
+	pid_t child, parent;
+
+	if (!self->tracefs_ok)
+		SKIP(return, "tracefs not available");
+
+	parent = getpid();
+
+	/*
+	 * Set a known comm so the denied variant can verify both the trace line
+	 * task name and the tracee_comm= field.
+	 */
+	prctl(PR_SET_NAME, "ll_trace_test");
+
+	/*
+	 * For the non-zero tracee_domain case, sandbox the parent (the tracee)
+	 * before forking.  The child inherits that domain and adds its own
+	 * layer, so the child (tracer) is not an ancestor of the tracee and the
+	 * ptrace is still denied, with tracee_domain= naming the parent's
+	 * domain.
+	 */
+	if (variant->sandbox_target)
+		create_domain(_metadata);
+
+	child = fork();
+	ASSERT_LE(0, child);
+
+	if (child == 0) {
+		if (variant->sandbox) {
+			struct landlock_ruleset_attr ruleset_attr = {
+				.scoped = LANDLOCK_SCOPE_SIGNAL,
+			};
+			int ruleset_fd;
+
+			/*
+			 * Any scope creates a domain.  Ptrace denial checks
+			 * domain ancestry, not specific flags.
+			 */
+			ruleset_fd = landlock_create_ruleset(
+				&ruleset_attr, sizeof(ruleset_attr), 0);
+			if (ruleset_fd < 0)
+				_exit(1);
+
+			prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+			if (landlock_restrict_self(ruleset_fd, 0)) {
+				close(ruleset_fd);
+				_exit(1);
+			}
+			close(ruleset_fd);
+
+			/* PTRACE_ATTACH on unsandboxed parent: denied. */
+			if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) == 0) {
+				ptrace(PTRACE_DETACH, parent, NULL, NULL);
+				_exit(2);
+			}
+			if (errno != EPERM)
+				_exit(3);
+		} else {
+			/* No sandbox: ptrace should succeed. */
+			if (ptrace(PTRACE_TRACEME) != 0)
+				_exit(1);
+		}
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	count = tracefs_count_matches(buf, REGEX_DENY_PTRACE("ll_trace_test"));
+	if (variant->expect_denied) {
+		EXPECT_EQ(variant->expect_denied, count)
+		{
+			TH_LOG("Expected deny_ptrace event, got %d\n%s", count,
+			       buf);
+		}
+
+		/* Verify tracee_pid is the parent's TGID. */
+		snprintf(expected_pid, sizeof(expected_pid), "%d", parent);
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_PTRACE("ll_trace_test"),
+				     "tracee_pid", field, sizeof(field)));
+		EXPECT_STREQ(expected_pid, field);
+
+		/* Verify tracee_comm matches prctl(PR_SET_NAME). */
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_PTRACE("ll_trace_test"),
+				     "tracee_comm", field, sizeof(field)));
+		EXPECT_STREQ("ll_trace_test", field);
+
+		/*
+		 * Verify tracee_domain: 0 when the tracee is unsandboxed,
+		 * non-zero when the tracee is in a domain.
+		 */
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_PTRACE("ll_trace_test"),
+				     "tracee_domain", field, sizeof(field)));
+		EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
+		{
+			TH_LOG("Unexpected tracee_domain=%s", field);
+		}
+	} else {
+		EXPECT_EQ(0, count)
+		{
+			TH_LOG("Expected 0 deny_ptrace events, got %d\n%s",
+			       count, buf);
+		}
+	}
+
+	free(buf);
+}
+
+/* clang-format off */
+FIXTURE(trace_ptrace_traceme) {
+	/* clang-format on */
+	int tracefs_ok;
+};
+
+FIXTURE_SETUP(trace_ptrace_traceme)
+{
+	int ret;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	ret = tracefs_fixture_setup();
+	if (ret) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		self->tracefs_ok = 0;
+		SKIP(return, "tracefs not available");
+	}
+	self->tracefs_ok = 1;
+
+	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, true));
+	ASSERT_EQ(0, tracefs_clear());
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(trace_ptrace_traceme)
+{
+	if (!self->tracefs_ok)
+		return;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false);
+	tracefs_fixture_teardown();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE_VARIANT(trace_ptrace_traceme)
+{
+	/* clang-format on */
+	bool sandbox_tracer;
+	bool sandbox_tracee;
+	int expect_denied;
+};
+
+/*
+ * Denied: a sandboxed tracer cannot trace the unsandboxed child that asked to
+ * be traced with PTRACE_TRACEME (tracee_domain=0).
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace_traceme, denied) {
+	/* clang-format on */
+	.sandbox_tracer = true,
+	.sandbox_tracee = false,
+	.expect_denied = 1,
+};
+
+/*
+ * Denied: a sandboxed child in its own domain asks to be traced by a tracer in
+ * an unrelated domain, so the tracee is in a domain and tracee_domain= is
+ * non-zero.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace_traceme, denied_scoped_tracee) {
+	/* clang-format on */
+	.sandbox_tracer = true,
+	.sandbox_tracee = true,
+	.expect_denied = 1,
+};
+
+/* Allowed: unsandboxed child uses PTRACE_TRACEME with an unsandboxed tracer. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_ptrace_traceme, allowed) {
+	/* clang-format on */
+	.sandbox_tracer = false,
+	.sandbox_tracee = false,
+	.expect_denied = 0,
+};
+
+TEST_F(trace_ptrace_traceme, deny_ptrace)
+{
+	char *buf, field[64], expected_pid[16];
+	int count, status, sync_pipe[2];
+	pid_t child;
+
+	if (!self->tracefs_ok)
+		SKIP(return, "tracefs not available");
+
+	/*
+	 * Set a known comm so the denied variant can verify both the trace line
+	 * task name and the tracee_comm= field.  The tracee is the current
+	 * (child) task for PTRACE_TRACEME, so the child inherits this name.
+	 */
+	prctl(PR_SET_NAME, "ll_trace_test");
+
+	ASSERT_EQ(0, pipe2(sync_pipe, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+
+	if (child == 0) {
+		char c;
+
+		close(sync_pipe[1]);
+
+		/*
+		 * The tracee is the current task; for the non-zero
+		 * tracee_domain case it sandboxes itself in its own domain,
+		 * unrelated to the tracer's domain, so PTRACE_TRACEME is still
+		 * denied and tracee_domain= names the child's own domain.
+		 */
+		if (variant->sandbox_tracee)
+			create_domain(_metadata);
+
+		/* Waits for the tracer (parent) to enter its domain, if any. */
+		if (read(sync_pipe[0], &c, 1) != 1)
+			_exit(1);
+		close(sync_pipe[0]);
+
+		if (variant->expect_denied) {
+			if (ptrace(PTRACE_TRACEME) == 0)
+				_exit(2);
+			if (errno != EPERM)
+				_exit(3);
+		} else {
+			if (ptrace(PTRACE_TRACEME) != 0)
+				_exit(4);
+			/* Lets the tracer reap the trace-stop and detach. */
+			raise(SIGSTOP);
+		}
+
+		_exit(0);
+	}
+
+	close(sync_pipe[0]);
+
+	/*
+	 * For a denial, the proposed tracer must be in a domain that is not an
+	 * ancestor of the tracee's domain.  Sandboxing the parent after the
+	 * fork gives it a domain unrelated to the child.
+	 */
+	if (variant->sandbox_tracer)
+		create_domain(_metadata);
+
+	/* Signals the child that the tracer is in its domain, if any. */
+	ASSERT_EQ(1, write(sync_pipe[1], ".", 1));
+	close(sync_pipe[1]);
+
+	if (!variant->expect_denied) {
+		/* PTRACE_TRACEME succeeded: reap the SIGSTOP and detach. */
+		ASSERT_EQ(child, waitpid(child, &status, WUNTRACED));
+		ASSERT_TRUE(WIFSTOPPED(status));
+		ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	count = tracefs_count_matches(buf, REGEX_DENY_PTRACE("ll_trace_test"));
+	if (variant->expect_denied) {
+		EXPECT_EQ(variant->expect_denied, count)
+		{
+			TH_LOG("Expected deny_ptrace event, got %d\n%s", count,
+			       buf);
+		}
+
+		/* Verify tracee_pid is the child's TGID (the traced task). */
+		snprintf(expected_pid, sizeof(expected_pid), "%d", child);
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_PTRACE("ll_trace_test"),
+				     "tracee_pid", field, sizeof(field)));
+		EXPECT_STREQ(expected_pid, field);
+
+		/*
+		 * Verify tracee_domain: 0 when the tracee is unsandboxed,
+		 * non-zero when the tracee is in a domain.
+		 */
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_PTRACE("ll_trace_test"),
+				     "tracee_domain", field, sizeof(field)));
+		EXPECT_EQ(variant->sandbox_tracee, strcmp("0", field) != 0)
+		{
+			TH_LOG("Unexpected tracee_domain=%s", field);
+		}
+	} else {
+		EXPECT_EQ(0, count)
+		{
+			TH_LOG("Expected 0 deny_ptrace events, got %d\n%s",
+			       count, buf);
+		}
+	}
+
+	free(buf);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 40fc82fbf01d..6dbe863ea571 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -12,6 +12,7 @@
 #include <sched.h>
 #include <signal.h>
 #include <stddef.h>
+#include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -23,6 +24,9 @@
 #include "audit.h"
 #include "common.h"
 #include "scoped_common.h"
+#include "trace.h"
+
+#define TRACE_TASK "scoped_abstract"
 
 /* Number of pending connections queue to be hold. */
 const short backlog = 10;
@@ -1205,4 +1209,264 @@ TEST(self_connect)
 		_metadata->exit_code = KSFT_FAIL;
 }
 
+/* Trace tests */
+
+/* clang-format off */
+FIXTURE(trace_unix) {
+	/* clang-format on */
+	int tracefs_ok;
+};
+
+FIXTURE_SETUP(trace_unix)
+{
+	int ret;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	ret = tracefs_fixture_setup();
+	if (ret) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		self->tracefs_ok = 0;
+		SKIP(return, "tracefs not available");
+	}
+	self->tracefs_ok = 1;
+
+	ASSERT_EQ(0, tracefs_enable_event(
+			     TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
+			     true));
+	ASSERT_EQ(0, tracefs_clear());
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(trace_unix)
+{
+	if (!self->tracefs_ok)
+		return;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_enable_event(TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
+			     false);
+	tracefs_fixture_teardown();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE_VARIANT(trace_unix) {
+	/* clang-format on */
+	int sock_type; /* SOCK_STREAM (connect) or SOCK_DGRAM (sendto). */
+	bool sandbox;
+	bool sandbox_target; /* Peer owned by a domain: peer_domain != 0. */
+	int expect_denied;
+};
+
+/* clang-format off */
+
+/* Stream: sandboxed client connect() to an unsandboxed peer (peer_domain=0). */
+FIXTURE_VARIANT_ADD(trace_unix, stream_denied) {
+	.sock_type = SOCK_STREAM, .sandbox = true,
+	.sandbox_target = false, .expect_denied = 1,
+};
+
+/* Stream: peer socket owned by a domain, so peer_domain != 0. */
+FIXTURE_VARIANT_ADD(trace_unix, stream_denied_scoped_peer) {
+	.sock_type = SOCK_STREAM, .sandbox = true,
+	.sandbox_target = true, .expect_denied = 1,
+};
+
+/* Stream: unsandboxed client, connect() succeeds, no event. */
+FIXTURE_VARIANT_ADD(trace_unix, stream_allowed) {
+	.sock_type = SOCK_STREAM, .sandbox = false,
+	.sandbox_target = false, .expect_denied = 0,
+};
+
+/* Datagram: sandboxed client sendto() an unsandboxed peer (peer_domain=0). */
+FIXTURE_VARIANT_ADD(trace_unix, dgram_denied) {
+	.sock_type = SOCK_DGRAM, .sandbox = true,
+	.sandbox_target = false, .expect_denied = 1,
+};
+
+/* Datagram: peer socket owned by a domain, so peer_domain != 0. */
+FIXTURE_VARIANT_ADD(trace_unix, dgram_denied_scoped_peer) {
+	.sock_type = SOCK_DGRAM, .sandbox = true,
+	.sandbox_target = true, .expect_denied = 1,
+};
+
+/* Datagram: unsandboxed client, sendto() succeeds, no event. */
+FIXTURE_VARIANT_ADD(trace_unix, dgram_allowed) {
+	.sock_type = SOCK_DGRAM, .sandbox = false,
+	.sandbox_target = false, .expect_denied = 0,
+};
+
+/* clang-format on */
+
+/*
+ * A sandboxed thread reaching an abstract unix socket peer through connect(2)
+ * (stream) or sendto(2) (datagram) is denied and emits
+ * landlock_deny_scope_abstract_unix_socket.  The abstract name is crafted with
+ * a space and an embedded NUL followed by an "END" marker to check the
+ * tracepoint escaping and its length handling (a raw space would break the
+ * sun_path field regex; strlen() would truncate at the NUL and drop "END").
+ * peer_pid is only meaningful for a stream peer (a datagram peer has no
+ * SO_PEERCRED), so it is asserted only there.
+ */
+TEST_F(trace_unix, deny_scope_unix)
+{
+	struct sockaddr_un addr = {
+		.sun_family = AF_UNIX,
+	};
+	char *buf, field[128], expected_pid[16];
+	int server_fd, count, status, name_len, addr_len;
+	pid_t child;
+
+	if (!self->tracefs_ok)
+		SKIP(return, "tracefs not available");
+
+	/*
+	 * For the non-zero peer_domain case, sandbox the parent before it
+	 * creates the server socket, so the socket carries the parent's domain
+	 * and peer_domain= is non-zero.
+	 */
+	if (variant->sandbox_target)
+		create_scoped_domain(_metadata,
+				     LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+
+	server_fd = socket(AF_UNIX, variant->sock_type | SOCK_CLOEXEC, 0);
+	ASSERT_LE(0, server_fd);
+
+	addr.sun_path[0] = '\0';
+	name_len = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1,
+			    "landlock_trace_test_%d ", getpid());
+	addr.sun_path[1 + name_len] = '\0';
+	memcpy(addr.sun_path + 1 + name_len + 1, "END", 3);
+	addr_len =
+		offsetof(struct sockaddr_un, sun_path) + 1 + name_len + 1 + 3;
+
+	ASSERT_EQ(0, bind(server_fd, (struct sockaddr *)&addr, addr_len));
+	if (variant->sock_type == SOCK_STREAM)
+		ASSERT_EQ(0, listen(server_fd, 1));
+
+	child = fork();
+	ASSERT_LE(0, child);
+
+	if (child == 0) {
+		int client_fd, ret;
+
+		if (variant->sandbox) {
+			struct landlock_ruleset_attr ruleset_attr = {
+				.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+			};
+			int ruleset_fd;
+
+			ruleset_fd = landlock_create_ruleset(
+				&ruleset_attr, sizeof(ruleset_attr), 0);
+			if (ruleset_fd < 0)
+				_exit(1);
+
+			prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+			if (landlock_restrict_self(ruleset_fd, 0)) {
+				close(ruleset_fd);
+				_exit(1);
+			}
+			close(ruleset_fd);
+		}
+
+		client_fd =
+			socket(AF_UNIX, variant->sock_type | SOCK_CLOEXEC, 0);
+		if (client_fd < 0)
+			_exit(1);
+
+		if (variant->sock_type == SOCK_STREAM)
+			ret = connect(client_fd, (struct sockaddr *)&addr,
+				      addr_len);
+		else
+			ret = sendto(client_fd, ".", 1, 0,
+				     (struct sockaddr *)&addr, addr_len);
+
+		if (variant->sandbox) {
+			/* Reaching the peer should be denied. */
+			if (ret != -1 || errno != EPERM) {
+				close(client_fd);
+				_exit(2);
+			}
+		} else {
+			/* No sandbox: stream connect() == 0, sendto() == 1. */
+			int ok = variant->sock_type == SOCK_STREAM ? 0 : 1;
+
+			if (ret != ok) {
+				close(client_fd);
+				_exit(2);
+			}
+		}
+		close(client_fd);
+		_exit(0);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+	close(server_fd);
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	count = tracefs_count_matches(
+		buf, REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK));
+	if (!variant->expect_denied) {
+		EXPECT_EQ(0, count)
+		{
+			TH_LOG("Expected 0 deny_scope events, got %d\n%s",
+			       count, buf);
+		}
+		free(buf);
+		return;
+	}
+
+	EXPECT_EQ(variant->expect_denied, count)
+	{
+		TH_LOG("Expected deny_scope_abstract_unix_socket event, "
+		       "got %d\n%s",
+		       count, buf);
+	}
+
+	/*
+	 * sun_path is escaped: a raw space would break this field's [^ ]*$
+	 * regex, so a successful extract proves the space was escaped, and its
+	 * full length is honored: the "END" marker after the embedded NUL must
+	 * survive (strlen() would truncate it at the NUL).
+	 */
+	ASSERT_EQ(0, tracefs_extract_field(
+			     buf,
+			     REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK),
+			     "sun_path", field, sizeof(field)));
+	EXPECT_NE(NULL, strstr(field, "END"))
+	{
+		TH_LOG("sun_path truncated or unescaped: %s", field);
+	}
+
+	/* peer_pid is the parent's PID for a stream peer (0 for datagram). */
+	if (variant->sock_type == SOCK_STREAM) {
+		snprintf(expected_pid, sizeof(expected_pid), "%d", getpid());
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf,
+				     REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(
+					     TRACE_TASK),
+				     "peer_pid", field, sizeof(field)));
+		EXPECT_STREQ(expected_pid, field);
+	}
+
+	/* peer_domain: 0 when the peer is unsandboxed, non-zero otherwise. */
+	ASSERT_EQ(0, tracefs_extract_field(
+			     buf,
+			     REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK),
+			     "peer_domain", field, sizeof(field)));
+	EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
+	{
+		TH_LOG("Unexpected peer_domain=%s", field);
+	}
+
+	free(buf);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index 2d37d0c06c06..259cdcc8aa5c 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -10,7 +10,9 @@
 #include <fcntl.h>
 #include <linux/landlock.h>
 #include <pthread.h>
+#include <sched.h>
 #include <signal.h>
+#include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -18,6 +20,9 @@
 
 #include "common.h"
 #include "scoped_common.h"
+#include "trace.h"
+
+#define TRACE_TASK "scoped_signal_t"
 
 /* This variable is used for handling several signals. */
 static volatile sig_atomic_t is_signaled;
@@ -762,4 +767,403 @@ TEST(sigio_to_pgid_self)
 	EXPECT_EQ(0, close(trigger[1]));
 }
 
+/* Trace tests */
+
+/* clang-format off */
+FIXTURE(trace_signal) {
+	/* clang-format on */
+	int tracefs_ok;
+};
+
+FIXTURE_SETUP(trace_signal)
+{
+	int ret;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	ret = tracefs_fixture_setup();
+	if (ret) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		self->tracefs_ok = 0;
+		SKIP(return, "tracefs not available");
+	}
+	self->tracefs_ok = 1;
+
+	ASSERT_EQ(0,
+		  tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, true));
+	ASSERT_EQ(0, tracefs_clear());
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(trace_signal)
+{
+	if (!self->tracefs_ok)
+		return;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, false);
+	tracefs_fixture_teardown();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE_VARIANT(trace_signal)
+{
+	/* clang-format on */
+	bool sandbox;
+	bool sandbox_target;
+	int expect_denied;
+};
+
+/* Denied: sandboxed child signals unsandboxed parent (target_domain=0). */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_signal, denied) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = false,
+	.expect_denied = 1,
+};
+
+/*
+ * Denied: sandboxed child signals a sandboxed parent, so the target is in a
+ * domain and target_domain= is non-zero.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_signal, denied_scoped_target) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = true,
+	.expect_denied = 1,
+};
+
+/* Allowed: unsandboxed child signals unsandboxed parent. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_signal, allowed) {
+	/* clang-format on */
+	.sandbox = false,
+	.sandbox_target = false,
+	.expect_denied = 0,
+};
+
+TEST_F(trace_signal, deny_scope_signal)
+{
+	char *buf, field[64], expected_pid[16];
+	int count, status;
+	pid_t child;
+
+	if (!self->tracefs_ok)
+		SKIP(return, "tracefs not available");
+
+	/*
+	 * For the non-zero target_domain case, sandbox the parent (the signal
+	 * target) before forking.  The child inherits that domain and adds its
+	 * own scoped layer, so the signal is still denied and target_domain=
+	 * names the parent's domain.
+	 */
+	if (variant->sandbox_target)
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+
+	child = fork();
+	ASSERT_LE(0, child);
+
+	if (child == 0) {
+		if (variant->sandbox) {
+			struct landlock_ruleset_attr ruleset_attr = {
+				.scoped = LANDLOCK_SCOPE_SIGNAL,
+			};
+			int ruleset_fd;
+
+			ruleset_fd = landlock_create_ruleset(
+				&ruleset_attr, sizeof(ruleset_attr), 0);
+			if (ruleset_fd < 0)
+				_exit(1);
+
+			prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+			if (landlock_restrict_self(ruleset_fd, 0)) {
+				close(ruleset_fd);
+				_exit(1);
+			}
+			close(ruleset_fd);
+		}
+
+		if (variant->sandbox) {
+			/* Signal to unsandboxed parent should be denied. */
+			if (kill(getppid(), 0) == 0)
+				_exit(2);
+			if (errno != EPERM)
+				_exit(3);
+		} else {
+			/* No sandbox: kill should succeed. */
+			if (kill(getppid(), 0) != 0)
+				_exit(1);
+		}
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	count = tracefs_count_matches(buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK));
+	if (variant->expect_denied) {
+		EXPECT_EQ(variant->expect_denied, count)
+		{
+			TH_LOG("Expected deny_scope_signal event, got %d\n%s",
+			       count, buf);
+		}
+
+		/* Verify target_pid is the parent's PID. */
+		snprintf(expected_pid, sizeof(expected_pid), "%d", getpid());
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
+				     "target_pid", field, sizeof(field)));
+		EXPECT_STREQ(expected_pid, field);
+
+		/*
+		 * Verify target_domain: 0 when the target is unsandboxed,
+		 * non-zero when the target is in a domain.
+		 */
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
+				     "target_domain", field, sizeof(field)));
+		EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
+		{
+			TH_LOG("Unexpected target_domain=%s", field);
+		}
+	} else {
+		EXPECT_EQ(0, count)
+		{
+			TH_LOG("Expected 0 deny_scope_signal events, "
+			       "got %d\n%s",
+			       count, buf);
+		}
+	}
+
+	free(buf);
+}
+
+/*
+ * Trace test for the asynchronous SIGIO/SIGURG delivery path
+ * (hook_file_send_sigiotask), which reaches the same landlock_deny_scope_signal
+ * tracepoint as a synchronous kill(2) but through fcntl(F_SETOWN).
+ */
+
+/* clang-format off */
+FIXTURE(trace_fown) {
+	/* clang-format on */
+	int tracefs_ok;
+};
+
+FIXTURE_SETUP(trace_fown)
+{
+	int ret;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	ret = tracefs_fixture_setup();
+	if (ret) {
+		clear_cap(_metadata, CAP_SYS_ADMIN);
+		self->tracefs_ok = 0;
+		SKIP(return, "tracefs not available");
+	}
+	self->tracefs_ok = 1;
+
+	ASSERT_EQ(0,
+		  tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, true));
+	ASSERT_EQ(0, tracefs_clear());
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(trace_fown)
+{
+	if (!self->tracefs_ok)
+		return;
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, false);
+	tracefs_fixture_teardown();
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+/* clang-format off */
+FIXTURE_VARIANT(trace_fown)
+{
+	/* clang-format on */
+	bool sandbox;
+	bool sandbox_target;
+	int expect_denied;
+};
+
+/*
+ * Denied: a sandboxed file owner's SIGURG is delivered to an unsandboxed target
+ * process (target_domain=0).
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_fown, denied) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = false,
+	.expect_denied = 1,
+};
+
+/*
+ * Denied: the SIGURG target sandboxes itself in its own domain, so the target
+ * is in a domain and target_domain= is non-zero.
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_fown, denied_scoped_target) {
+	/* clang-format on */
+	.sandbox = true,
+	.sandbox_target = true,
+	.expect_denied = 1,
+};
+
+/* Allowed: an unsandboxed file owner delivers SIGURG. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_fown, allowed) {
+	/* clang-format on */
+	.sandbox = false,
+	.sandbox_target = false,
+	.expect_denied = 0,
+};
+
+TEST_F(trace_fown, deny_scope_fown)
+{
+	int server_socket, recv_socket;
+	struct service_fixture server_address;
+	char buffer_parent, field[64], *buf;
+	int status, count;
+	int pipe_parent[2], pipe_child[2];
+	pid_t child;
+
+	if (!self->tracefs_ok)
+		SKIP(return, "tracefs not available");
+
+	memset(&server_address, 0, sizeof(server_address));
+	set_unix_address(&server_address, 0);
+
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int client_socket;
+		char buffer_child;
+
+		EXPECT_EQ(0, close(pipe_parent[1]));
+		EXPECT_EQ(0, close(pipe_child[0]));
+
+		ASSERT_EQ(0, setup_signal_handler(SIGURG));
+		client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+		ASSERT_LE(0, client_socket);
+
+		/*
+		 * The SIGURG target is this child; for the non-zero
+		 * target_domain case it sandboxes itself in its own domain,
+		 * unrelated to the file owner's domain.
+		 */
+		if (variant->sandbox_target)
+			create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+
+		/* Waits for the parent to listen. */
+		ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
+		ASSERT_EQ(0, connect(client_socket, &server_address.unix_addr,
+				     server_address.unix_addr_len));
+
+		/*
+		 * Waits for the parent to accept the connection, sandbox
+		 * itself, and call fcntl(F_SETOWN).
+		 */
+		ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
+		/* Triggers the asynchronous SIGURG to this file owner. */
+		ASSERT_EQ(1, send(client_socket, ".", 1, MSG_OOB));
+		EXPECT_EQ(0, close(client_socket));
+		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+		EXPECT_EQ(0, close(pipe_child[1]));
+
+		_exit(0);
+		return;
+	}
+	EXPECT_EQ(0, close(pipe_parent[0]));
+	EXPECT_EQ(0, close(pipe_child[1]));
+
+	server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+	ASSERT_LE(0, server_socket);
+	ASSERT_EQ(0, bind(server_socket, &server_address.unix_addr,
+			  server_address.unix_addr_len));
+	ASSERT_EQ(0, listen(server_socket, backlog));
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	recv_socket = accept(server_socket, NULL, NULL);
+	ASSERT_LE(0, recv_socket);
+
+	/*
+	 * The file owner is the denying subject; its domain is captured at
+	 * fcntl(F_SETOWN) time, so sandbox it before setting the owner.
+	 */
+	if (variant->sandbox)
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
+
+	/*
+	 * Sets the child to receive SIGURG for MSG_OOB.  This uncommon use is a
+	 * valid attack scenario which also simplifies this test.
+	 */
+	ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child));
+
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	/* Waits for the child to send MSG_OOB. */
+	ASSERT_EQ(1, read(pipe_child[0], &buffer_parent, 1));
+	EXPECT_EQ(0, close(pipe_child[0]));
+	ASSERT_EQ(1, recv(recv_socket, &buffer_parent, 1, MSG_OOB));
+	EXPECT_EQ(0, close(recv_socket));
+	EXPECT_EQ(0, close(server_socket));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+
+	buf = tracefs_read_buf();
+	ASSERT_NE(NULL, buf);
+
+	count = tracefs_count_matches(buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK));
+	if (variant->expect_denied) {
+		EXPECT_EQ(variant->expect_denied, count)
+		{
+			TH_LOG("Expected deny_scope_signal event, got %d\n%s",
+			       count, buf);
+		}
+
+		/*
+		 * Verify target_domain: 0 when the target is unsandboxed,
+		 * non-zero when the target is in a domain.
+		 */
+		ASSERT_EQ(0, tracefs_extract_field(
+				     buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
+				     "target_domain", field, sizeof(field)));
+		EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
+		{
+			TH_LOG("Unexpected target_domain=%s", field);
+		}
+	} else {
+		EXPECT_EQ(0, count)
+		{
+			TH_LOG("Expected 0 deny_scope_signal events, "
+			       "got %d\n%s",
+			       count, buf);
+		}
+	}
+
+	free(buf);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h
index aa35d8ad166e..4c82ecffc235 100644
--- a/tools/testing/selftests/landlock/trace.h
+++ b/tools/testing/selftests/landlock/trace.h
@@ -146,13 +146,14 @@
 	"sport=[0-9]\\+ "            \
 	"dport=[0-9]\\+$"
 
-#define REGEX_DENY_PTRACE(task)  \
-	TRACE_PREFIX(task)       \
-	"landlock_deny_ptrace: " \
-	"domain=[0-9a-f]\\+ "    \
-	"same_exec=[01] "        \
-	"logged=[01] "           \
-	"tracee_pid=[0-9]\\+ "   \
+#define REGEX_DENY_PTRACE(task)      \
+	TRACE_PREFIX(task)           \
+	"landlock_deny_ptrace: "     \
+	"domain=[0-9a-f]\\+ "        \
+	"same_exec=[01] "            \
+	"logged=[01] "               \
+	"tracee_domain=[0-9a-f]\\+ " \
+	"tracee_pid=[0-9]\\+ "       \
 	"tracee_comm=[^ ]*$"
 
 #define REGEX_DENY_SCOPE_SIGNAL(task)  \
@@ -161,6 +162,7 @@
 	"domain=[0-9a-f]\\+ "          \
 	"same_exec=[01] "              \
 	"logged=[01] "                 \
+	"target_domain=[0-9a-f]\\+ "   \
 	"target_pid=[0-9]\\+ "         \
 	"target_comm=[^ ]*$"
 
@@ -170,6 +172,7 @@
 	"domain=[0-9a-f]\\+ "                        \
 	"same_exec=[01] "                            \
 	"logged=[01] "                               \
+	"peer_domain=[0-9a-f]\\+ "                   \
 	"peer_pid=[0-9]\\+ "                         \
 	"sun_path=[^ ]*$"
 
-- 
2.54.0


  parent reply	other threads:[~2026-08-11  9:50 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-11  9:43 [PATCH v4 00/19] Landlock tracepoints Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 01/19] landlock: Prepare ruleset and domain type split Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 02/19] landlock: Move domain query functions to domain.c Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 03/19] landlock: Split struct landlock_domain from struct landlock_ruleset Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 04/19] landlock: Split denial logging from audit into common framework Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 05/19] landlock: Decouple the per-denial logging decision from CONFIG_AUDIT Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 06/19] landlock: Consolidate access-right and scope names in a shared header Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 07/19] landlock: Add create_ruleset and free_ruleset tracepoints Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 08/19] landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 09/19] landlock: Add create_domain and free_domain tracepoints Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 10/19] landlock: Add landlock_enforce_domain tracepoint Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 11/19] landlock: Add tracepoints for rule checking Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 12/19] landlock: Add landlock_deny_access_fs and landlock_deny_access_net Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 13/19] landlock: Add tracepoints for ptrace and scope denials Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 14/19] selftests/landlock: Add trace event test infrastructure and tests Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 15/19] selftests/landlock: Add filesystem tracepoint tests Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 16/19] selftests/landlock: Add network " Mickaël Salaün
2026-08-11  9:43 ` Mickaël Salaün [this message]
2026-08-11  9:43 ` [PATCH v4 18/19] selftests/landlock: Add landlock_enforce_domain trace tests Mickaël Salaün
2026-08-11  9:43 ` [PATCH v4 19/19] landlock: Document tracepoints Mickaël Salaün
2026-08-11 13:51 ` [PATCH v4 00/19] Landlock tracepoints Steven Rostedt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260811094338.288094-18-mic@digikod.net \
    --to=mic@digikod.net \
    --cc=brauner@kernel.org \
    --cc=gnoack@google.com \
    --cc=ivanov.mikhail1@huawei-partners.com \
    --cc=jannh@google.com \
    --cc=jeffxu@google.com \
    --cc=kees@kernel.org \
    --cc=kernel-team@cloudflare.com \
    --cc=linux-security-module@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=m@maowtm.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=matthieu@buffet.re \
    --cc=mhiramat@kernel.org \
    --cc=rostedt@goodmis.org \
    --cc=utilityemal77@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox