xref: /linux/tools/testing/selftests/landlock/trace_test.c (revision 15dd86021b57a9747b44321dda5384e19ce1a0d8)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Landlock tests - Tracepoints
4  *
5  * Copyright © 2026 Cloudflare, Inc.
6  */
7 
8 #define _GNU_SOURCE
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <linux/landlock.h>
12 #include <pthread.h>
13 #include <sched.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <sys/mount.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 #include <unistd.h>
21 
22 #include "common.h"
23 #include "trace.h"
24 
25 #define TRACE_TASK "trace_test"
26 
27 /* clang-format off */
28 FIXTURE(trace) {
29 	/* clang-format on */
30 	int tracefs_ok;
31 };
32 
33 FIXTURE_SETUP(trace)
34 {
35 	int ret;
36 
37 	set_cap(_metadata, CAP_SYS_ADMIN);
38 	ASSERT_EQ(0, unshare(CLONE_NEWNS));
39 	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
40 
41 	ret = tracefs_fixture_setup();
42 	if (ret) {
43 		clear_cap(_metadata, CAP_SYS_ADMIN);
44 		self->tracefs_ok = 0;
45 		SKIP(return, "tracefs not available");
46 	}
47 	self->tracefs_ok = 1;
48 
49 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
50 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
51 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
52 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
53 					  true));
54 	ASSERT_EQ(0,
55 		  tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true));
56 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
57 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true));
58 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
59 	ASSERT_EQ(0,
60 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, true));
61 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, true));
62 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, true));
63 	ASSERT_EQ(0, tracefs_clear());
64 	clear_cap(_metadata, CAP_SYS_ADMIN);
65 }
66 
67 FIXTURE_TEARDOWN(trace)
68 {
69 	if (!self->tracefs_ok)
70 		return;
71 
72 	/* Disables landlock events and clears PID filter. */
73 	set_cap(_metadata, CAP_SYS_ADMIN);
74 	tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
75 	tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
76 	tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
77 	tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);
78 	tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false);
79 	tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
80 	tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);
81 	tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
82 	tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false);
83 	tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false);
84 	tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false);
85 	tracefs_clear_pid_filter();
86 	clear_cap(_metadata, CAP_SYS_ADMIN);
87 
88 	/*
89 	 * The mount namespace is cleaned up automatically when the test process
90 	 * (harness child) exits.
91 	 */
92 }
93 
94 /*
95  * Verifies that no trace events are emitted when the tracepoints are disabled.
96  */
97 TEST_F(trace, no_trace_when_disabled)
98 {
99 	char *buf;
100 
101 	/* Disable all landlock events. */
102 	set_cap(_metadata, CAP_SYS_ADMIN);
103 	ASSERT_EQ(0,
104 		  tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false));
105 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));
106 	ASSERT_EQ(0,
107 		  tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));
108 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
109 					  false));
110 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE,
111 					  false));
112 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));
113 	ASSERT_EQ(0,
114 		  tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false));
115 	ASSERT_EQ(0,
116 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false));
117 	ASSERT_EQ(0,
118 		  tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false));
119 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false));
120 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE,
121 					  false));
122 	ASSERT_EQ(0, tracefs_enable_event(
123 			     TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
124 			     false));
125 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false));
126 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false));
127 	ASSERT_EQ(0, tracefs_clear());
128 	clear_cap(_metadata, CAP_SYS_ADMIN);
129 
130 	/*
131 	 * Trigger both allowed and denied accesses to verify neither check_rule
132 	 * nor check_access events fire when disabled.
133 	 */
134 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
135 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
136 
137 	/* Read trace buffer and verify no landlock events at all. */
138 	buf = tracefs_read_buf();
139 	ASSERT_NE(NULL, buf);
140 
141 	EXPECT_EQ(0, tracefs_count_matches(buf, "landlock_"))
142 	{
143 		TH_LOG("Expected 0 landlock events when disabled\n%s", buf);
144 	}
145 
146 	free(buf);
147 }
148 
149 /*
150  * Verifies that landlock_create_ruleset emits a trace event with the correct
151  * handled access and permission masks.
152  */
153 TEST_F(trace, create_ruleset)
154 {
155 	struct landlock_ruleset_attr ruleset_attr = {
156 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
157 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
158 	};
159 	int ruleset_fd;
160 	char *buf, *dot;
161 	char field[64];
162 
163 	ruleset_fd =
164 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
165 	ASSERT_LE(0, ruleset_fd);
166 	ASSERT_EQ(0, close(ruleset_fd));
167 
168 	buf = tracefs_read_buf();
169 	ASSERT_NE(NULL, buf);
170 
171 	EXPECT_EQ(1,
172 		  tracefs_count_matches(buf, REGEX_CREATE_RULESET(TRACE_TASK)))
173 	{
174 		TH_LOG("Expected 1 create_ruleset event\n%s", buf);
175 	}
176 
177 	/* Verify handled_fs matches what we requested. */
178 	EXPECT_EQ(0,
179 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
180 					"handled_fs", field, sizeof(field)));
181 	EXPECT_STREQ("read_file", field);
182 
183 	/* Verify handled_net matches. */
184 	EXPECT_EQ(0,
185 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
186 					"handled_net", field, sizeof(field)));
187 	EXPECT_STREQ("bind_tcp", field);
188 
189 	/* Verify that no permission is handled. */
190 	EXPECT_EQ(0, tracefs_extract_field(
191 			     buf, REGEX_CREATE_RULESET(TRACE_TASK),
192 			     "handled_permissions", field, sizeof(field)));
193 	EXPECT_STREQ("", field);
194 
195 	/* Verify version is 0 at creation (no rules added yet). */
196 	EXPECT_EQ(0,
197 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
198 					"ruleset", field, sizeof(field)));
199 	/* Format is <hex>.<dec>; version is after the dot. */
200 	dot = strchr(field, '.');
201 	ASSERT_NE(0, !!dot);
202 	EXPECT_STREQ("0", dot + 1);
203 
204 	free(buf);
205 }
206 
207 /*
208  * Verifies that the ruleset version increments with each add_rule call and that
209  * create_domain records the correct version.
210  */
211 TEST_F(trace, ruleset_version)
212 {
213 	pid_t pid;
214 	int status;
215 	char *buf;
216 	const char *dot;
217 	char field[64];
218 
219 	ASSERT_EQ(0, tracefs_clear_buf());
220 
221 	pid = fork();
222 	ASSERT_LE(0, pid);
223 
224 	if (pid == 0) {
225 		struct landlock_ruleset_attr ruleset_attr = {
226 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
227 		};
228 		struct landlock_path_beneath_attr path_beneath = {
229 			.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
230 		};
231 		int ruleset_fd;
232 
233 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
234 						     sizeof(ruleset_attr), 0);
235 		if (ruleset_fd < 0)
236 			_exit(1);
237 
238 		/* First rule: version becomes 1. */
239 		path_beneath.parent_fd =
240 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
241 		if (path_beneath.parent_fd < 0)
242 			_exit(1);
243 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
244 				  &path_beneath, 0);
245 		close(path_beneath.parent_fd);
246 
247 		/* Second rule: version becomes 2. */
248 		path_beneath.parent_fd =
249 			open("/tmp", O_PATH | O_DIRECTORY | O_CLOEXEC);
250 		if (path_beneath.parent_fd < 0)
251 			_exit(1);
252 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
253 				  &path_beneath, 0);
254 		close(path_beneath.parent_fd);
255 
256 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
257 		if (landlock_restrict_self(ruleset_fd, 0))
258 			_exit(1);
259 		close(ruleset_fd);
260 		_exit(0);
261 	}
262 
263 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
264 	ASSERT_TRUE(WIFEXITED(status));
265 	EXPECT_EQ(0, WEXITSTATUS(status));
266 
267 	buf = tracefs_read_buf();
268 	ASSERT_NE(NULL, buf);
269 
270 	/* Verify create_ruleset has version=0. */
271 	ASSERT_EQ(0,
272 		  tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK),
273 					"ruleset", field, sizeof(field)));
274 	dot = strchr(field, '.');
275 	ASSERT_NE(0, !!dot);
276 	EXPECT_STREQ("0", dot + 1);
277 
278 	/* Verify two add_rule_path_beneath events were emitted. */
279 	EXPECT_EQ(2, tracefs_count_matches(
280 			     buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
281 	{
282 		TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf);
283 	}
284 
285 	/*
286 	 * Verify create_domain records version=2 (after 2 add_rule calls).  The
287 	 * ruleset field format is <hex_id>.<dec_version>.
288 	 */
289 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
290 					   "ruleset", field, sizeof(field)));
291 	dot = strchr(field, '.');
292 	ASSERT_NE(0, !!dot);
293 	EXPECT_STREQ("2", dot + 1);
294 
295 	free(buf);
296 }
297 
298 /*
299  * Verifies that landlock_create_domain emits a trace event linking the ruleset
300  * ID to the new domain ID.
301  */
302 TEST_F(trace, create_domain)
303 {
304 	pid_t pid;
305 	int status, check_count;
306 	char *buf;
307 	char parent_id[64], domain_id[64], check_domain[64];
308 
309 	/* Clear before the sandboxed child. */
310 	ASSERT_EQ(0, tracefs_clear_buf());
311 
312 	pid = fork();
313 	ASSERT_LE(0, pid);
314 
315 	if (pid == 0) {
316 		struct landlock_ruleset_attr ruleset_attr = {
317 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
318 		};
319 		struct landlock_path_beneath_attr path_beneath = {
320 			.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
321 		};
322 		int ruleset_fd, fd;
323 
324 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
325 						     sizeof(ruleset_attr), 0);
326 		if (ruleset_fd < 0)
327 			_exit(1);
328 
329 		path_beneath.parent_fd =
330 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
331 		if (path_beneath.parent_fd < 0)
332 			_exit(1);
333 
334 		landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
335 				  &path_beneath, 0);
336 		close(path_beneath.parent_fd);
337 
338 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
339 		if (landlock_restrict_self(ruleset_fd, 0))
340 			_exit(1);
341 		close(ruleset_fd);
342 
343 		/* Trigger a check_rule to verify domain_id correlation. */
344 		fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
345 		if (fd >= 0)
346 			close(fd);
347 
348 		_exit(0);
349 	}
350 
351 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
352 	ASSERT_TRUE(WIFEXITED(status));
353 	EXPECT_EQ(0, WEXITSTATUS(status));
354 
355 	buf = tracefs_read_buf();
356 	ASSERT_NE(NULL, buf);
357 
358 	/* Verify create_domain event exists. */
359 	EXPECT_EQ(1,
360 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
361 	{
362 		TH_LOG("Expected 1 create_domain event\n%s", buf);
363 	}
364 
365 	/* Extract the domain ID from create_domain. */
366 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
367 					   "domain", domain_id,
368 					   sizeof(domain_id)));
369 
370 	/* Verify domain ID is non-zero. */
371 	EXPECT_NE(0, strcmp(domain_id, "0"));
372 
373 	/* Verify parent=0 (first restriction, no prior domain). */
374 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
375 					   "parent", parent_id,
376 					   sizeof(parent_id)));
377 	EXPECT_STREQ("0", parent_id);
378 
379 	/*
380 	 * Verify the same domain ID appears in the check_rule event, confirming
381 	 * end-to-end correlation.
382 	 */
383 	check_count =
384 		tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
385 	ASSERT_LE(1, check_count)
386 	{
387 		TH_LOG("Expected check_rule_inode events\n%s", buf);
388 	}
389 
390 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
391 					   "domain", check_domain,
392 					   sizeof(check_domain)));
393 	EXPECT_STREQ(domain_id, check_domain);
394 
395 	free(buf);
396 }
397 
398 /* Builds a rule-less scope-based ruleset; returns the fd or -1. */
399 static int build_enforce_ruleset(void)
400 {
401 	const struct landlock_ruleset_attr attr = {
402 		.scoped = LANDLOCK_SCOPE_SIGNAL,
403 	};
404 
405 	return landlock_create_ruleset(&attr, sizeof(attr), 0);
406 }
407 
408 /*
409  * Verifies that nested landlock_restrict_self calls produce trace events with
410  * correct parent domain IDs: the second create_domain's parent should be the
411  * first domain's ID.
412  */
413 TEST_F(trace, create_domain_nested)
414 {
415 	pid_t pid;
416 	int status;
417 	char *buf;
418 	const char *after_first;
419 	char first_domain[64], first_parent[64], second_parent[64];
420 
421 	ASSERT_EQ(0, tracefs_clear_buf());
422 
423 	pid = fork();
424 	ASSERT_LE(0, pid);
425 
426 	if (pid == 0) {
427 		int ruleset_fd;
428 
429 		/* First restriction. */
430 		ruleset_fd = build_enforce_ruleset();
431 		if (ruleset_fd < 0)
432 			_exit(1);
433 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
434 		if (landlock_restrict_self(ruleset_fd, 0))
435 			_exit(1);
436 		close(ruleset_fd);
437 
438 		/* Second restriction (nested). */
439 		ruleset_fd = build_enforce_ruleset();
440 		if (ruleset_fd < 0)
441 			_exit(1);
442 		if (landlock_restrict_self(ruleset_fd, 0))
443 			_exit(1);
444 		close(ruleset_fd);
445 
446 		_exit(0);
447 	}
448 
449 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
450 	ASSERT_TRUE(WIFEXITED(status));
451 	EXPECT_EQ(0, WEXITSTATUS(status));
452 
453 	buf = tracefs_read_buf();
454 	ASSERT_NE(NULL, buf);
455 
456 	/* Should have 2 create_domain events. */
457 	EXPECT_EQ(2,
458 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
459 	{
460 		TH_LOG("Expected 2 create_domain events\n%s", buf);
461 	}
462 
463 	/*
464 	 * Extract domain and parent from each create_domain event.  The first
465 	 * event (parent=0) is the outer domain; the second (parent!=0) is the
466 	 * nested domain whose parent should match the first domain's ID.
467 	 */
468 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
469 					   "domain", first_domain,
470 					   sizeof(first_domain)));
471 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
472 					   "parent", first_parent,
473 					   sizeof(first_parent)));
474 	EXPECT_STREQ("0", first_parent);
475 
476 	/*
477 	 * Find the second create_domain by scanning past the first.
478 	 * tracefs_extract_field returns the first match, so search in the
479 	 * buffer after the first event.
480 	 *
481 	 * Skip past the first create_domain line. tracefs_extract_field matches
482 	 * the first line that matches the regex, so passing the buffer after
483 	 * the first matching line gives us the second event.
484 	 */
485 	after_first = strstr(buf, "landlock_create_domain:");
486 	ASSERT_NE(NULL, after_first);
487 	after_first = strchr(after_first, '\n');
488 	ASSERT_NE(NULL, after_first);
489 
490 	ASSERT_EQ(0, tracefs_extract_field(
491 			     after_first + 1, REGEX_CREATE_DOMAIN(TRACE_TASK),
492 			     "parent", second_parent, sizeof(second_parent)));
493 
494 	/* The second domain's parent should be the first domain's ID. */
495 	EXPECT_STREQ(first_domain, second_parent);
496 
497 	free(buf);
498 }
499 
500 /*
501  * Verifies that landlock_add_rule does not emit a trace event when the syscall
502  * fails (e.g., invalid ruleset fd).
503  */
504 TEST_F(trace, add_rule_invalid_fd)
505 {
506 	struct landlock_path_beneath_attr path_beneath = {
507 		.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
508 	};
509 	char *buf;
510 
511 	path_beneath.parent_fd = open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
512 	ASSERT_LE(0, path_beneath.parent_fd);
513 
514 	/* Invalid ruleset fd (-1). */
515 	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH,
516 					&path_beneath, 0));
517 	ASSERT_EQ(0, close(path_beneath.parent_fd));
518 
519 	buf = tracefs_read_buf();
520 	ASSERT_NE(NULL, buf);
521 
522 	EXPECT_EQ(0, tracefs_count_matches(
523 			     buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
524 	{
525 		TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s",
526 		       buf);
527 	}
528 
529 	free(buf);
530 }
531 
532 /*
533  * Verifies that landlock_create_domain does not emit a trace event when the
534  * syscall fails (e.g., invalid ruleset fd or unknown flags).
535  */
536 TEST_F(trace, create_domain_invalid)
537 {
538 	int ruleset_fd;
539 	char *buf;
540 
541 	ruleset_fd = build_enforce_ruleset();
542 	ASSERT_LE(0, ruleset_fd);
543 
544 	/* Clear the trace buffer after create_ruleset event. */
545 	ASSERT_EQ(0, tracefs_clear_buf());
546 
547 	/* Invalid fd. */
548 	ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
549 
550 	/* Unknown flags. */
551 	ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, -1));
552 
553 	ASSERT_EQ(0, close(ruleset_fd));
554 
555 	buf = tracefs_read_buf();
556 	ASSERT_NE(NULL, buf);
557 
558 	EXPECT_EQ(0,
559 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)))
560 	{
561 		TH_LOG("No create_domain event expected on error\n%s", buf);
562 	}
563 
564 	free(buf);
565 }
566 
567 /*
568  * Verifies that trace_landlock_free_domain fires when a domain is deallocated,
569  * with the correct denials count.
570  */
571 TEST_F(trace, free_domain)
572 {
573 	char *buf;
574 	int count;
575 	char denials_field[32];
576 
577 	ASSERT_EQ(0, tracefs_clear_buf());
578 
579 	/*
580 	 * The domain is freed via a work queue (kworker), so the free_domain
581 	 * trace event is emitted from a different PID.  Clear the PID filter
582 	 * BEFORE the child exits, so the kworker event passes the filter when
583 	 * it fires.
584 	 */
585 	set_cap(_metadata, CAP_SYS_ADMIN);
586 	tracefs_clear_pid_filter();
587 	clear_cap(_metadata, CAP_SYS_ADMIN);
588 
589 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
590 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
591 
592 	/*
593 	 * Wait for the deferred deallocation work to run.  The domain is freed
594 	 * asynchronously from a kworker; poll until the event appears or a
595 	 * timeout is reached.
596 	 */
597 	for (int retry = 0; retry < 10; retry++) {
598 		usleep(100000);
599 
600 		set_cap(_metadata, CAP_SYS_ADMIN);
601 		buf = tracefs_read_trace();
602 		clear_cap(_metadata, CAP_SYS_ADMIN);
603 		ASSERT_NE(NULL, buf);
604 
605 		count = tracefs_count_matches(buf,
606 					      REGEX_FREE_DOMAIN(KWORKER_TASK));
607 		if (count >= 1)
608 			break;
609 		free(buf);
610 		buf = NULL;
611 	}
612 
613 	set_cap(_metadata, CAP_SYS_ADMIN);
614 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
615 	clear_cap(_metadata, CAP_SYS_ADMIN);
616 
617 	ASSERT_NE(NULL, buf);
618 	EXPECT_LE(1, count)
619 	{
620 		TH_LOG("Expected free_domain event, got %d\n%s", count, buf);
621 	}
622 
623 	/* Verify denials count matches the single denial we triggered. */
624 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK),
625 					   "denials", denials_field,
626 					   sizeof(denials_field)));
627 	EXPECT_STREQ("1", denials_field);
628 
629 	free(buf);
630 }
631 
632 /*
633  * Verifies that deny_access_fs includes the enriched fields: same_exec and
634  * logged.
635  */
636 TEST_F(trace, deny_access_fs_fields)
637 {
638 	char *buf;
639 	char field_buf[64];
640 
641 	ASSERT_EQ(0, tracefs_clear_buf());
642 
643 	/* Trigger a denial: rule for /usr, access /tmp. */
644 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
645 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
646 
647 	buf = tracefs_read_buf();
648 	ASSERT_NE(NULL, buf);
649 
650 	/* Verify the enriched fields are present and have valid values. */
651 	ASSERT_EQ(0, tracefs_extract_field(
652 			     buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "same_exec",
653 			     field_buf, sizeof(field_buf)));
654 	/* Child is the same exec that restricted itself. */
655 	EXPECT_STREQ("1", field_buf);
656 
657 	/* Same exec with default flags: audit would log this denial. */
658 	ASSERT_EQ(0, tracefs_extract_field(
659 			     buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "logged",
660 			     field_buf, sizeof(field_buf)));
661 	EXPECT_STREQ("1", field_buf);
662 
663 	free(buf);
664 }
665 
666 /*
667  * Verifies that same_exec is 1 (true) for denials from the same executable that
668  * called landlock_restrict_self().
669  */
670 TEST_F(trace, same_exec_before_exec)
671 {
672 	pid_t pid;
673 	int status;
674 	char *buf;
675 	char field[64];
676 
677 	ASSERT_EQ(0, tracefs_clear_buf());
678 
679 	pid = fork();
680 	ASSERT_LE(0, pid);
681 
682 	if (pid == 0) {
683 		struct landlock_ruleset_attr attr = {
684 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
685 		};
686 		int ruleset_fd, dir_fd;
687 
688 		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
689 		if (ruleset_fd < 0)
690 			_exit(1);
691 
692 		/* No rules: all read_dir access is denied. */
693 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
694 		if (landlock_restrict_self(ruleset_fd, 0))
695 			_exit(1);
696 		close(ruleset_fd);
697 
698 		/* Trigger denial without exec (same executable). */
699 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
700 		if (dir_fd >= 0)
701 			close(dir_fd);
702 		_exit(0);
703 	}
704 
705 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
706 	ASSERT_TRUE(WIFEXITED(status));
707 	EXPECT_EQ(0, WEXITSTATUS(status));
708 
709 	buf = tracefs_read_buf();
710 	ASSERT_NE(NULL, buf);
711 
712 	/* Should have at least one deny_access_fs denial. */
713 	EXPECT_LE(1,
714 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)));
715 
716 	/* Verify same_exec=1 (same executable, no exec). */
717 	ASSERT_EQ(0,
718 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
719 					"same_exec", field, sizeof(field)));
720 	EXPECT_STREQ("1", field);
721 
722 	/* Same exec with default flags: audit would log this denial. */
723 	ASSERT_EQ(0,
724 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
725 					"logged", field, sizeof(field)));
726 	EXPECT_STREQ("1", field);
727 
728 	free(buf);
729 }
730 
731 /*
732  * Verifies that same_exec is 0 (false) for denials from a process that has
733  * exec'd a new binary after landlock_restrict_self().  The sandboxed child
734  * exec's true which opens "." and triggers a read_dir denial.  Covers the
735  * "trace-only" visibility condition: with same_exec=0 and the default
736  * log_new_exec=0, audit suppresses the denial (logged=0) but the trace event
737  * still fires.
738  */
739 TEST_F(trace, same_exec_after_exec)
740 {
741 	char *buf;
742 	char field[64];
743 
744 	ASSERT_EQ(0, tracefs_clear_buf());
745 
746 	sandbox_child_exec_true(_metadata, 0);
747 
748 	buf = tracefs_read_buf();
749 	ASSERT_NE(NULL, buf);
750 
751 	EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true")));
752 
753 	/* Verify same_exec=0 (different executable after exec). */
754 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
755 					   "same_exec", field, sizeof(field)));
756 	EXPECT_STREQ("0", field);
757 
758 	/*
759 	 * same_exec=0 with default log_new_exec=0: audit suppresses (logged=0).
760 	 */
761 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
762 					   "logged", field, sizeof(field)));
763 	EXPECT_STREQ("0", field);
764 
765 	free(buf);
766 }
767 
768 /*
769  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF suppresses logging
770  * (logged=0) for a denial from the same executable.
771  */
772 TEST_F(trace, log_flags_same_exec_off)
773 {
774 	pid_t pid;
775 	int status;
776 	char *buf;
777 	char field[64];
778 
779 	ASSERT_EQ(0, tracefs_clear_buf());
780 
781 	pid = fork();
782 	ASSERT_LE(0, pid);
783 
784 	if (pid == 0) {
785 		struct landlock_ruleset_attr attr = {
786 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
787 		};
788 		int ruleset_fd, dir_fd;
789 
790 		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
791 		if (ruleset_fd < 0)
792 			_exit(1);
793 
794 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
795 		if (landlock_restrict_self(
796 			    ruleset_fd,
797 			    LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF))
798 			_exit(1);
799 		close(ruleset_fd);
800 
801 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
802 		if (dir_fd >= 0)
803 			close(dir_fd);
804 		_exit(0);
805 	}
806 
807 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
808 	ASSERT_TRUE(WIFEXITED(status));
809 	EXPECT_EQ(0, WEXITSTATUS(status));
810 
811 	buf = tracefs_read_buf();
812 	ASSERT_NE(NULL, buf);
813 
814 	EXPECT_LE(1,
815 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)));
816 
817 	/* Same-exec denial with LOG_SAME_EXEC_OFF: audit suppresses it. */
818 	ASSERT_EQ(0,
819 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
820 					"logged", field, sizeof(field)));
821 	EXPECT_STREQ("0", field);
822 
823 	free(buf);
824 }
825 
826 /*
827  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON causes a post-exec
828  * denial to be logged (logged=1).  The child exec's true so that the denial
829  * comes from a new executable (same_exec=0).
830  */
831 TEST_F(trace, log_flags_new_exec_on)
832 {
833 	char *buf;
834 	char field[64];
835 
836 	ASSERT_EQ(0, tracefs_clear_buf());
837 
838 	sandbox_child_exec_true(_metadata,
839 				LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
840 
841 	buf = tracefs_read_buf();
842 	ASSERT_NE(NULL, buf);
843 
844 	EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true")));
845 
846 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
847 					   "same_exec", field, sizeof(field)));
848 	EXPECT_STREQ("0", field);
849 
850 	/* LOG_NEW_EXEC_ON: the post-exec denial (same_exec=0) is logged. */
851 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"),
852 					   "logged", field, sizeof(field)));
853 	EXPECT_STREQ("1", field);
854 
855 	free(buf);
856 }
857 
858 /*
859  * Verifies that denials suppressed by audit log flags are still counted in
860  * num_denials.  The child restricts itself with default flags (log_same_exec=1,
861  * log_new_exec=0), then execs true which attempts to read a denied directory.
862  * After exec, same_exec=0 and log_new_exec=0, so audit suppresses the denial.
863  * But the trace event fires unconditionally and free_domain must report the
864  * correct denials count.
865  */
866 TEST_F(trace, non_audit_visible_denial_counting)
867 {
868 	char *buf = NULL;
869 	char denials_field[32];
870 	int count;
871 
872 	set_cap(_metadata, CAP_SYS_ADMIN);
873 	ASSERT_EQ(0, tracefs_clear());
874 	tracefs_clear_pid_filter();
875 	clear_cap(_metadata, CAP_SYS_ADMIN);
876 
877 	sandbox_child_exec_true(_metadata, 0);
878 
879 	/* Wait for free_domain event with retry. */
880 	for (int retry = 0; retry < 10; retry++) {
881 		usleep(100000);
882 
883 		set_cap(_metadata, CAP_SYS_ADMIN);
884 		buf = tracefs_read_trace();
885 		clear_cap(_metadata, CAP_SYS_ADMIN);
886 		if (!buf)
887 			break;
888 
889 		count = tracefs_count_matches(buf,
890 					      REGEX_FREE_DOMAIN(KWORKER_TASK));
891 		if (count >= 1)
892 			break;
893 		free(buf);
894 		buf = NULL;
895 	}
896 
897 	set_cap(_metadata, CAP_SYS_ADMIN);
898 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
899 	clear_cap(_metadata, CAP_SYS_ADMIN);
900 
901 	/*
902 	 * The denial happened after exec (same_exec=0), so audit would suppress
903 	 * it.  But num_denials counts all denials regardless.
904 	 */
905 	ASSERT_NE(NULL, buf)
906 	{
907 		TH_LOG("free_domain event not found after 10 retries");
908 	}
909 	EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK),
910 					   "denials", denials_field,
911 					   sizeof(denials_field)));
912 	EXPECT_STREQ("1", denials_field);
913 
914 	free(buf);
915 }
916 
917 /*
918  * Verifies that landlock_add_rule_net_port emits a trace event with the correct
919  * port and allowed access mask fields.
920  */
921 TEST_F(trace, add_rule_net_port_fields)
922 {
923 	struct landlock_ruleset_attr ruleset_attr = {
924 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
925 	};
926 	struct landlock_net_port_attr net_port = {
927 		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
928 		.port = 8080,
929 	};
930 	int ruleset_fd;
931 	char *buf;
932 	char field[64];
933 
934 	ruleset_fd =
935 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
936 	ASSERT_LE(0, ruleset_fd);
937 
938 	ASSERT_EQ(0, tracefs_clear_buf());
939 
940 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
941 				       &net_port, 0));
942 	close(ruleset_fd);
943 
944 	buf = tracefs_read_buf();
945 	ASSERT_NE(NULL, buf);
946 
947 	EXPECT_EQ(1, tracefs_count_matches(buf,
948 					   REGEX_ADD_RULE_NET_PORT(TRACE_TASK)))
949 	{
950 		TH_LOG("Expected 1 add_rule_net_port event\n%s", buf);
951 	}
952 
953 	/*
954 	 * Verify the port is in host endianness, matching the UAPI convention
955 	 * (landlock_net_port_attr.port).  On little-endian, htons(8080) is
956 	 * 36895, so this comparison catches byte-order bugs.
957 	 */
958 	EXPECT_EQ(0, tracefs_extract_field(buf,
959 					   REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
960 					   "port", field, sizeof(field)));
961 	EXPECT_STREQ("8080", field);
962 	/*
963 	 * The allowed mask is the absolute value after transformation: the
964 	 * user-requested BIND_TCP plus all unhandled access rights (the other
965 	 * net access bits are unhandled because the ruleset only handles
966 	 * BIND_TCP).
967 	 */
968 	EXPECT_EQ(0, tracefs_extract_field(
969 			     buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
970 			     "access_rights", field, sizeof(field)));
971 	EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field);
972 
973 	free(buf);
974 }
975 
976 /*
977  * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF suppresses audit
978  * logging for child domains (logged=0) even though the child's own
979  * per-execution flags are the defaults, while the trace event still fires
980  * (tracing is unconditional).  The parent creates a domain with
981  * LOG_SUBDOMAINS_OFF, then the child creates a sub-domain and triggers a
982  * denial.
983  */
984 TEST_F(trace, log_flags_subdomains_off)
985 {
986 	pid_t pid;
987 	int status;
988 	char *buf;
989 	char field[64];
990 
991 	ASSERT_EQ(0, tracefs_clear_buf());
992 
993 	pid = fork();
994 	ASSERT_LE(0, pid);
995 
996 	if (pid == 0) {
997 		struct landlock_ruleset_attr attr = {
998 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
999 		};
1000 		int parent_fd, child_fd, dir_fd;
1001 
1002 		/* Parent domain with LOG_SUBDOMAINS_OFF. */
1003 		parent_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
1004 		if (parent_fd < 0)
1005 			_exit(1);
1006 
1007 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1008 		if (landlock_restrict_self(
1009 			    parent_fd,
1010 			    LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF))
1011 			_exit(1);
1012 		close(parent_fd);
1013 
1014 		/* Child sub-domain with default flags. */
1015 		child_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
1016 		if (child_fd < 0)
1017 			_exit(1);
1018 
1019 		if (landlock_restrict_self(child_fd, 0))
1020 			_exit(1);
1021 		close(child_fd);
1022 
1023 		/* Trigger a denial from the child domain. */
1024 		dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
1025 		if (dir_fd >= 0)
1026 			close(dir_fd);
1027 		_exit(0);
1028 	}
1029 
1030 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1031 	ASSERT_TRUE(WIFEXITED(status));
1032 	EXPECT_EQ(0, WEXITSTATUS(status));
1033 
1034 	buf = tracefs_read_buf();
1035 	ASSERT_NE(NULL, buf);
1036 
1037 	/*
1038 	 * Trace fires unconditionally even though audit is disabled for the
1039 	 * child domain (parent had LOG_SUBDOMAINS_OFF).
1040 	 */
1041 	EXPECT_LE(1,
1042 		  tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)))
1043 	{
1044 		TH_LOG("Expected deny_access_fs event despite "
1045 		       "LOG_SUBDOMAINS_OFF\n%s",
1046 		       buf);
1047 	}
1048 
1049 	/*
1050 	 * The child's per-execution flags default to logging, but the
1051 	 * ancestor's LOG_SUBDOMAINS_OFF disables it, so audit suppresses this
1052 	 * denial (logged=0).  This is exactly the case the single logged field
1053 	 * captures and the raw per-execution flags could not.
1054 	 */
1055 	ASSERT_EQ(0,
1056 		  tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK),
1057 					"logged", field, sizeof(field)));
1058 	EXPECT_STREQ("0", field);
1059 
1060 	free(buf);
1061 }
1062 
1063 /* Verifies that landlock_free_ruleset fires when a ruleset FD is closed. */
1064 TEST_F(trace, free_ruleset_on_close)
1065 {
1066 	struct landlock_ruleset_attr ruleset_attr = {
1067 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
1068 	};
1069 	int ruleset_fd;
1070 	char *buf;
1071 
1072 	ruleset_fd =
1073 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
1074 	ASSERT_LE(0, ruleset_fd);
1075 
1076 	ASSERT_EQ(0, tracefs_clear_buf());
1077 
1078 	/* Closing the FD should trigger free_ruleset. */
1079 	close(ruleset_fd);
1080 
1081 	buf = tracefs_read_buf();
1082 	ASSERT_NE(NULL, buf);
1083 
1084 	EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_FREE_RULESET(TRACE_TASK)))
1085 	{
1086 		TH_LOG("Expected 1 free_ruleset event\n%s", buf);
1087 	}
1088 
1089 	free(buf);
1090 }
1091 
1092 /*
1093  * Counts landlock_enforce_domain lines, filtered by @domain (NULL matches any),
1094  * @complete and @process_wide (a negative value matches any).  Builds the
1095  * anchored regex dynamically so a single helper covers every field assertion.
1096  */
1097 static int count_enforce_matches(const char *buf, const char *domain,
1098 				 int complete, int process_wide,
1099 				 int no_new_privs)
1100 {
1101 	char pattern[512], dom[80], comp[8], pw[8], nnp[8];
1102 
1103 	if (domain)
1104 		snprintf(dom, sizeof(dom), "%s", domain);
1105 	else
1106 		snprintf(dom, sizeof(dom), "[0-9a-f]\\+");
1107 	if (complete < 0)
1108 		snprintf(comp, sizeof(comp), "[01]");
1109 	else
1110 		snprintf(comp, sizeof(comp), "%d", complete);
1111 	if (process_wide < 0)
1112 		snprintf(pw, sizeof(pw), "[01]");
1113 	else
1114 		snprintf(pw, sizeof(pw), "%d", process_wide);
1115 	if (no_new_privs < 0)
1116 		snprintf(nnp, sizeof(nnp), "[01]");
1117 	else
1118 		snprintf(nnp, sizeof(nnp), "%d", no_new_privs);
1119 
1120 	snprintf(pattern, sizeof(pattern),
1121 		 TRACE_PREFIX(TRACE_TASK) "landlock_enforce_domain: "
1122 					  "domain=%s "
1123 					  "complete=%s process_wide=%s "
1124 					  "no_new_privs=%s$",
1125 		 dom, comp, pw, nnp);
1126 	return tracefs_count_matches(buf, pattern);
1127 }
1128 
1129 /* Idle sibling: waits on the barrier so it is a live thread, then sleeps. */
1130 static void *enforce_idle(void *arg)
1131 {
1132 	pthread_barrier_t *barrier = arg;
1133 
1134 	pthread_barrier_wait(barrier);
1135 	while (true)
1136 		sleep(1);
1137 	return NULL;
1138 }
1139 
1140 /*
1141  * Child body: spawns @nthreads idle siblings (barrier-synchronized so they are
1142  * live when the syscall runs), then enforces a domain with @flags.  Returns 0
1143  * on success; the process exits afterwards, reaping the siblings.
1144  */
1145 static int child_enforce(int nthreads, __u32 flags)
1146 {
1147 	pthread_t threads[8];
1148 	pthread_barrier_t barrier;
1149 	int ruleset_fd, i;
1150 
1151 	if (nthreads > 0) {
1152 		if (pthread_barrier_init(&barrier, NULL, nthreads + 1))
1153 			return 1;
1154 		for (i = 0; i < nthreads; i++)
1155 			if (pthread_create(&threads[i], NULL, enforce_idle,
1156 					   &barrier))
1157 				return 1;
1158 		pthread_barrier_wait(&barrier);
1159 	}
1160 
1161 	ruleset_fd = build_enforce_ruleset();
1162 	if (ruleset_fd < 0)
1163 		return 1;
1164 
1165 	/*
1166 	 * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs itself, so skip
1167 	 * the prctl() to exercise that path; otherwise Landlock requires
1168 	 * no_new_privs up front.
1169 	 */
1170 	if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS))
1171 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1172 	if (landlock_restrict_self(ruleset_fd, flags))
1173 		return 1;
1174 	close(ruleset_fd);
1175 	return 0;
1176 }
1177 
1178 /*
1179  * Runs in a spawned thread after the group leader called pthread_exit().  The
1180  * leader lingers as an un-reaped zombie, so get_nr_threads() still counts it
1181  * and this non-leader is not the only thread; enforcing here therefore reports
1182  * process_wide=0.
1183  */
1184 static void *enforce_nonleader(void *arg)
1185 {
1186 	int ruleset_fd;
1187 
1188 	ruleset_fd = build_enforce_ruleset();
1189 	if (ruleset_fd < 0)
1190 		_exit(1);
1191 	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1192 	if (landlock_restrict_self(ruleset_fd, 0))
1193 		_exit(1);
1194 	_exit(0);
1195 }
1196 
1197 /*
1198  * Collapses the enforce_domain field cases into one parametrized test.  Each
1199  * variant runs child_enforce(nthreads, flags) and checks the resulting
1200  * enforce_domain events.  The flags column also selects how no_new_privs is
1201  * set: with LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS child_enforce() skips the
1202  * prctl() so the flag sets it (and, with TSYNC, propagates to the siblings);
1203  * otherwise a prior prctl() sets it on the caller (and TSYNC propagates that).
1204  */
1205 
1206 /* clang-format off */
1207 FIXTURE(trace_enforce) {
1208 	/* clang-format on */
1209 	int tracefs_ok;
1210 };
1211 
1212 FIXTURE_SETUP(trace_enforce)
1213 {
1214 	int ret;
1215 
1216 	set_cap(_metadata, CAP_SYS_ADMIN);
1217 	ASSERT_EQ(0, unshare(CLONE_NEWNS));
1218 	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
1219 
1220 	ret = tracefs_fixture_setup();
1221 	if (ret) {
1222 		clear_cap(_metadata, CAP_SYS_ADMIN);
1223 		self->tracefs_ok = 0;
1224 		SKIP(return, "tracefs not available");
1225 	}
1226 	self->tracefs_ok = 1;
1227 
1228 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
1229 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
1230 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
1231 	ASSERT_EQ(0, tracefs_clear());
1232 	clear_cap(_metadata, CAP_SYS_ADMIN);
1233 }
1234 
1235 FIXTURE_TEARDOWN(trace_enforce)
1236 {
1237 	if (!self->tracefs_ok)
1238 		return;
1239 
1240 	set_cap(_metadata, CAP_SYS_ADMIN);
1241 	tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
1242 	tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
1243 	tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
1244 	tracefs_fixture_teardown();
1245 	clear_cap(_metadata, CAP_SYS_ADMIN);
1246 }
1247 
1248 /* clang-format off */
1249 FIXTURE_VARIANT(trace_enforce) {
1250 	/* clang-format on */
1251 	/* Inputs to child_enforce(). */
1252 	int nthreads;
1253 	__u32 flags;
1254 	/* Expected enforce_domain event counts. */
1255 	int total;
1256 	int complete;
1257 	int process_wide;
1258 	int no_new_privs;
1259 };
1260 
1261 /* clang-format off */
1262 
1263 /* Single thread, no flags: prctl-backed no_new_privs. */
1264 FIXTURE_VARIANT_ADD(trace_enforce, single) {
1265 	.nthreads = 0, .flags = 0,
1266 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1267 };
1268 
1269 /* Single thread: the NO_NEW_PRIVS flag sets no_new_privs (no prctl). */
1270 FIXTURE_VARIANT_ADD(trace_enforce, no_new_privs) {
1271 	.nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
1272 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1273 };
1274 
1275 /* TSYNC on a lone thread still concludes, process-wide. */
1276 FIXTURE_VARIANT_ADD(trace_enforce, tsync_single) {
1277 	.nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_TSYNC,
1278 	.total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1,
1279 };
1280 
1281 /* TSYNC sweeps N siblings; the caller's prctl-backed nnp propagates to all. */
1282 FIXTURE_VARIANT_ADD(trace_enforce, tsync_multithread) {
1283 	.nthreads = 3, .flags = LANDLOCK_RESTRICT_SELF_TSYNC,
1284 	.total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4,
1285 };
1286 
1287 /* TSYNC + NO_NEW_PRIVS flag sets nnp on the caller and every swept sibling. */
1288 FIXTURE_VARIANT_ADD(trace_enforce, tsync_no_new_privs) {
1289 	.nthreads = 3,
1290 	.flags = LANDLOCK_RESTRICT_SELF_TSYNC | LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS,
1291 	.total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4,
1292 };
1293 
1294 /* Non-TSYNC on a multi-threaded process enforces only the caller. */
1295 FIXTURE_VARIANT_ADD(trace_enforce, multithread_non_tsync) {
1296 	.nthreads = 3, .flags = 0,
1297 	.total = 1, .complete = 1, .process_wide = 0, .no_new_privs = 1,
1298 };
1299 
1300 /* clang-format on */
1301 
1302 /*
1303  * One create_domain and variant->total enforce_domain events sharing that
1304  * domain ID; complete=1 marks the single concluding event, and the process_wide
1305  * / no_new_privs counts match the variant.  Counts are order-independent,
1306  * evaluated after the syscall returns.
1307  */
1308 TEST_F(trace_enforce, enforce)
1309 {
1310 	pid_t pid;
1311 	int status;
1312 	char *buf;
1313 	char domain[64];
1314 
1315 	ASSERT_EQ(0, tracefs_clear_buf());
1316 
1317 	pid = fork();
1318 	ASSERT_LE(0, pid);
1319 	if (pid == 0)
1320 		_exit(child_enforce(variant->nthreads, variant->flags));
1321 
1322 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1323 	ASSERT_TRUE(WIFEXITED(status));
1324 	EXPECT_EQ(0, WEXITSTATUS(status));
1325 
1326 	buf = tracefs_read_buf();
1327 	ASSERT_NE(NULL, buf);
1328 
1329 	EXPECT_EQ(1,
1330 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1331 	EXPECT_EQ(variant->total, count_enforce_matches(buf, NULL, -1, -1, -1))
1332 	{
1333 		TH_LOG("Expected %d enforce_domain events\n%s", variant->total,
1334 		       buf);
1335 	}
1336 	EXPECT_EQ(variant->complete,
1337 		  count_enforce_matches(buf, NULL, 1, -1, -1));
1338 	EXPECT_EQ(variant->total - variant->complete,
1339 		  count_enforce_matches(buf, NULL, 0, -1, -1));
1340 	EXPECT_EQ(variant->process_wide,
1341 		  count_enforce_matches(buf, NULL, -1, 1, -1));
1342 	EXPECT_EQ(variant->total - variant->process_wide,
1343 		  count_enforce_matches(buf, NULL, -1, 0, -1));
1344 	EXPECT_EQ(variant->no_new_privs,
1345 		  count_enforce_matches(buf, NULL, -1, -1, 1));
1346 
1347 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK),
1348 					   "domain", domain, sizeof(domain)));
1349 	EXPECT_EQ(variant->total,
1350 		  count_enforce_matches(buf, domain, -1, -1, -1));
1351 
1352 	free(buf);
1353 }
1354 
1355 /*
1356  * A non-leader thread enforcing a domain while the group leader lingers as an
1357  * un-reaped zombie reports process_wide=0: get_nr_threads() counts the zombie
1358  * leader, so the group is not single-threaded.  This is the reachable half of
1359  * the caveat that process_wide==0 never proves the process is multi-threaded
1360  * (get_nr_threads(), unlike the leader-relative thread_group_empty(), counts
1361  * the zombie leader).
1362  */
1363 TEST_F(trace, enforce_single_non_leader)
1364 {
1365 	pid_t pid;
1366 	int status;
1367 	char *buf;
1368 
1369 	ASSERT_EQ(0, tracefs_clear_buf());
1370 
1371 	pid = fork();
1372 	ASSERT_LE(0, pid);
1373 	if (pid == 0) {
1374 		pthread_t worker;
1375 
1376 		if (pthread_create(&worker, NULL, enforce_nonleader, NULL))
1377 			_exit(1);
1378 		/* Leader leaves; the worker enforces as a non-leader. */
1379 		pthread_exit(NULL);
1380 	}
1381 
1382 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1383 	ASSERT_TRUE(WIFEXITED(status));
1384 	EXPECT_EQ(0, WEXITSTATUS(status));
1385 
1386 	buf = tracefs_read_buf();
1387 	ASSERT_NE(NULL, buf);
1388 
1389 	EXPECT_EQ(1,
1390 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1391 	EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 0, -1))
1392 	{
1393 		TH_LOG("Expected complete=1 process_wide=0 for non-leader\n%s",
1394 		       buf);
1395 	}
1396 
1397 	free(buf);
1398 }
1399 
1400 /*
1401  * Verifies the flags-only path (ruleset_fd == -1) creates no domain and emits
1402  * neither create_domain nor enforce_domain, with and without TSYNC.
1403  */
1404 TEST_F(trace, enforce_flags_only)
1405 {
1406 	pid_t pid;
1407 	int status;
1408 	char *buf;
1409 
1410 	ASSERT_EQ(0, tracefs_clear_buf());
1411 
1412 	pid = fork();
1413 	ASSERT_LE(0, pid);
1414 	if (pid == 0) {
1415 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1416 		if (landlock_restrict_self(
1417 			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF))
1418 			_exit(1);
1419 		if (landlock_restrict_self(
1420 			    -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
1421 					LANDLOCK_RESTRICT_SELF_TSYNC))
1422 			_exit(1);
1423 		_exit(0);
1424 	}
1425 
1426 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1427 	ASSERT_TRUE(WIFEXITED(status));
1428 	EXPECT_EQ(0, WEXITSTATUS(status));
1429 
1430 	buf = tracefs_read_buf();
1431 	ASSERT_NE(NULL, buf);
1432 
1433 	EXPECT_EQ(0,
1434 		  tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK)));
1435 	EXPECT_EQ(0, count_enforce_matches(buf, NULL, -1, -1, -1))
1436 	{
1437 		TH_LOG("No enforce_domain expected on flags-only path\n%s",
1438 		       buf);
1439 	}
1440 
1441 	free(buf);
1442 }
1443 
1444 static void enforce_nop_handler(int sig)
1445 {
1446 }
1447 
1448 struct abort_signaler_data {
1449 	pthread_t target;
1450 	volatile bool stop;
1451 };
1452 
1453 /*
1454  * Hammers the target thread with SIGUSR1 to interrupt the TSYNC prepare wait.
1455  */
1456 static void *abort_signaler(void *arg)
1457 {
1458 	struct abort_signaler_data *data = arg;
1459 
1460 	while (!data->stop)
1461 		pthread_kill(data->target, SIGUSR1);
1462 	return NULL;
1463 }
1464 
1465 /*
1466  * Child body for the abort test: with idle siblings and a signaler interrupting
1467  * it, repeatedly enforces under TSYNC.  An interrupted attempt aborts its
1468  * just-created domain (create_domain + free_domain, zero enforce_domain) while
1469  * -ERESTARTNOINTR transparently restarts the syscall, so a successful retry may
1470  * add its own full lifecycle.
1471  */
1472 static int child_abort(int nsiblings, int attempts)
1473 {
1474 	pthread_t threads[200];
1475 	pthread_t signaler;
1476 	pthread_barrier_t barrier;
1477 	struct abort_signaler_data data = {};
1478 	struct sigaction sa = {};
1479 	int i;
1480 
1481 	sa.sa_handler = enforce_nop_handler;
1482 	if (sigaction(SIGUSR1, &sa, NULL))
1483 		return 1;
1484 
1485 	if (pthread_barrier_init(&barrier, NULL, nsiblings + 1))
1486 		return 1;
1487 	for (i = 0; i < nsiblings; i++)
1488 		if (pthread_create(&threads[i], NULL, enforce_idle, &barrier))
1489 			return 1;
1490 	pthread_barrier_wait(&barrier);
1491 
1492 	data.target = pthread_self();
1493 	if (pthread_create(&signaler, NULL, abort_signaler, &data))
1494 		return 1;
1495 
1496 	prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1497 	for (i = 0; i < attempts; i++) {
1498 		int ruleset_fd = build_enforce_ruleset();
1499 
1500 		if (ruleset_fd < 0)
1501 			break;
1502 		/*
1503 		 * Ignore the result: an abort returns an error, that is fine.
1504 		 */
1505 		landlock_restrict_self(ruleset_fd,
1506 				       LANDLOCK_RESTRICT_SELF_TSYNC);
1507 		close(ruleset_fd);
1508 	}
1509 
1510 	data.stop = true;
1511 	pthread_join(signaler, NULL);
1512 	return 0;
1513 }
1514 
1515 /*
1516  * Verifies the abort contract: a domain aborted by a thread-sync failure emits
1517  * create_domain and free_domain but zero enforce_domain.  The signal race is
1518  * probabilistic and -ERESTARTNOINTR may add a successful retry's lifecycle, so
1519  * events are grouped by domain ID and the test SKIPs if no abort occurred.
1520  */
1521 TEST_F(trace, enforce_abort)
1522 {
1523 	pid_t pid;
1524 	int status, retry;
1525 	char *buf = NULL;
1526 	const char *cursor;
1527 	char domain[64];
1528 	bool abort_found = false;
1529 
1530 	ASSERT_EQ(0, tracefs_clear_buf());
1531 
1532 	/* free_domain fires from a kworker, so widen the filter first. */
1533 	set_cap(_metadata, CAP_SYS_ADMIN);
1534 	tracefs_clear_pid_filter();
1535 	clear_cap(_metadata, CAP_SYS_ADMIN);
1536 
1537 	pid = fork();
1538 	ASSERT_LE(0, pid);
1539 	if (pid == 0)
1540 		/*
1541 		 * Match tsync_test's NUM_IDLE_THREADS: enough siblings that
1542 		 * credential preparation runs in several serialized waves,
1543 		 * giving the signaler a window to interrupt the thread-sync
1544 		 * wait and abort the operation.  A handful of threads finishes
1545 		 * in a single wave, leaving no window (the abort never fires).
1546 		 */
1547 		_exit(child_abort(200, 8));
1548 
1549 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
1550 	ASSERT_TRUE(WIFEXITED(status));
1551 	EXPECT_EQ(0, WEXITSTATUS(status));
1552 
1553 	/* Poll for the asynchronous free_domain events. */
1554 	for (retry = 0; retry < 10; retry++) {
1555 		usleep(100000);
1556 		set_cap(_metadata, CAP_SYS_ADMIN);
1557 		free(buf);
1558 		buf = tracefs_read_trace();
1559 		clear_cap(_metadata, CAP_SYS_ADMIN);
1560 		ASSERT_NE(NULL, buf);
1561 	}
1562 
1563 	set_cap(_metadata, CAP_SYS_ADMIN);
1564 	ASSERT_EQ(0, tracefs_set_pid_filter(getpid()));
1565 	clear_cap(_metadata, CAP_SYS_ADMIN);
1566 
1567 	/*
1568 	 * Walk every create_domain and look for one whose domain ID has zero
1569 	 * enforce_domain events but a matching free_domain: that is an aborted
1570 	 * domain (created, never enforced, freed).
1571 	 */
1572 	cursor = buf;
1573 	while (tracefs_extract_field(cursor, REGEX_CREATE_DOMAIN(TRACE_TASK),
1574 				     "domain", domain, sizeof(domain)) == 0) {
1575 		const char *cd, *nl;
1576 		char free_pattern[256];
1577 
1578 		if (count_enforce_matches(buf, domain, -1, -1, -1) == 0) {
1579 			snprintf(
1580 				free_pattern, sizeof(free_pattern),
1581 				TRACE_PREFIX(
1582 					KWORKER_TASK) "landlock_free_domain: "
1583 						      "domain=%s denials=[0-9]\\+$",
1584 				domain);
1585 			if (tracefs_count_matches(buf, free_pattern) >= 1)
1586 				abort_found = true;
1587 		}
1588 
1589 		cd = strstr(cursor, "landlock_create_domain:");
1590 		if (!cd)
1591 			break;
1592 		nl = strchr(cd, '\n');
1593 		if (!nl)
1594 			break;
1595 		cursor = nl + 1;
1596 	}
1597 
1598 	if (!abort_found) {
1599 		free(buf);
1600 		SKIP(return, "signal race did not produce a thread-sync abort");
1601 	}
1602 
1603 	free(buf);
1604 }
1605 
1606 /*
1607  * The following tests are intentionally elided because the underlying kernel
1608  * mechanisms are already validated by audit tests:
1609  *
1610  * - Domain ID monotonicity: validated by audit_test.c:layers.  The same
1611  *   landlock_get_id_range() function serves both audit and trace.
1612  *
1613  * - Domain deallocation order (LIFO): validated by audit_test.c:layers.  Trace
1614  *   events fire from the same free_domain_work() code path.
1615  *
1616  * - Max-layer stacking (16 domains): validated by audit_test.c:layers.
1617  *
1618  * - IPv6 network tests: IPv6 hook dispatch uses the same
1619  *   current_check_access_socket() as IPv4, validated by net_test.c:audit tests.
1620  *
1621  * - Per-access-right full matrix (all 16 FS rights): hook dispatch is validated
1622  *   by fs_test.c:audit tests.  Trace tests verify representative samples to
1623  *   ensure bitmask encoding is correct.
1624  *
1625  * - Combined log flag variants (e.g., LOG_SUBDOMAINS_OFF + LOG_NEW_EXEC_ON):
1626  *   individual flag tests above cover each flag's effect on trace fields.  Flag
1627  *   combination logic is validated by audit_test.c:audit_flags tests.
1628  *
1629  * - fs.refer multi-record denials and fs.change_topology (mount):
1630  *   trace_denial() uses the same code path for all FS request types.  The
1631  *   DENTRY union member is validated by the deny_access_fs_fields
1632  *   test.  Audit tests in fs_test.c cover refer and mount denial specifics.
1633  */
1634 
1635 TEST_HARNESS_MAIN
1636