xref: /linux/tools/testing/selftests/landlock/trace_fs_test.c (revision 83684c4e4d62cb02b2e4d0d18963d1035439278e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Landlock tests - Filesystem 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 <sched.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <sys/mount.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <unistd.h>
20 
21 #include "common.h"
22 #include "trace.h"
23 
24 #define TRACE_TASK "trace_fs_test"
25 
26 /*
27  * Like REGEX_DENY_ACCESS_FS(), but pins the logged field to a specific value
28  * ("0" or "1") so a test can tell a suppressed (quiet) denial from a logged
29  * one.  The tracepoint fires for every denial; logged carries the audit
30  * verdict.
31  */
32 #define REGEX_DENY_ACCESS_FS_LOGGED(task, log) \
33 	TRACE_PREFIX(task)                     \
34 	"landlock_deny_access_fs: "            \
35 	"domain=[0-9a-f]\\+ "                  \
36 	"same_exec=[01] "                      \
37 	"logged=" log " "                      \
38 	"blockers=[a-z_|]* "                   \
39 	"dev=[0-9]\\+:[0-9]\\+ "               \
40 	"ino=[0-9]\\+ "                        \
41 	"path=[^ ]*$"
42 
43 /* clang-format off */
44 FIXTURE(trace_fs) {
45 	/* clang-format on */
46 	int tracefs_ok;
47 };
48 
49 FIXTURE_SETUP(trace_fs)
50 {
51 	int ret;
52 
53 	set_cap(_metadata, CAP_SYS_ADMIN);
54 	ASSERT_EQ(0, unshare(CLONE_NEWNS));
55 	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
56 
57 	ret = tracefs_fixture_setup();
58 	if (ret) {
59 		clear_cap(_metadata, CAP_SYS_ADMIN);
60 		self->tracefs_ok = 0;
61 		SKIP(return, "tracefs not available");
62 	}
63 	self->tracefs_ok = 1;
64 
65 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
66 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
67 	ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
68 	ASSERT_EQ(0, tracefs_clear());
69 	clear_cap(_metadata, CAP_SYS_ADMIN);
70 }
71 
72 FIXTURE_TEARDOWN(trace_fs)
73 {
74 	if (!self->tracefs_ok)
75 		return;
76 
77 	set_cap(_metadata, CAP_SYS_ADMIN);
78 	tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
79 	tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
80 	tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
81 	tracefs_fixture_teardown();
82 	clear_cap(_metadata, CAP_SYS_ADMIN);
83 }
84 
85 /*
86  * Baseline: verifies that without Landlock, the operation succeeds and no
87  * check_rule or deny_access trace events fire.
88  */
89 TEST_F(trace_fs, unsandboxed)
90 {
91 	char *buf;
92 	int count, status, fd;
93 	pid_t pid;
94 
95 	ASSERT_EQ(0, tracefs_clear_buf());
96 
97 	pid = fork();
98 	ASSERT_LE(0, pid);
99 
100 	if (pid == 0) {
101 		/*
102 		 * No sandbox: verify that a normal FS access does not produce
103 		 * Landlock trace events.
104 		 */
105 		fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
106 		if (fd >= 0)
107 			close(fd);
108 		_exit(0);
109 	}
110 
111 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
112 	ASSERT_TRUE(WIFEXITED(status));
113 	EXPECT_EQ(0, WEXITSTATUS(status));
114 
115 	buf = tracefs_read_buf();
116 	ASSERT_NE(NULL, buf);
117 
118 	count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
119 	EXPECT_EQ(0, count);
120 	count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
121 	EXPECT_EQ(0, count);
122 
123 	free(buf);
124 }
125 
126 /*
127  * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace
128  * event with the expected path and field values: ruleset ID is non-zero,
129  * access_rights is non-zero, and path matches.
130  */
131 TEST_F(trace_fs, add_rule_fs)
132 {
133 	struct landlock_ruleset_attr ruleset_attr = {
134 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
135 				     LANDLOCK_ACCESS_FS_WRITE_FILE |
136 				     LANDLOCK_ACCESS_FS_READ_DIR,
137 	};
138 	struct landlock_path_beneath_attr path_beneath = {
139 		.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
140 	};
141 	char *buf, field_buf[64];
142 	int ruleset_fd, count;
143 
144 	ruleset_fd =
145 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
146 	ASSERT_LE(0, ruleset_fd);
147 
148 	path_beneath.parent_fd = open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
149 	ASSERT_LE(0, path_beneath.parent_fd);
150 
151 	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
152 				       &path_beneath, 0));
153 	ASSERT_EQ(0, close(path_beneath.parent_fd));
154 	ASSERT_EQ(0, close(ruleset_fd));
155 
156 	buf = tracefs_read_buf();
157 	ASSERT_NE(NULL, buf);
158 
159 	count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));
160 	EXPECT_EQ(1, count)
161 	{
162 		TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf);
163 	}
164 
165 	/* Ruleset ID should be non-zero. */
166 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
167 					   "ruleset", field_buf,
168 					   sizeof(field_buf)));
169 	EXPECT_STRNE("0", field_buf);
170 
171 	/* Access rights should be non-zero. */
172 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
173 					   "access_rights", field_buf,
174 					   sizeof(field_buf)));
175 	EXPECT_STRNE("", field_buf);
176 
177 	/* Path should be /usr. */
178 	ASSERT_EQ(0,
179 		  tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
180 					"path", field_buf, sizeof(field_buf)));
181 	EXPECT_STREQ("/usr", field_buf);
182 
183 	free(buf);
184 }
185 
186 /*
187  * Verifies that an allowed access emits check_rule events (rule matched during
188  * pathwalk) but does NOT emit deny_access events (no denial).
189  */
190 TEST_F(trace_fs, allowed_access)
191 {
192 	char *buf, field_buf[64];
193 	int count;
194 
195 	ASSERT_EQ(0, tracefs_clear_buf());
196 
197 	/* Rule allows READ_DIR for /usr, access /usr which is allowed. */
198 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
199 				LANDLOCK_ACCESS_FS_READ_DIR, "/usr");
200 
201 	buf = tracefs_read_buf();
202 	ASSERT_NE(NULL, buf);
203 
204 	count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
205 	EXPECT_LE(1, count);
206 
207 	/* Single-layer grants array, intersected with the request. */
208 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
209 					   "grants", field_buf,
210 					   sizeof(field_buf)));
211 	EXPECT_STREQ("{read_dir}", field_buf);
212 
213 	count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
214 	EXPECT_EQ(0, count);
215 
216 	free(buf);
217 }
218 
219 /*
220  * Verifies that accessing a path whose access type is not in the handled set
221  * does not emit landlock_check_rule events.  The ruleset handles READ_FILE, but
222  * the directory open checks READ_DIR which is unhandled; Landlock has no
223  * opinion and no rule evaluation occurs.
224  */
225 TEST_F(trace_fs, check_rule_unhandled)
226 {
227 	char *buf;
228 	int count;
229 
230 	ASSERT_EQ(0, tracefs_clear_buf());
231 
232 	/* Handles READ_FILE only; READ_DIR is unhandled. */
233 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_FILE,
234 				LANDLOCK_ACCESS_FS_READ_FILE, "/tmp");
235 
236 	buf = tracefs_read_buf();
237 	ASSERT_NE(NULL, buf);
238 
239 	/* No check_rule events because READ_DIR is not in the handled set. */
240 	count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
241 	EXPECT_EQ(0, count);
242 
243 	free(buf);
244 }
245 
246 /*
247  * Verifies that nested domains (child sandboxed under a parent domain) emit
248  * check_rule events from both layers and produce a deny_access event when the
249  * inner domain's rule does not cover the access.
250  */
251 TEST_F(trace_fs, check_rule_nested)
252 {
253 	char *buf, field_buf[64], *comma;
254 	size_t first_len, second_len;
255 	int count_rule, count_access, status;
256 	pid_t pid;
257 
258 	ASSERT_EQ(0, tracefs_clear_buf());
259 
260 	pid = fork();
261 	ASSERT_LE(0, pid);
262 
263 	if (pid == 0) {
264 		struct landlock_ruleset_attr ruleset_attr = {
265 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
266 		};
267 		struct landlock_path_beneath_attr path_beneath = {
268 			.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
269 		};
270 		int ruleset_fd, fd;
271 
272 		/* First layer: allow /usr. */
273 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
274 						     sizeof(ruleset_attr), 0);
275 		if (ruleset_fd < 0)
276 			_exit(1);
277 
278 		path_beneath.parent_fd =
279 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
280 		if (path_beneath.parent_fd < 0) {
281 			close(ruleset_fd);
282 			_exit(1);
283 		}
284 
285 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
286 				      &path_beneath, 0)) {
287 			close(path_beneath.parent_fd);
288 			close(ruleset_fd);
289 			_exit(1);
290 		}
291 		close(path_beneath.parent_fd);
292 
293 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
294 		if (landlock_restrict_self(ruleset_fd, 0)) {
295 			close(ruleset_fd);
296 			_exit(1);
297 		}
298 		close(ruleset_fd);
299 
300 		/* Second layer: also allow /usr. */
301 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
302 						     sizeof(ruleset_attr), 0);
303 		if (ruleset_fd < 0)
304 			_exit(1);
305 
306 		path_beneath.parent_fd =
307 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
308 		if (path_beneath.parent_fd < 0) {
309 			close(ruleset_fd);
310 			_exit(1);
311 		}
312 
313 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
314 				      &path_beneath, 0)) {
315 			close(path_beneath.parent_fd);
316 			close(ruleset_fd);
317 			_exit(1);
318 		}
319 		close(path_beneath.parent_fd);
320 
321 		if (landlock_restrict_self(ruleset_fd, 0)) {
322 			close(ruleset_fd);
323 			_exit(1);
324 		}
325 		close(ruleset_fd);
326 
327 		/* Access /usr which is allowed by both layers. */
328 		fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
329 		if (fd >= 0)
330 			close(fd);
331 
332 		/* Access /tmp which has no rule in either layer. */
333 		fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
334 		if (fd >= 0)
335 			close(fd);
336 
337 		_exit(0);
338 	}
339 
340 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
341 	ASSERT_TRUE(WIFEXITED(status));
342 	EXPECT_EQ(0, WEXITSTATUS(status));
343 
344 	buf = tracefs_read_buf();
345 	ASSERT_NE(NULL, buf);
346 
347 	count_rule =
348 		tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
349 	EXPECT_LE(1, count_rule);
350 
351 	/*
352 	 * Both layers have the same rule, so the grants array must have two
353 	 * identical symbolic entries, e.g. {read_dir,read_dir}.
354 	 */
355 	ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
356 					   "grants", field_buf,
357 					   sizeof(field_buf)));
358 	comma = strchr(field_buf, ',');
359 	EXPECT_NE(0, !!comma);
360 	if (comma) {
361 		/*
362 		 * Verify both entries are identical: compare the substring
363 		 * before the comma with the substring after it (stripping the
364 		 * braces).
365 		 */
366 		first_len = comma - field_buf - 1;
367 		second_len = strlen(comma + 1) - 1;
368 		EXPECT_EQ(first_len, second_len);
369 		EXPECT_EQ(0, strncmp(field_buf + 1, comma + 1, first_len));
370 	}
371 
372 	count_access =
373 		tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
374 	EXPECT_LE(1, count_access);
375 
376 	free(buf);
377 }
378 
379 /*
380  * Verifies that a denied FS access emits a landlock_deny_access_fs trace event
381  * with the blocked access and path.
382  */
383 TEST_F(trace_fs, deny_access_fs_denied)
384 {
385 	char *buf;
386 	int count;
387 
388 	ASSERT_EQ(0, tracefs_clear_buf());
389 
390 	/*
391 	 * Rule allows READ_DIR for /usr, but access /tmp which has no rule.
392 	 * READ_DIR access to /tmp is denied by absence and should emit a
393 	 * deny_access_fs event.
394 	 */
395 	sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR,
396 				LANDLOCK_ACCESS_FS_READ_DIR, "/tmp");
397 
398 	buf = tracefs_read_buf();
399 	ASSERT_NE(NULL, buf);
400 
401 	count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
402 	EXPECT_LE(1, count);
403 
404 	free(buf);
405 }
406 
407 /*
408  * A denied FS access covered by a quiet rule (LANDLOCK_ADD_RULE_QUIET with the
409  * access listed in quiet_access_fs) still emits a landlock_deny_access_fs
410  * event, but with logged=0, the same audit-logging verdict audit would apply to
411  * suppress the record.
412  */
413 TEST_F(trace_fs, deny_access_fs_quiet)
414 {
415 	char *buf, field[64];
416 	pid_t pid;
417 	int status;
418 
419 	ASSERT_EQ(0, tracefs_clear_buf());
420 
421 	pid = fork();
422 	ASSERT_LE(0, pid);
423 	if (pid == 0) {
424 		struct landlock_ruleset_attr ruleset_attr = {
425 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
426 			.quiet_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
427 		};
428 		struct landlock_path_beneath_attr path_beneath = {
429 			.allowed_access = 0,
430 		};
431 		int ruleset_fd, fd;
432 
433 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
434 						     sizeof(ruleset_attr), 0);
435 		if (ruleset_fd < 0)
436 			_exit(1);
437 
438 		/* Marks /tmp quiet without granting any access. */
439 		path_beneath.parent_fd =
440 			open("/tmp", O_PATH | O_DIRECTORY | O_CLOEXEC);
441 		if (path_beneath.parent_fd < 0) {
442 			close(ruleset_fd);
443 			_exit(1);
444 		}
445 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
446 				      &path_beneath, LANDLOCK_ADD_RULE_QUIET)) {
447 			close(path_beneath.parent_fd);
448 			close(ruleset_fd);
449 			_exit(1);
450 		}
451 		close(path_beneath.parent_fd);
452 
453 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
454 		if (landlock_restrict_self(ruleset_fd, 0)) {
455 			close(ruleset_fd);
456 			_exit(1);
457 		}
458 		close(ruleset_fd);
459 
460 		/* Denied READ_DIR on the quiet /tmp: suppressed, logged=0. */
461 		fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
462 		if (fd >= 0)
463 			close(fd);
464 		_exit(0);
465 	}
466 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
467 	ASSERT_TRUE(WIFEXITED(status));
468 	EXPECT_EQ(0, WEXITSTATUS(status));
469 
470 	buf = tracefs_read_buf();
471 	ASSERT_NE(NULL, buf);
472 
473 	/* The event fires with the suppressed verdict. */
474 	EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS_LOGGED(
475 							TRACE_TASK, "0")));
476 	/* The quiet rule must not leave the denial logged. */
477 	EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS_LOGGED(
478 							TRACE_TASK, "1")));
479 
480 	/*
481 	 * Quiet suppresses only the logged verdict: the rest of the denial
482 	 * event stays populated (non-zero domain, non-empty blockers).
483 	 */
484 	ASSERT_EQ(0, tracefs_extract_field(
485 			     buf, REGEX_DENY_ACCESS_FS_LOGGED(TRACE_TASK, "0"),
486 			     "domain", field, sizeof(field)));
487 	EXPECT_STRNE("0", field);
488 	ASSERT_EQ(0, tracefs_extract_field(
489 			     buf, REGEX_DENY_ACCESS_FS_LOGGED(TRACE_TASK, "0"),
490 			     "blockers", field, sizeof(field)));
491 	EXPECT_STRNE("", field);
492 
493 	free(buf);
494 }
495 
496 TEST_HARNESS_MAIN
497