mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 20:54:03 +02:00
selftests/landlock: Add trace event test infrastructure and tests
Add tracefs test infrastructure in trace.h: helpers for mounting tracefs, enabling/disabling events, reading the trace buffer, counting regex matches, and extracting field values, plus per-event regex patterns. The patterns are anchored with ^ and $, verify every TP_printk field, and use no unescaped dot characters; TRACE_PREFIX matches the ftrace line format with either the expected task name (truncated to TASK_COMM_LEN - 1) or "<...>" for an evicted comm cache entry. Add trace_test.c with the trace fixture (setup enables all available events with a PID filter, teardown disables and clears) and the lifecycle, API, denial-field, and log-flag tests. Extend the existing true helper to open its working directory before exiting, triggering a read_dir denial inside a sandbox, so the exec-based tests can verify same_exec and the logged decision across an exec. Move regex_escape() from audit.h to common.h for shared use by the audit and trace tests. Enable CONFIG_ENABLE_DEFAULT_TRACERS alongside CONFIG_FTRACE in the selftest config: CONFIG_FTRACE alone only enables the tracer menu without activating any tracer, while CONFIG_ENABLE_DEFAULT_TRACERS selects TRACING (and thus TRACEPOINTS and event tracing) without depending on architecture-specific syscall tracepoints. When CONFIG_FTRACE is disabled it cannot be set, so TRACEPOINTS is correctly disabled too. Cc: Günther Noack <gnoack@google.com> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-15-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
bb91730f16
commit
2651712a15
|
|
@ -214,41 +214,6 @@ static int audit_set_status(int fd, __u32 key, __u32 val)
|
|||
return audit_request(fd, &msg, NULL);
|
||||
}
|
||||
|
||||
/* Returns a pointer to the last filled character of @dst, which is `\0`. */
|
||||
static __maybe_unused char *regex_escape(const char *const src, char *dst,
|
||||
size_t dst_size)
|
||||
{
|
||||
char *d = dst;
|
||||
|
||||
for (const char *s = src; *s; s++) {
|
||||
switch (*s) {
|
||||
case '$':
|
||||
case '*':
|
||||
case '.':
|
||||
case '[':
|
||||
case '\\':
|
||||
case ']':
|
||||
case '^':
|
||||
if (d >= dst + dst_size - 2)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d++ = '\\';
|
||||
*d++ = *s;
|
||||
break;
|
||||
default:
|
||||
if (d >= dst + dst_size - 1)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d++ = *s;
|
||||
}
|
||||
}
|
||||
if (d >= dst + dst_size - 1)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d = '\0';
|
||||
return d;
|
||||
}
|
||||
|
||||
/*
|
||||
* @domain_id: The domain ID extracted from the audit message (if the first part
|
||||
* of @pattern is REGEX_LANDLOCK_PREFIX). It is set to 0 if the domain ID is
|
||||
|
|
|
|||
|
|
@ -253,3 +253,50 @@ static void __maybe_unused set_unix_address(struct service_fixture *const srv,
|
|||
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
|
||||
srv->unix_addr.sun_path[0] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* regex_escape - Escape BRE metacharacters in a string
|
||||
*
|
||||
* @src: Source string to escape.
|
||||
* @dst: Destination buffer for the escaped string.
|
||||
* @dst_size: Size of the destination buffer.
|
||||
*
|
||||
* Escapes characters that have special meaning in POSIX Basic Regular
|
||||
* Expressions: $ * . [ \ ] ^
|
||||
*
|
||||
* Returns a pointer to the NUL terminator in @dst (cursor-style API for
|
||||
* chaining), or (char *)-ENOMEM if the buffer is too small.
|
||||
*/
|
||||
static __maybe_unused char *regex_escape(const char *const src, char *dst,
|
||||
size_t dst_size)
|
||||
{
|
||||
char *d = dst;
|
||||
|
||||
for (const char *s = src; *s; s++) {
|
||||
switch (*s) {
|
||||
case '$':
|
||||
case '*':
|
||||
case '.':
|
||||
case '[':
|
||||
case '\\':
|
||||
case ']':
|
||||
case '^':
|
||||
if (d >= dst + dst_size - 2)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d++ = '\\';
|
||||
*d++ = *s;
|
||||
break;
|
||||
default:
|
||||
if (d >= dst + dst_size - 1)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d++ = *s;
|
||||
}
|
||||
}
|
||||
if (d >= dst + dst_size - 1)
|
||||
return (char *)-ENOMEM;
|
||||
|
||||
*d = '\0';
|
||||
return d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ CONFIG_AF_UNIX_OOB=y
|
|||
CONFIG_AUDIT=y
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_ENABLE_DEFAULT_TRACERS=y
|
||||
CONFIG_FTRACE=y
|
||||
CONFIG_INET=y
|
||||
CONFIG_IPV6=y
|
||||
CONFIG_KEYS=y
|
||||
|
|
|
|||
634
tools/testing/selftests/landlock/trace.h
Normal file
634
tools/testing/selftests/landlock/trace.h
Normal file
|
|
@ -0,0 +1,634 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Landlock trace test helpers
|
||||
*
|
||||
* Copyright © 2026 Cloudflare, Inc.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#define TRACEFS_ROOT "/sys/kernel/tracing"
|
||||
#define TRACEFS_LANDLOCK_DIR TRACEFS_ROOT "/events/landlock"
|
||||
#define TRACEFS_CREATE_RULESET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_create_ruleset/enable"
|
||||
#define TRACEFS_CREATE_DOMAIN_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable"
|
||||
#define TRACEFS_ADD_RULE_FS_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable"
|
||||
#define TRACEFS_ADD_RULE_NET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net/enable"
|
||||
#define TRACEFS_CHECK_RULE_FS_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable"
|
||||
#define TRACEFS_CHECK_RULE_NET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable"
|
||||
#define TRACEFS_DENY_ACCESS_FS_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable"
|
||||
#define TRACEFS_DENY_ACCESS_NET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_net/enable"
|
||||
#define TRACEFS_DENY_PTRACE_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_deny_ptrace/enable"
|
||||
#define TRACEFS_DENY_SCOPE_SIGNAL_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_deny_scope_signal/enable"
|
||||
#define TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR \
|
||||
"/landlock_deny_scope_abstract_unix_socket/enable"
|
||||
#define TRACEFS_FREE_DOMAIN_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_free_domain/enable"
|
||||
#define TRACEFS_FREE_RULESET_ENABLE \
|
||||
TRACEFS_LANDLOCK_DIR "/landlock_free_ruleset/enable"
|
||||
#define TRACEFS_TRACE TRACEFS_ROOT "/trace"
|
||||
#define TRACEFS_SET_EVENT_PID TRACEFS_ROOT "/set_event_pid"
|
||||
#define TRACEFS_OPTIONS_EVENT_FORK TRACEFS_ROOT "/options/event-fork"
|
||||
|
||||
#define TRACE_BUFFER_SIZE (64 * 1024)
|
||||
|
||||
/*
|
||||
* Trace line prefix: matches the ftrace "trace" file format. Format: "
|
||||
* <task>-<pid> [<cpu>] <flags> <timestamp>: "
|
||||
*
|
||||
* The task parameter must be a string literal truncated to 15 chars
|
||||
* (TASK_COMM_LEN - 1), matching what the kernel stores in task->comm. The
|
||||
* pattern accepts either the expected task name or "<...>" because the ftrace
|
||||
* comm cache may evict short-lived processes (e.g., forked children that exit
|
||||
* before the trace buffer is read).
|
||||
*
|
||||
* No unescaped '.' in any REGEX macro; literal dots use '\\.'.
|
||||
*/
|
||||
#define TRACE_PREFIX(task) \
|
||||
"^ *\\(<\\.\\.\\.>" \
|
||||
"\\|" task "\\)" \
|
||||
"-[0-9]\\+ *\\[[0-9]\\+\\] [^ ]\\+ \\+[0-9]\\+\\.[0-9]\\+: "
|
||||
|
||||
/*
|
||||
* Task name for events emitted by kworker threads (e.g., free_domain fires from
|
||||
* a work queue, not from the test process).
|
||||
*/
|
||||
#define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+"
|
||||
|
||||
#define REGEX_ADD_RULE_FS(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_add_rule_fs: " \
|
||||
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
|
||||
"access_rights=[a-z_|]* " \
|
||||
"dev=[0-9]\\+:[0-9]\\+ " \
|
||||
"ino=[0-9]\\+ " \
|
||||
"path=[^ ]\\+$"
|
||||
|
||||
#define REGEX_ADD_RULE_NET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_add_rule_net: " \
|
||||
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
|
||||
"access_rights=[a-z_|]* " \
|
||||
"port=[0-9]\\+$"
|
||||
|
||||
#define REGEX_CREATE_RULESET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_create_ruleset: " \
|
||||
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
|
||||
"handled_fs=[a-z_|]* " \
|
||||
"handled_net=[a-z_|]* " \
|
||||
"scoped=[a-z_|]*$"
|
||||
|
||||
#define REGEX_CREATE_DOMAIN(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_create_domain: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"parent=[0-9a-f]\\+ " \
|
||||
"ruleset=[0-9a-f]\\+\\.[0-9]\\+$"
|
||||
|
||||
#define REGEX_CHECK_RULE_FS(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_check_rule_fs: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"access_request=[a-z_|]* " \
|
||||
"dev=[0-9]\\+:[0-9]\\+ " \
|
||||
"ino=[0-9]\\+ " \
|
||||
"grants={[a-z_|,]*}$"
|
||||
|
||||
#define REGEX_CHECK_RULE_NET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_check_rule_net: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"access_request=[a-z_|]* " \
|
||||
"port=[0-9]\\+ " \
|
||||
"grants={[a-z_|,]*}$"
|
||||
|
||||
#define REGEX_DENY_ACCESS_FS(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_access_fs: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"blockers=[a-z_|]* " \
|
||||
"dev=[0-9]\\+:[0-9]\\+ " \
|
||||
"ino=[0-9]\\+ " \
|
||||
"path=[^ ]*$"
|
||||
|
||||
#define REGEX_DENY_ACCESS_NET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_access_net: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"blockers=[a-z_|]* " \
|
||||
"sport=[0-9]\\+ " \
|
||||
"dport=[0-9]\\+$"
|
||||
|
||||
#define REGEX_DENY_PTRACE(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_ptrace: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"tracee_pid=[0-9]\\+ " \
|
||||
"tracee_comm=[^ ]*$"
|
||||
|
||||
#define REGEX_DENY_SCOPE_SIGNAL(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_scope_signal: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"target_pid=[0-9]\\+ " \
|
||||
"target_comm=[^ ]*$"
|
||||
|
||||
#define REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_scope_abstract_unix_socket: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"peer_pid=[0-9]\\+ " \
|
||||
"sun_path=[^ ]*$"
|
||||
|
||||
#define REGEX_FREE_DOMAIN(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_free_domain: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"denials=[0-9]\\+$"
|
||||
|
||||
#define REGEX_FREE_RULESET(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_free_ruleset: " \
|
||||
"ruleset=[0-9a-f]\\+\\.[0-9]\\+$"
|
||||
|
||||
static int __maybe_unused tracefs_write(const char *path, const char *value)
|
||||
{
|
||||
int fd;
|
||||
ssize_t ret;
|
||||
size_t len = strlen(value);
|
||||
|
||||
fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
ret = write(fd, value, len);
|
||||
close(fd);
|
||||
if (ret < 0)
|
||||
return -errno;
|
||||
if ((size_t)ret != len)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused tracefs_write_int(const char *path, int value)
|
||||
{
|
||||
char buf[32];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", value);
|
||||
return tracefs_write(path, buf);
|
||||
}
|
||||
|
||||
static int __maybe_unused tracefs_setup(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
/* Mount tracefs if not already mounted. */
|
||||
if (stat(TRACEFS_ROOT, &st) != 0) {
|
||||
int ret = mount("tracefs", TRACEFS_ROOT, "tracefs", 0, NULL);
|
||||
|
||||
if (ret)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* Verify landlock events are available. */
|
||||
if (stat(TRACEFS_LANDLOCK_DIR, &st) != 0)
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up PID-based event filtering so only events from the current process and
|
||||
* its children are recorded. This is analogous to audit's AUDIT_EXE filter: it
|
||||
* prevents events from unrelated processes from polluting the trace buffer.
|
||||
*/
|
||||
static int __maybe_unused tracefs_set_pid_filter(pid_t pid)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Enable event-fork so children inherit the PID filter. */
|
||||
ret = tracefs_write(TRACEFS_OPTIONS_EVENT_FORK, "1");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return tracefs_write_int(TRACEFS_SET_EVENT_PID, pid);
|
||||
}
|
||||
|
||||
/* Clear the PID filter to stop filtering by PID. */
|
||||
static int __maybe_unused tracefs_clear_pid_filter(void)
|
||||
{
|
||||
return tracefs_write(TRACEFS_SET_EVENT_PID, "");
|
||||
}
|
||||
|
||||
static int __maybe_unused tracefs_enable_event(const char *enable_path,
|
||||
bool enable)
|
||||
{
|
||||
return tracefs_write(enable_path, enable ? "1" : "0");
|
||||
}
|
||||
|
||||
static int __maybe_unused tracefs_clear(void)
|
||||
{
|
||||
return tracefs_write(TRACEFS_TRACE, "");
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the trace buffer content into a newly allocated buffer. The caller is
|
||||
* responsible for freeing the returned buffer. Returns NULL on error.
|
||||
*/
|
||||
static char __maybe_unused *tracefs_read_trace(void)
|
||||
{
|
||||
char *buf;
|
||||
int fd;
|
||||
ssize_t total = 0, ret;
|
||||
|
||||
buf = malloc(TRACE_BUFFER_SIZE);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
fd = open(TRACEFS_TRACE, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (total < TRACE_BUFFER_SIZE - 1) {
|
||||
ret = read(fd, buf + total, TRACE_BUFFER_SIZE - 1 - total);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
total += ret;
|
||||
}
|
||||
close(fd);
|
||||
buf[total] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Counts the number of lines in @buf matching the basic regex @pattern. */
|
||||
static int __maybe_unused tracefs_count_matches(const char *buf,
|
||||
const char *pattern)
|
||||
{
|
||||
regex_t regex;
|
||||
int count = 0;
|
||||
const char *line, *end;
|
||||
|
||||
if (regcomp(®ex, pattern, 0) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
line = buf;
|
||||
while (*line) {
|
||||
end = strchr(line, '\n');
|
||||
if (!end)
|
||||
end = line + strlen(line);
|
||||
|
||||
/* Create a temporary NUL-terminated line. */
|
||||
size_t len = end - line;
|
||||
char *tmp = malloc(len + 1);
|
||||
|
||||
if (tmp) {
|
||||
memcpy(tmp, line, len);
|
||||
tmp[len] = '\0';
|
||||
if (regexec(®ex, tmp, 0, NULL, 0) == 0)
|
||||
count++;
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
if (*end == '\n')
|
||||
line = end + 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
regfree(®ex);
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extracts the value of a named field from a trace line in @buf. Searches for
|
||||
* the first line matching @line_pattern, then extracts the value after
|
||||
* "@field_name=" into @out. Stops at space or newline.
|
||||
*
|
||||
* Returns 0 on success, -ENOENT if no match.
|
||||
*/
|
||||
static int __maybe_unused tracefs_extract_field(const char *buf,
|
||||
const char *line_pattern,
|
||||
const char *field_name,
|
||||
char *out, size_t out_size)
|
||||
{
|
||||
regex_t regex;
|
||||
const char *line, *end;
|
||||
|
||||
if (regcomp(®ex, line_pattern, 0) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
line = buf;
|
||||
while (*line) {
|
||||
end = strchr(line, '\n');
|
||||
if (!end)
|
||||
end = line + strlen(line);
|
||||
|
||||
size_t len = end - line;
|
||||
char *tmp = malloc(len + 1);
|
||||
|
||||
if (tmp) {
|
||||
const char *field, *val_start;
|
||||
size_t field_len, val_len;
|
||||
|
||||
memcpy(tmp, line, len);
|
||||
tmp[len] = '\0';
|
||||
|
||||
if (regexec(®ex, tmp, 0, NULL, 0) != 0) {
|
||||
free(tmp);
|
||||
goto next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find "field_name=" in the line, ensuring a word
|
||||
* boundary before the field name to avoid substring
|
||||
* matches (e.g., "port" in "sport").
|
||||
*/
|
||||
field_len = strlen(field_name);
|
||||
field = tmp;
|
||||
while ((field = strstr(field, field_name))) {
|
||||
if (field[field_len] == '=' &&
|
||||
(field == tmp || field[-1] == ' '))
|
||||
break;
|
||||
field++;
|
||||
}
|
||||
if (!field) {
|
||||
free(tmp);
|
||||
regfree(®ex);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
val_start = field + field_len + 1;
|
||||
val_len = 0;
|
||||
while (val_start[val_len] &&
|
||||
val_start[val_len] != ' ' &&
|
||||
val_start[val_len] != '\n')
|
||||
val_len++;
|
||||
|
||||
if (val_len >= out_size)
|
||||
val_len = out_size - 1;
|
||||
memcpy(out, val_start, val_len);
|
||||
out[val_len] = '\0';
|
||||
|
||||
free(tmp);
|
||||
regfree(®ex);
|
||||
return 0;
|
||||
}
|
||||
next:
|
||||
if (*end == '\n')
|
||||
line = end + 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
regfree(®ex);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Common fixture setup for trace tests. Mounts tracefs if needed and sets a
|
||||
* PID filter. The caller must create a mount namespace first
|
||||
* (unshare(CLONE_NEWNS) + mount(MS_REC | MS_PRIVATE)) to isolate the tracefs
|
||||
* mount; the trace buffer, per-event enable flags, and PID filter are global
|
||||
* kernel state, scoped to the test by the PID filter.
|
||||
*
|
||||
* Returns 0 on success, -errno on failure (caller should SKIP).
|
||||
*/
|
||||
static int __maybe_unused tracefs_fixture_setup(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = tracefs_setup();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return tracefs_set_pid_filter(getpid());
|
||||
}
|
||||
|
||||
static void __maybe_unused tracefs_fixture_teardown(void)
|
||||
{
|
||||
tracefs_clear_pid_filter();
|
||||
}
|
||||
|
||||
/*
|
||||
* Temporarily raises CAP_SYS_ADMIN effective capability, calls @func, then
|
||||
* drops the capability. Returns the value from @func, or -EPERM if the
|
||||
* capability manipulation fails.
|
||||
*/
|
||||
static int __maybe_unused tracefs_priv_call(int (*func)(void))
|
||||
{
|
||||
const cap_value_t admin = CAP_SYS_ADMIN;
|
||||
cap_t cap_p;
|
||||
int ret;
|
||||
|
||||
cap_p = cap_get_proc();
|
||||
if (!cap_p)
|
||||
return -EPERM;
|
||||
|
||||
if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) ||
|
||||
cap_set_proc(cap_p)) {
|
||||
cap_free(cap_p);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ret = func();
|
||||
|
||||
cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR);
|
||||
cap_set_proc(cap_p);
|
||||
cap_free(cap_p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Read the trace buffer with elevated privileges. Returns NULL on failure. */
|
||||
static char __maybe_unused *tracefs_read_buf(void)
|
||||
{
|
||||
/* Cannot use tracefs_priv_call() because the return type is char *. */
|
||||
cap_t cap_p;
|
||||
char *buf;
|
||||
const cap_value_t admin = CAP_SYS_ADMIN;
|
||||
|
||||
cap_p = cap_get_proc();
|
||||
if (!cap_p)
|
||||
return NULL;
|
||||
|
||||
if (cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_SET) ||
|
||||
cap_set_proc(cap_p)) {
|
||||
cap_free(cap_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = tracefs_read_trace();
|
||||
|
||||
cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &admin, CAP_CLEAR);
|
||||
cap_set_proc(cap_p);
|
||||
cap_free(cap_p);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Clear the trace buffer with elevated privileges. Returns 0 on success. */
|
||||
static int __maybe_unused tracefs_clear_buf(void)
|
||||
{
|
||||
return tracefs_priv_call(tracefs_clear);
|
||||
}
|
||||
|
||||
/*
|
||||
* Forks a child that creates a Landlock sandbox and performs an FS access. The
|
||||
* parent waits for the child, then reads the trace buffer.
|
||||
*
|
||||
* Requires common.h and wrappers.h to be included before trace.h.
|
||||
*/
|
||||
static void __maybe_unused sandbox_child_fs_access(
|
||||
struct __test_metadata *const _metadata, const char *rule_path,
|
||||
__u64 handled_access, __u64 allowed_access, const char *access_path)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
|
||||
pid = fork();
|
||||
ASSERT_LE(0, pid);
|
||||
|
||||
if (pid == 0) {
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = handled_access,
|
||||
};
|
||||
struct landlock_path_beneath_attr path_beneath = {
|
||||
.allowed_access = allowed_access,
|
||||
};
|
||||
int ruleset_fd, fd;
|
||||
|
||||
ruleset_fd = landlock_create_ruleset(&ruleset_attr,
|
||||
sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
path_beneath.parent_fd =
|
||||
open(rule_path, O_PATH | O_DIRECTORY | O_CLOEXEC);
|
||||
if (path_beneath.parent_fd < 0) {
|
||||
close(ruleset_fd);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0)) {
|
||||
close(path_beneath.parent_fd);
|
||||
close(ruleset_fd);
|
||||
_exit(1);
|
||||
}
|
||||
close(path_beneath.parent_fd);
|
||||
|
||||
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
||||
if (landlock_restrict_self(ruleset_fd, 0)) {
|
||||
close(ruleset_fd);
|
||||
_exit(1);
|
||||
}
|
||||
close(ruleset_fd);
|
||||
|
||||
fd = open(access_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
ASSERT_EQ(pid, waitpid(pid, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
/*
|
||||
* Forks a child that creates a Landlock sandbox allowing execute+read_dir for
|
||||
* /usr and execute-only for ".", then execs ./true. The true binary opens "."
|
||||
* on startup, triggering a read_dir denial with same_exec=0. The parent waits
|
||||
* for the child to exit.
|
||||
*/
|
||||
static void __maybe_unused sandbox_child_exec_true(
|
||||
struct __test_metadata *const _metadata, __u32 restrict_flags)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
|
||||
pid = fork();
|
||||
ASSERT_LE(0, pid);
|
||||
|
||||
if (pid == 0) {
|
||||
struct landlock_ruleset_attr attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR |
|
||||
LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
};
|
||||
struct landlock_path_beneath_attr path_beneath = {
|
||||
.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
};
|
||||
int ruleset_fd;
|
||||
|
||||
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
path_beneath.parent_fd =
|
||||
open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC);
|
||||
if (path_beneath.parent_fd >= 0) {
|
||||
landlock_add_rule(ruleset_fd,
|
||||
LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0);
|
||||
close(path_beneath.parent_fd);
|
||||
}
|
||||
|
||||
path_beneath.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE;
|
||||
path_beneath.parent_fd =
|
||||
open(".", O_PATH | O_DIRECTORY | O_CLOEXEC);
|
||||
if (path_beneath.parent_fd >= 0) {
|
||||
landlock_add_rule(ruleset_fd,
|
||||
LANDLOCK_RULE_PATH_BENEATH,
|
||||
&path_beneath, 0);
|
||||
close(path_beneath.parent_fd);
|
||||
}
|
||||
|
||||
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
||||
if (landlock_restrict_self(ruleset_fd, restrict_flags))
|
||||
_exit(1);
|
||||
close(ruleset_fd);
|
||||
|
||||
execl("./true", "./true", NULL);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
ASSERT_EQ(pid, waitpid(pid, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
1101
tools/testing/selftests/landlock/trace_test.c
Normal file
1101
tools/testing/selftests/landlock/trace_test.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Minimal helper for Landlock selftests. Opens its own working directory
|
||||
* before exiting, which may trigger access denials depending on the sandbox
|
||||
* configuration.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
close(open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user