xref: /linux/tools/testing/selftests/landlock/trace.h (revision 67f8bc848ee31831336bd478e57d2f993551902e)
12651712aSMickaël Salaün /* SPDX-License-Identifier: GPL-2.0 */
22651712aSMickaël Salaün /*
32651712aSMickaël Salaün  * Landlock trace test helpers
42651712aSMickaël Salaün  *
52651712aSMickaël Salaün  * Copyright © 2026 Cloudflare, Inc.
62651712aSMickaël Salaün  */
72651712aSMickaël Salaün 
82651712aSMickaël Salaün #define _GNU_SOURCE
92651712aSMickaël Salaün #include <errno.h>
102651712aSMickaël Salaün #include <fcntl.h>
112651712aSMickaël Salaün #include <regex.h>
122651712aSMickaël Salaün #include <stdbool.h>
132651712aSMickaël Salaün #include <stdio.h>
142651712aSMickaël Salaün #include <stdlib.h>
152651712aSMickaël Salaün #include <string.h>
162651712aSMickaël Salaün #include <sys/mount.h>
172651712aSMickaël Salaün #include <sys/stat.h>
182651712aSMickaël Salaün #include <unistd.h>
192651712aSMickaël Salaün 
202651712aSMickaël Salaün #include "kselftest_harness.h"
212651712aSMickaël Salaün 
222651712aSMickaël Salaün #define TRACEFS_ROOT "/sys/kernel/tracing"
232651712aSMickaël Salaün #define TRACEFS_LANDLOCK_DIR TRACEFS_ROOT "/events/landlock"
242651712aSMickaël Salaün #define TRACEFS_CREATE_RULESET_ENABLE \
252651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_create_ruleset/enable"
262651712aSMickaël Salaün #define TRACEFS_CREATE_DOMAIN_ENABLE \
272651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable"
28*ac6d193dSMickaël Salaün #define TRACEFS_ENFORCE_DOMAIN_ENABLE \
29*ac6d193dSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable"
302651712aSMickaël Salaün #define TRACEFS_ADD_RULE_FS_ENABLE \
312651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable"
322651712aSMickaël Salaün #define TRACEFS_ADD_RULE_NET_ENABLE \
332651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net/enable"
342651712aSMickaël Salaün #define TRACEFS_CHECK_RULE_FS_ENABLE \
352651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable"
362651712aSMickaël Salaün #define TRACEFS_CHECK_RULE_NET_ENABLE \
372651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable"
382651712aSMickaël Salaün #define TRACEFS_DENY_ACCESS_FS_ENABLE \
392651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable"
402651712aSMickaël Salaün #define TRACEFS_DENY_ACCESS_NET_ENABLE \
412651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_deny_access_net/enable"
422651712aSMickaël Salaün #define TRACEFS_DENY_PTRACE_ENABLE \
432651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_deny_ptrace/enable"
442651712aSMickaël Salaün #define TRACEFS_DENY_SCOPE_SIGNAL_ENABLE \
452651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_deny_scope_signal/enable"
462651712aSMickaël Salaün #define TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE \
472651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR                           \
482651712aSMickaël Salaün 	"/landlock_deny_scope_abstract_unix_socket/enable"
492651712aSMickaël Salaün #define TRACEFS_FREE_DOMAIN_ENABLE \
502651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_free_domain/enable"
512651712aSMickaël Salaün #define TRACEFS_FREE_RULESET_ENABLE \
522651712aSMickaël Salaün 	TRACEFS_LANDLOCK_DIR "/landlock_free_ruleset/enable"
532651712aSMickaël Salaün #define TRACEFS_TRACE TRACEFS_ROOT "/trace"
542651712aSMickaël Salaün #define TRACEFS_SET_EVENT_PID TRACEFS_ROOT "/set_event_pid"
552651712aSMickaël Salaün #define TRACEFS_OPTIONS_EVENT_FORK TRACEFS_ROOT "/options/event-fork"
562651712aSMickaël Salaün 
572651712aSMickaël Salaün #define TRACE_BUFFER_SIZE (64 * 1024)
582651712aSMickaël Salaün 
592651712aSMickaël Salaün /*
602651712aSMickaël Salaün  * Trace line prefix: matches the ftrace "trace" file format.  Format: "
612651712aSMickaël Salaün  * <task>-<pid> [<cpu>] <flags> <timestamp>: "
622651712aSMickaël Salaün  *
632651712aSMickaël Salaün  * The task parameter must be a string literal truncated to 15 chars
642651712aSMickaël Salaün  * (TASK_COMM_LEN - 1), matching what the kernel stores in task->comm.  The
652651712aSMickaël Salaün  * pattern accepts either the expected task name or "<...>" because the ftrace
662651712aSMickaël Salaün  * comm cache may evict short-lived processes (e.g., forked children that exit
672651712aSMickaël Salaün  * before the trace buffer is read).
682651712aSMickaël Salaün  *
692651712aSMickaël Salaün  * No unescaped '.' in any REGEX macro; literal dots use '\\.'.
702651712aSMickaël Salaün  */
712651712aSMickaël Salaün #define TRACE_PREFIX(task)  \
722651712aSMickaël Salaün 	"^ *\\(<\\.\\.\\.>" \
732651712aSMickaël Salaün 	"\\|" task "\\)"    \
742651712aSMickaël Salaün 	"-[0-9]\\+ *\\[[0-9]\\+\\] [^ ]\\+ \\+[0-9]\\+\\.[0-9]\\+: "
752651712aSMickaël Salaün 
762651712aSMickaël Salaün /*
772651712aSMickaël Salaün  * Task name for events emitted by kworker threads (e.g., free_domain fires from
782651712aSMickaël Salaün  * a work queue, not from the test process).
792651712aSMickaël Salaün  */
802651712aSMickaël Salaün #define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+"
812651712aSMickaël Salaün 
822651712aSMickaël Salaün #define REGEX_ADD_RULE_FS(task)           \
832651712aSMickaël Salaün 	TRACE_PREFIX(task)                \
842651712aSMickaël Salaün 	"landlock_add_rule_fs: "          \
852651712aSMickaël Salaün 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
862651712aSMickaël Salaün 	"access_rights=[a-z_|]* "         \
872651712aSMickaël Salaün 	"dev=[0-9]\\+:[0-9]\\+ "          \
882651712aSMickaël Salaün 	"ino=[0-9]\\+ "                   \
892651712aSMickaël Salaün 	"path=[^ ]\\+$"
902651712aSMickaël Salaün 
912651712aSMickaël Salaün #define REGEX_ADD_RULE_NET(task)          \
922651712aSMickaël Salaün 	TRACE_PREFIX(task)                \
932651712aSMickaël Salaün 	"landlock_add_rule_net: "         \
942651712aSMickaël Salaün 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
952651712aSMickaël Salaün 	"access_rights=[a-z_|]* "         \
962651712aSMickaël Salaün 	"port=[0-9]\\+$"
972651712aSMickaël Salaün 
982651712aSMickaël Salaün #define REGEX_CREATE_RULESET(task)        \
992651712aSMickaël Salaün 	TRACE_PREFIX(task)                \
1002651712aSMickaël Salaün 	"landlock_create_ruleset: "       \
1012651712aSMickaël Salaün 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
1022651712aSMickaël Salaün 	"handled_fs=[a-z_|]* "            \
1032651712aSMickaël Salaün 	"handled_net=[a-z_|]* "           \
1042651712aSMickaël Salaün 	"scoped=[a-z_|]*$"
1052651712aSMickaël Salaün 
1062651712aSMickaël Salaün #define REGEX_CREATE_DOMAIN(task)  \
1072651712aSMickaël Salaün 	TRACE_PREFIX(task)         \
1082651712aSMickaël Salaün 	"landlock_create_domain: " \
1092651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "      \
1102651712aSMickaël Salaün 	"parent=[0-9a-f]\\+ "      \
1112651712aSMickaël Salaün 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+$"
1122651712aSMickaël Salaün 
1132651712aSMickaël Salaün #define REGEX_CHECK_RULE_FS(task)  \
1142651712aSMickaël Salaün 	TRACE_PREFIX(task)         \
1152651712aSMickaël Salaün 	"landlock_check_rule_fs: " \
1162651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "      \
1172651712aSMickaël Salaün 	"access_request=[a-z_|]* " \
1182651712aSMickaël Salaün 	"dev=[0-9]\\+:[0-9]\\+ "   \
1192651712aSMickaël Salaün 	"ino=[0-9]\\+ "            \
1202651712aSMickaël Salaün 	"grants={[a-z_|,]*}$"
1212651712aSMickaël Salaün 
1222651712aSMickaël Salaün #define REGEX_CHECK_RULE_NET(task)  \
1232651712aSMickaël Salaün 	TRACE_PREFIX(task)          \
1242651712aSMickaël Salaün 	"landlock_check_rule_net: " \
1252651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "       \
1262651712aSMickaël Salaün 	"access_request=[a-z_|]* "  \
1272651712aSMickaël Salaün 	"port=[0-9]\\+ "            \
1282651712aSMickaël Salaün 	"grants={[a-z_|,]*}$"
1292651712aSMickaël Salaün 
1302651712aSMickaël Salaün #define REGEX_DENY_ACCESS_FS(task)  \
1312651712aSMickaël Salaün 	TRACE_PREFIX(task)          \
1322651712aSMickaël Salaün 	"landlock_deny_access_fs: " \
1332651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "       \
1342651712aSMickaël Salaün 	"same_exec=[01] "           \
1352651712aSMickaël Salaün 	"logged=[01] "              \
1362651712aSMickaël Salaün 	"blockers=[a-z_|]* "        \
1372651712aSMickaël Salaün 	"dev=[0-9]\\+:[0-9]\\+ "    \
1382651712aSMickaël Salaün 	"ino=[0-9]\\+ "             \
1392651712aSMickaël Salaün 	"path=[^ ]*$"
1402651712aSMickaël Salaün 
1412651712aSMickaël Salaün #define REGEX_DENY_ACCESS_NET(task)  \
1422651712aSMickaël Salaün 	TRACE_PREFIX(task)           \
1432651712aSMickaël Salaün 	"landlock_deny_access_net: " \
1442651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "        \
1452651712aSMickaël Salaün 	"same_exec=[01] "            \
1462651712aSMickaël Salaün 	"logged=[01] "               \
1472651712aSMickaël Salaün 	"blockers=[a-z_|]* "         \
1482651712aSMickaël Salaün 	"sport=[0-9]\\+ "            \
1492651712aSMickaël Salaün 	"dport=[0-9]\\+$"
1502651712aSMickaël Salaün 
1512651712aSMickaël Salaün #define REGEX_DENY_PTRACE(task)      \
1522651712aSMickaël Salaün 	TRACE_PREFIX(task)           \
1532651712aSMickaël Salaün 	"landlock_deny_ptrace: "     \
1542651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "        \
1552651712aSMickaël Salaün 	"same_exec=[01] "            \
1562651712aSMickaël Salaün 	"logged=[01] "               \
157aef2dd32SMickaël Salaün 	"tracee_domain=[0-9a-f]\\+ " \
1582651712aSMickaël Salaün 	"tracee_pid=[0-9]\\+ "       \
1592651712aSMickaël Salaün 	"tracee_comm=[^ ]*$"
1602651712aSMickaël Salaün 
1612651712aSMickaël Salaün #define REGEX_DENY_SCOPE_SIGNAL(task)  \
1622651712aSMickaël Salaün 	TRACE_PREFIX(task)             \
1632651712aSMickaël Salaün 	"landlock_deny_scope_signal: " \
1642651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "          \
1652651712aSMickaël Salaün 	"same_exec=[01] "              \
1662651712aSMickaël Salaün 	"logged=[01] "                 \
167aef2dd32SMickaël Salaün 	"target_domain=[0-9a-f]\\+ "   \
1682651712aSMickaël Salaün 	"target_pid=[0-9]\\+ "         \
1692651712aSMickaël Salaün 	"target_comm=[^ ]*$"
1702651712aSMickaël Salaün 
1712651712aSMickaël Salaün #define REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(task)  \
1722651712aSMickaël Salaün 	TRACE_PREFIX(task)                           \
1732651712aSMickaël Salaün 	"landlock_deny_scope_abstract_unix_socket: " \
1742651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "                        \
1752651712aSMickaël Salaün 	"same_exec=[01] "                            \
1762651712aSMickaël Salaün 	"logged=[01] "                               \
177aef2dd32SMickaël Salaün 	"peer_domain=[0-9a-f]\\+ "                   \
1782651712aSMickaël Salaün 	"peer_pid=[0-9]\\+ "                         \
1792651712aSMickaël Salaün 	"sun_path=[^ ]*$"
1802651712aSMickaël Salaün 
1812651712aSMickaël Salaün #define REGEX_FREE_DOMAIN(task)  \
1822651712aSMickaël Salaün 	TRACE_PREFIX(task)       \
1832651712aSMickaël Salaün 	"landlock_free_domain: " \
1842651712aSMickaël Salaün 	"domain=[0-9a-f]\\+ "    \
1852651712aSMickaël Salaün 	"denials=[0-9]\\+$"
1862651712aSMickaël Salaün 
1872651712aSMickaël Salaün #define REGEX_FREE_RULESET(task)  \
1882651712aSMickaël Salaün 	TRACE_PREFIX(task)        \
1892651712aSMickaël Salaün 	"landlock_free_ruleset: " \
1902651712aSMickaël Salaün 	"ruleset=[0-9a-f]\\+\\.[0-9]\\+$"
1912651712aSMickaël Salaün 
1922651712aSMickaël Salaün static int __maybe_unused tracefs_write(const char *path, const char *value)
1932651712aSMickaël Salaün {
1942651712aSMickaël Salaün 	int fd;
1952651712aSMickaël Salaün 	ssize_t ret;
1962651712aSMickaël Salaün 	size_t len = strlen(value);
1972651712aSMickaël Salaün 
1982651712aSMickaël Salaün 	fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
1992651712aSMickaël Salaün 	if (fd < 0)
2002651712aSMickaël Salaün 		return -errno;
2012651712aSMickaël Salaün 
2022651712aSMickaël Salaün 	ret = write(fd, value, len);
2032651712aSMickaël Salaün 	close(fd);
2042651712aSMickaël Salaün 	if (ret < 0)
2052651712aSMickaël Salaün 		return -errno;
2062651712aSMickaël Salaün 	if ((size_t)ret != len)
2072651712aSMickaël Salaün 		return -EIO;
2082651712aSMickaël Salaün 
2092651712aSMickaël Salaün 	return 0;
2102651712aSMickaël Salaün }
2112651712aSMickaël Salaün 
2122651712aSMickaël Salaün static int __maybe_unused tracefs_write_int(const char *path, int value)
2132651712aSMickaël Salaün {
2142651712aSMickaël Salaün 	char buf[32];
2152651712aSMickaël Salaün 
2162651712aSMickaël Salaün 	snprintf(buf, sizeof(buf), "%d", value);
2172651712aSMickaël Salaün 	return tracefs_write(path, buf);
2182651712aSMickaël Salaün }
2192651712aSMickaël Salaün 
2202651712aSMickaël Salaün static int __maybe_unused tracefs_setup(void)
2212651712aSMickaël Salaün {
2222651712aSMickaël Salaün 	struct stat st;
2232651712aSMickaël Salaün 
2242651712aSMickaël Salaün 	/* Mount tracefs if not already mounted. */
2252651712aSMickaël Salaün 	if (stat(TRACEFS_ROOT, &st) != 0) {
2262651712aSMickaël Salaün 		int ret = mount("tracefs", TRACEFS_ROOT, "tracefs", 0, NULL);
2272651712aSMickaël Salaün 
2282651712aSMickaël Salaün 		if (ret)
2292651712aSMickaël Salaün 			return -errno;
2302651712aSMickaël Salaün 	}
2312651712aSMickaël Salaün 
2322651712aSMickaël Salaün 	/* Verify landlock events are available. */
2332651712aSMickaël Salaün 	if (stat(TRACEFS_LANDLOCK_DIR, &st) != 0)
2342651712aSMickaël Salaün 		return -ENOENT;
2352651712aSMickaël Salaün 
2362651712aSMickaël Salaün 	return 0;
2372651712aSMickaël Salaün }
2382651712aSMickaël Salaün 
2392651712aSMickaël Salaün /*
2402651712aSMickaël Salaün  * Set up PID-based event filtering so only events from the current process and
2412651712aSMickaël Salaün  * its children are recorded.  This is analogous to audit's AUDIT_EXE filter: it
2422651712aSMickaël Salaün  * prevents events from unrelated processes from polluting the trace buffer.
2432651712aSMickaël Salaün  */
2442651712aSMickaël Salaün static int __maybe_unused tracefs_set_pid_filter(pid_t pid)
2452651712aSMickaël Salaün {
2462651712aSMickaël Salaün 	int ret;
2472651712aSMickaël Salaün 
2482651712aSMickaël Salaün 	/* Enable event-fork so children inherit the PID filter. */
2492651712aSMickaël Salaün 	ret = tracefs_write(TRACEFS_OPTIONS_EVENT_FORK, "1");
2502651712aSMickaël Salaün 	if (ret)
2512651712aSMickaël Salaün 		return ret;
2522651712aSMickaël Salaün 
2532651712aSMickaël Salaün 	return tracefs_write_int(TRACEFS_SET_EVENT_PID, pid);
2542651712aSMickaël Salaün }
2552651712aSMickaël Salaün 
2562651712aSMickaël Salaün /* Clear the PID filter to stop filtering by PID. */
2572651712aSMickaël Salaün static int __maybe_unused tracefs_clear_pid_filter(void)
2582651712aSMickaël Salaün {
2592651712aSMickaël Salaün 	return tracefs_write(TRACEFS_SET_EVENT_PID, "");
2602651712aSMickaël Salaün }
2612651712aSMickaël Salaün 
2622651712aSMickaël Salaün static int __maybe_unused tracefs_enable_event(const char *enable_path,
2632651712aSMickaël Salaün 					       bool enable)
2642651712aSMickaël Salaün {
2652651712aSMickaël Salaün 	return tracefs_write(enable_path, enable ? "1" : "0");
2662651712aSMickaël Salaün }
2672651712aSMickaël Salaün 
2682651712aSMickaël Salaün static int __maybe_unused tracefs_clear(void)
2692651712aSMickaël Salaün {
2702651712aSMickaël Salaün 	return tracefs_write(TRACEFS_TRACE, "");
2712651712aSMickaël Salaün }
2722651712aSMickaël Salaün 
2732651712aSMickaël Salaün /*
2742651712aSMickaël Salaün  * Reads the trace buffer content into a newly allocated buffer.  The caller is
2752651712aSMickaël Salaün  * responsible for freeing the returned buffer.  Returns NULL on error.
2762651712aSMickaël Salaün  */
2772651712aSMickaël Salaün static char __maybe_unused *tracefs_read_trace(void)
2782651712aSMickaël Salaün {
2792651712aSMickaël Salaün 	char *buf;
2802651712aSMickaël Salaün 	int fd;
2812651712aSMickaël Salaün 	ssize_t total = 0, ret;
2822651712aSMickaël Salaün 
2832651712aSMickaël Salaün 	buf = malloc(TRACE_BUFFER_SIZE);
2842651712aSMickaël Salaün 	if (!buf)
2852651712aSMickaël Salaün 		return NULL;
2862651712aSMickaël Salaün 
2872651712aSMickaël Salaün 	fd = open(TRACEFS_TRACE, O_RDONLY | O_CLOEXEC);
2882651712aSMickaël Salaün 	if (fd < 0) {
2892651712aSMickaël Salaün 		free(buf);
2902651712aSMickaël Salaün 		return NULL;
2912651712aSMickaël Salaün 	}
2922651712aSMickaël Salaün 
2932651712aSMickaël Salaün 	while (total < TRACE_BUFFER_SIZE - 1) {
2942651712aSMickaël Salaün 		ret = read(fd, buf + total, TRACE_BUFFER_SIZE - 1 - total);
2952651712aSMickaël Salaün 		if (ret <= 0)
2962651712aSMickaël Salaün 			break;
2972651712aSMickaël Salaün 		total += ret;
2982651712aSMickaël Salaün 	}
2992651712aSMickaël Salaün 	close(fd);
3002651712aSMickaël Salaün 	buf[total] = '\0';
3012651712aSMickaël Salaün 	return buf;
3022651712aSMickaël Salaün }
3032651712aSMickaël Salaün 
3042651712aSMickaël Salaün /* Counts the number of lines in @buf matching the basic regex @pattern. */
3052651712aSMickaël Salaün static int __maybe_unused tracefs_count_matches(const char *buf,
3062651712aSMickaël Salaün 						const char *pattern)
3072651712aSMickaël Salaün {
3082651712aSMickaël Salaün 	regex_t regex;
3092651712aSMickaël Salaün 	int count = 0;
3102651712aSMickaël Salaün 	const char *line, *end;
3112651712aSMickaël Salaün 
3122651712aSMickaël Salaün 	if (regcomp(&regex, pattern, 0) != 0)
3132651712aSMickaël Salaün 		return -EINVAL;
3142651712aSMickaël Salaün 
3152651712aSMickaël Salaün 	line = buf;
3162651712aSMickaël Salaün 	while (*line) {
3172651712aSMickaël Salaün 		end = strchr(line, '\n');
3182651712aSMickaël Salaün 		if (!end)
3192651712aSMickaël Salaün 			end = line + strlen(line);
3202651712aSMickaël Salaün 
3212651712aSMickaël Salaün 		/* Create a temporary NUL-terminated line. */
3222651712aSMickaël Salaün 		size_t len = end - line;
3232651712aSMickaël Salaün 		char *tmp = malloc(len + 1);
3242651712aSMickaël Salaün 
3252651712aSMickaël Salaün 		if (tmp) {
3262651712aSMickaël Salaün 			memcpy(tmp, line, len);
3272651712aSMickaël Salaün 			tmp[len] = '\0';
3282651712aSMickaël Salaün 			if (regexec(&regex, tmp, 0, NULL, 0) == 0)
3292651712aSMickaël Salaün 				count++;
3302651712aSMickaël Salaün 			free(tmp);
3312651712aSMickaël Salaün 		}
3322651712aSMickaël Salaün 
3332651712aSMickaël Salaün 		if (*end == '\n')
3342651712aSMickaël Salaün 			line = end + 1;
3352651712aSMickaël Salaün 		else
3362651712aSMickaël Salaün 			break;
3372651712aSMickaël Salaün 	}
3382651712aSMickaël Salaün 
3392651712aSMickaël Salaün 	regfree(&regex);
3402651712aSMickaël Salaün 	return count;
3412651712aSMickaël Salaün }
3422651712aSMickaël Salaün 
3432651712aSMickaël Salaün /*
3442651712aSMickaël Salaün  * Extracts the value of a named field from a trace line in @buf.  Searches for
3452651712aSMickaël Salaün  * the first line matching @line_pattern, then extracts the value after
3462651712aSMickaël Salaün  * "@field_name=" into @out.  Stops at space or newline.
3472651712aSMickaël Salaün  *
3482651712aSMickaël Salaün  * Returns 0 on success, -ENOENT if no match.
3492651712aSMickaël Salaün  */
3502651712aSMickaël Salaün static int __maybe_unused tracefs_extract_field(const char *buf,
3512651712aSMickaël Salaün 						const char *line_pattern,
3522651712aSMickaël Salaün 						const char *field_name,
3532651712aSMickaël Salaün 						char *out, size_t out_size)
3542651712aSMickaël Salaün {
3552651712aSMickaël Salaün 	regex_t regex;
3562651712aSMickaël Salaün 	const char *line, *end;
3572651712aSMickaël Salaün 
3582651712aSMickaël Salaün 	if (regcomp(&regex, line_pattern, 0) != 0)
3592651712aSMickaël Salaün 		return -EINVAL;
3602651712aSMickaël Salaün 
3612651712aSMickaël Salaün 	line = buf;
3622651712aSMickaël Salaün 	while (*line) {
3632651712aSMickaël Salaün 		end = strchr(line, '\n');
3642651712aSMickaël Salaün 		if (!end)
3652651712aSMickaël Salaün 			end = line + strlen(line);
3662651712aSMickaël Salaün 
3672651712aSMickaël Salaün 		size_t len = end - line;
3682651712aSMickaël Salaün 		char *tmp = malloc(len + 1);
3692651712aSMickaël Salaün 
3702651712aSMickaël Salaün 		if (tmp) {
3712651712aSMickaël Salaün 			const char *field, *val_start;
3722651712aSMickaël Salaün 			size_t field_len, val_len;
3732651712aSMickaël Salaün 
3742651712aSMickaël Salaün 			memcpy(tmp, line, len);
3752651712aSMickaël Salaün 			tmp[len] = '\0';
3762651712aSMickaël Salaün 
3772651712aSMickaël Salaün 			if (regexec(&regex, tmp, 0, NULL, 0) != 0) {
3782651712aSMickaël Salaün 				free(tmp);
3792651712aSMickaël Salaün 				goto next;
3802651712aSMickaël Salaün 			}
3812651712aSMickaël Salaün 
3822651712aSMickaël Salaün 			/*
3832651712aSMickaël Salaün 			 * Find "field_name=" in the line, ensuring a word
3842651712aSMickaël Salaün 			 * boundary before the field name to avoid substring
3852651712aSMickaël Salaün 			 * matches (e.g., "port" in "sport").
3862651712aSMickaël Salaün 			 */
3872651712aSMickaël Salaün 			field_len = strlen(field_name);
3882651712aSMickaël Salaün 			field = tmp;
3892651712aSMickaël Salaün 			while ((field = strstr(field, field_name))) {
3902651712aSMickaël Salaün 				if (field[field_len] == '=' &&
3912651712aSMickaël Salaün 				    (field == tmp || field[-1] == ' '))
3922651712aSMickaël Salaün 					break;
3932651712aSMickaël Salaün 				field++;
3942651712aSMickaël Salaün 			}
3952651712aSMickaël Salaün 			if (!field) {
3962651712aSMickaël Salaün 				free(tmp);
3972651712aSMickaël Salaün 				regfree(&regex);
3982651712aSMickaël Salaün 				return -ENOENT;
3992651712aSMickaël Salaün 			}
4002651712aSMickaël Salaün 
4012651712aSMickaël Salaün 			val_start = field + field_len + 1;
4022651712aSMickaël Salaün 			val_len = 0;
4032651712aSMickaël Salaün 			while (val_start[val_len] &&
4042651712aSMickaël Salaün 			       val_start[val_len] != ' ' &&
4052651712aSMickaël Salaün 			       val_start[val_len] != '\n')
4062651712aSMickaël Salaün 				val_len++;
4072651712aSMickaël Salaün 
4082651712aSMickaël Salaün 			if (val_len >= out_size)
4092651712aSMickaël Salaün 				val_len = out_size - 1;
4102651712aSMickaël Salaün 			memcpy(out, val_start, val_len);
4112651712aSMickaël Salaün 			out[val_len] = '\0';
4122651712aSMickaël Salaün 
4132651712aSMickaël Salaün 			free(tmp);
4142651712aSMickaël Salaün 			regfree(&regex);
4152651712aSMickaël Salaün 			return 0;
4162651712aSMickaël Salaün 		}
4172651712aSMickaël Salaün next:
4182651712aSMickaël Salaün 		if (*end == '\n')
4192651712aSMickaël Salaün 			line = end + 1;
4202651712aSMickaël Salaün 		else
4212651712aSMickaël Salaün 			break;
4222651712aSMickaël Salaün 	}
4232651712aSMickaël Salaün 
4242651712aSMickaël Salaün 	regfree(&regex);
4252651712aSMickaël Salaün 	return -ENOENT;
4262651712aSMickaël Salaün }
4272651712aSMickaël Salaün 
4282651712aSMickaël Salaün /*
4292651712aSMickaël Salaün  * Common fixture setup for trace tests.  Mounts tracefs if needed and sets a
4302651712aSMickaël Salaün  * PID filter.  The caller must create a mount namespace first
4312651712aSMickaël Salaün  * (unshare(CLONE_NEWNS) + mount(MS_REC | MS_PRIVATE)) to isolate the tracefs
4322651712aSMickaël Salaün  * mount; the trace buffer, per-event enable flags, and PID filter are global
4332651712aSMickaël Salaün  * kernel state, scoped to the test by the PID filter.
4342651712aSMickaël Salaün  *
4352651712aSMickaël Salaün  * Returns 0 on success, -errno on failure (caller should SKIP).
4362651712aSMickaël Salaün  */
4372651712aSMickaël Salaün static int __maybe_unused tracefs_fixture_setup(void)
4382651712aSMickaël Salaün {
4392651712aSMickaël Salaün 	int ret;
4402651712aSMickaël Salaün 
4412651712aSMickaël Salaün 	ret = tracefs_setup();
4422651712aSMickaël Salaün 	if (ret)
4432651712aSMickaël Salaün 		return ret;
4442651712aSMickaël Salaün 
4452651712aSMickaël Salaün 	return tracefs_set_pid_filter(getpid());
4462651712aSMickaël Salaün }
4472651712aSMickaël Salaün 
4482651712aSMickaël Salaün static void __maybe_unused tracefs_fixture_teardown(void)
4492651712aSMickaël Salaün {
4502651712aSMickaël Salaün 	tracefs_clear_pid_filter();
4512651712aSMickaël Salaün }
4522651712aSMickaël Salaün 
4532651712aSMickaël Salaün /*
4542651712aSMickaël Salaün  * Temporarily raises CAP_SYS_ADMIN effective capability, calls @func, then
4552651712aSMickaël Salaün  * drops the capability.  Returns the value from @func, or -EPERM if the
4562651712aSMickaël Salaün  * capability manipulation fails.
4572651712aSMickaël Salaün  */
4582651712aSMickaël Salaün static int __maybe_unused tracefs_priv_call(int (*func)(void))
4592651712aSMickaël Salaün {
4602651712aSMickaël Salaün 	const cap_value_t admin = CAP_SYS_ADMIN;
4612651712aSMickaël Salaün 	cap_t cap_p;
4622651712aSMickaël Salaün 	int ret;
4632651712aSMickaël Salaün 
4642651712aSMickaël Salaün 	cap_p = cap_get_proc();
4652651712aSMickaël Salaün 	if (!cap_p)
4662651712aSMickaël Salaün 		return -EPERM;
4672651712aSMickaël Salaün 
4682651712aSMickaël Salaün 	if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) ||
4692651712aSMickaël Salaün 	    cap_set_proc(cap_p)) {
4702651712aSMickaël Salaün 		cap_free(cap_p);
4712651712aSMickaël Salaün 		return -EPERM;
4722651712aSMickaël Salaün 	}
4732651712aSMickaël Salaün 
4742651712aSMickaël Salaün 	ret = func();
4752651712aSMickaël Salaün 
4762651712aSMickaël Salaün 	cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR);
4772651712aSMickaël Salaün 	cap_set_proc(cap_p);
4782651712aSMickaël Salaün 	cap_free(cap_p);
4792651712aSMickaël Salaün 	return ret;
4802651712aSMickaël Salaün }
4812651712aSMickaël Salaün 
4822651712aSMickaël Salaün /* Read the trace buffer with elevated privileges.  Returns NULL on failure. */
4832651712aSMickaël Salaün static char __maybe_unused *tracefs_read_buf(void)
4842651712aSMickaël Salaün {
4852651712aSMickaël Salaün 	/* Cannot use tracefs_priv_call() because the return type is char *. */
4862651712aSMickaël Salaün 	cap_t cap_p;
4872651712aSMickaël Salaün 	char *buf;
4882651712aSMickaël Salaün 	const cap_value_t admin = CAP_SYS_ADMIN;
4892651712aSMickaël Salaün 
4902651712aSMickaël Salaün 	cap_p = cap_get_proc();
4912651712aSMickaël Salaün 	if (!cap_p)
4922651712aSMickaël Salaün 		return NULL;
4932651712aSMickaël Salaün 
4942651712aSMickaël Salaün 	if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) ||
4952651712aSMickaël Salaün 	    cap_set_proc(cap_p)) {
4962651712aSMickaël Salaün 		cap_free(cap_p);
4972651712aSMickaël Salaün 		return NULL;
4982651712aSMickaël Salaün 	}
4992651712aSMickaël Salaün 
5002651712aSMickaël Salaün 	buf = tracefs_read_trace();
5012651712aSMickaël Salaün 
5022651712aSMickaël Salaün 	cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR);
5032651712aSMickaël Salaün 	cap_set_proc(cap_p);
5042651712aSMickaël Salaün 	cap_free(cap_p);
5052651712aSMickaël Salaün 	return buf;
5062651712aSMickaël Salaün }
5072651712aSMickaël Salaün 
5082651712aSMickaël Salaün /* Clear the trace buffer with elevated privileges.  Returns 0 on success. */
5092651712aSMickaël Salaün static int __maybe_unused tracefs_clear_buf(void)
5102651712aSMickaël Salaün {
5112651712aSMickaël Salaün 	return tracefs_priv_call(tracefs_clear);
5122651712aSMickaël Salaün }
5132651712aSMickaël Salaün 
5142651712aSMickaël Salaün /*
5152651712aSMickaël Salaün  * Forks a child that creates a Landlock sandbox and performs an FS access.  The
5162651712aSMickaël Salaün  * parent waits for the child, then reads the trace buffer.
5172651712aSMickaël Salaün  *
5182651712aSMickaël Salaün  * Requires common.h and wrappers.h to be included before trace.h.
5192651712aSMickaël Salaün  */
5202651712aSMickaël Salaün static void __maybe_unused sandbox_child_fs_access(
5212651712aSMickaël Salaün 	struct __test_metadata *const _metadata, const char *rule_path,
5222651712aSMickaël Salaün 	__u64 handled_access, __u64 allowed_access, const char *access_path)
5232651712aSMickaël Salaün {
5242651712aSMickaël Salaün 	pid_t pid;
5252651712aSMickaël Salaün 	int status;
5262651712aSMickaël Salaün 
5272651712aSMickaël Salaün 	pid = fork();
5282651712aSMickaël Salaün 	ASSERT_LE(0, pid);
5292651712aSMickaël Salaün 
5302651712aSMickaël Salaün 	if (pid == 0) {
5312651712aSMickaël Salaün 		struct landlock_ruleset_attr ruleset_attr = {
5322651712aSMickaël Salaün 			.handled_access_fs = handled_access,
5332651712aSMickaël Salaün 		};
5342651712aSMickaël Salaün 		struct landlock_path_beneath_attr path_beneath = {
5352651712aSMickaël Salaün 			.allowed_access = allowed_access,
5362651712aSMickaël Salaün 		};
5372651712aSMickaël Salaün 		int ruleset_fd, fd;
5382651712aSMickaël Salaün 
5392651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
5402651712aSMickaël Salaün 						     sizeof(ruleset_attr), 0);
5412651712aSMickaël Salaün 		if (ruleset_fd < 0)
5422651712aSMickaël Salaün 			_exit(1);
5432651712aSMickaël Salaün 
5442651712aSMickaël Salaün 		path_beneath.parent_fd =
5452651712aSMickaël Salaün 			open(rule_path, O_PATH | O_DIRECTORY | O_CLOEXEC);
5462651712aSMickaël Salaün 		if (path_beneath.parent_fd < 0) {
5472651712aSMickaël Salaün 			close(ruleset_fd);
5482651712aSMickaël Salaün 			_exit(1);
5492651712aSMickaël Salaün 		}
5502651712aSMickaël Salaün 
5512651712aSMickaël Salaün 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
5522651712aSMickaël Salaün 				      &path_beneath, 0)) {
5532651712aSMickaël Salaün 			close(path_beneath.parent_fd);
5542651712aSMickaël Salaün 			close(ruleset_fd);
5552651712aSMickaël Salaün 			_exit(1);
5562651712aSMickaël Salaün 		}
5572651712aSMickaël Salaün 		close(path_beneath.parent_fd);
5582651712aSMickaël Salaün 
5592651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
5602651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, 0)) {
5612651712aSMickaël Salaün 			close(ruleset_fd);
5622651712aSMickaël Salaün 			_exit(1);
5632651712aSMickaël Salaün 		}
5642651712aSMickaël Salaün 		close(ruleset_fd);
5652651712aSMickaël Salaün 
5662651712aSMickaël Salaün 		fd = open(access_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
5672651712aSMickaël Salaün 		if (fd >= 0)
5682651712aSMickaël Salaün 			close(fd);
5692651712aSMickaël Salaün 
5702651712aSMickaël Salaün 		_exit(0);
5712651712aSMickaël Salaün 	}
5722651712aSMickaël Salaün 
5732651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
5742651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
5752651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
5762651712aSMickaël Salaün }
5772651712aSMickaël Salaün 
5782651712aSMickaël Salaün /*
5792651712aSMickaël Salaün  * Forks a child that creates a Landlock sandbox allowing execute+read_dir for
5802651712aSMickaël Salaün  * /usr and execute-only for ".", then execs ./true.  The true binary opens "."
5812651712aSMickaël Salaün  * on startup, triggering a read_dir denial with same_exec=0.  The parent waits
5822651712aSMickaël Salaün  * for the child to exit.
5832651712aSMickaël Salaün  */
5842651712aSMickaël Salaün static void __maybe_unused sandbox_child_exec_true(
5852651712aSMickaël Salaün 	struct __test_metadata *const _metadata, __u32 restrict_flags)
5862651712aSMickaël Salaün {
5872651712aSMickaël Salaün 	pid_t pid;
5882651712aSMickaël Salaün 	int status;
5892651712aSMickaël Salaün 
5902651712aSMickaël Salaün 	pid = fork();
5912651712aSMickaël Salaün 	ASSERT_LE(0, pid);
5922651712aSMickaël Salaün 
5932651712aSMickaël Salaün 	if (pid == 0) {
5942651712aSMickaël Salaün 		struct landlock_ruleset_attr attr = {
5952651712aSMickaël Salaün 			.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR |
5962651712aSMickaël Salaün 					     LANDLOCK_ACCESS_FS_EXECUTE,
5972651712aSMickaël Salaün 		};
5982651712aSMickaël Salaün 		struct landlock_path_beneath_attr path_beneath = {
5992651712aSMickaël Salaün 			.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE |
6002651712aSMickaël Salaün 					  LANDLOCK_ACCESS_FS_READ_DIR,
6012651712aSMickaël Salaün 		};
6022651712aSMickaël Salaün 		int ruleset_fd;
6032651712aSMickaël Salaün 
6042651712aSMickaël Salaün 		ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
6052651712aSMickaël Salaün 		if (ruleset_fd < 0)
6062651712aSMickaël Salaün 			_exit(1);
6072651712aSMickaël Salaün 
6082651712aSMickaël Salaün 		path_beneath.parent_fd =
6092651712aSMickaël Salaün 			open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
6102651712aSMickaël Salaün 		if (path_beneath.parent_fd >= 0) {
6112651712aSMickaël Salaün 			landlock_add_rule(ruleset_fd,
6122651712aSMickaël Salaün 					  LANDLOCK_RULE_PATH_BENEATH,
6132651712aSMickaël Salaün 					  &path_beneath, 0);
6142651712aSMickaël Salaün 			close(path_beneath.parent_fd);
6152651712aSMickaël Salaün 		}
6162651712aSMickaël Salaün 
6172651712aSMickaël Salaün 		path_beneath.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE;
6182651712aSMickaël Salaün 		path_beneath.parent_fd =
6192651712aSMickaël Salaün 			open(".", O_PATH | O_DIRECTORY | O_CLOEXEC);
6202651712aSMickaël Salaün 		if (path_beneath.parent_fd >= 0) {
6212651712aSMickaël Salaün 			landlock_add_rule(ruleset_fd,
6222651712aSMickaël Salaün 					  LANDLOCK_RULE_PATH_BENEATH,
6232651712aSMickaël Salaün 					  &path_beneath, 0);
6242651712aSMickaël Salaün 			close(path_beneath.parent_fd);
6252651712aSMickaël Salaün 		}
6262651712aSMickaël Salaün 
6272651712aSMickaël Salaün 		prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
6282651712aSMickaël Salaün 		if (landlock_restrict_self(ruleset_fd, restrict_flags))
6292651712aSMickaël Salaün 			_exit(1);
6302651712aSMickaël Salaün 		close(ruleset_fd);
6312651712aSMickaël Salaün 
6322651712aSMickaël Salaün 		execl("./true", "./true", NULL);
6332651712aSMickaël Salaün 		_exit(1);
6342651712aSMickaël Salaün 	}
6352651712aSMickaël Salaün 
6362651712aSMickaël Salaün 	ASSERT_EQ(pid, waitpid(pid, &status, 0));
6372651712aSMickaël Salaün 	ASSERT_TRUE(WIFEXITED(status));
6382651712aSMickaël Salaün 	EXPECT_EQ(0, WEXITSTATUS(status));
6392651712aSMickaël Salaün }
640