Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436// SPDX-License-Identifier: GPL-2.0-only/* * Landlock - Tracepoint helpers * * Copyright © 2025 Microsoft Corporation * Copyright © 2026 Cloudflare, Inc. */
#include <kunit/test.h>#include <linux/cleanup.h>#include <linux/dcache.h>#include <linux/err.h>#include <linux/fs.h>#include <linux/lsm_audit.h>#include <linux/socket.h>#include <net/sock.h>
#include "access.h"#include "domain.h"#include "fs.h"#include "log.h"#include "ruleset.h"#include "trace.h"
/* * Generates the tracepoint definitions in this translation unit. The trace * event header dereferences the traced objects in TP_fast_assign, so the full * struct definitions (e.g. ruleset.h, domain.h) must be included before it. */#define CREATE_TRACE_POINTS#include <trace/events/landlock.h>
/** * landlock_trace_free_domain - Emit a tracepoint on domain deallocation * * @hierarchy: The domain's hierarchy being deallocated. * * Fires only for a hierarchy whose creation event was emitted, i.e. one that * left LANDLOCK_LOG_UNCOMMITTED in landlock_restrict_self(). This keeps the * create/free pair balanced: a hierarchy that never became observable is freed * silently, while a domain that landlock_restrict_self() created and a * thread-sync failure then aborted still fires free_domain, because its * creation event already fired. * * Called from landlock_log_free_domain(). */void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy){ /* * The log_status read is a correctness guard (keep the create/free pair * balanced), not a cost guard, so this cold path needs no * trace_..._enabled() check: the tracepoint is a static-branch no-op * when disabled. The denial path guards trace_..._enabled() instead * because it does expensive __getname()/path work before emitting. */ if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED) trace_landlock_free_domain(hierarchy);}
/** * landlock_trace_denial - Emit a tracepoint for a denied access request * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. * @missing: The final missing access or permission subset, when applicable. * @same_exec: Whether the policy subject is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). * @logged: Whether the domain's policy selects this denial for logging, as * computed by landlock_log_denial(). * * Emits the tracepoint matching @request->type when its event is enabled. * Unlike audit, fires regardless of @logged; the value is recorded in the event * so consumers can filter on it. * * Called from landlock_log_denial(). */void landlock_trace_denial( const struct landlock_request *const request, const struct landlock_hierarchy *const youngest_denied, const access_mask_t missing, const bool same_exec, const bool logged){ switch (request->type) { case LANDLOCK_REQUEST_NAMESPACE: if (trace_landlock_deny_permission_namespace_enabled()) { const struct landlock_blockers blockers = { .access = missing, .type = request->type, };
trace_landlock_deny_permission_namespace( youngest_denied, same_exec, logged, &blockers, request->audit.u.ns.ns_type, request->audit.u.ns.ns_id); } break; case LANDLOCK_REQUEST_CAPABILITY: if (trace_landlock_deny_permission_capability_enabled()) { const struct landlock_blockers blockers = { .access = missing, .type = request->type, };
trace_landlock_deny_permission_capability( youngest_denied, same_exec, logged, &blockers, request->audit.u.cap); } break; case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) { const struct landlock_blockers blockers = { .access = missing, .type = request->type, }; char *buf __free(__putname) = __getname(); struct path dentry_path; const char *pathname; const struct path *path = NULL;
/* * Selects the path from the audit data type, as * dump_common_audit_data() does. A FS_ACCESS denial * carries a file (hook_file_truncate) or an ioctl op * (hook_file_ioctl) rather than a path; * FS_CHANGE_TOPOLOGY carries a path or a bare dentry. * Reading the wrong union member would dereference * garbage, so every reachable type is handled here. */ switch (request->audit.type) { case LSM_AUDIT_DATA_FILE: path = &request->audit.u.file->f_path; break; case LSM_AUDIT_DATA_IOCTL_OP: path = &request->audit.u.op->path; break; case LSM_AUDIT_DATA_DENTRY: /* * Build a path on the stack with the real * dentry so TP_fast_assign can extract dev and * ino; the mnt field is unused there. */ dentry_path = (struct path){ .dentry = request->audit.u.dentry, }; path = &dentry_path; break; case LSM_AUDIT_DATA_PATH: path = &request->audit.u.path; break; default: WARN_ONCE(1, "Unhandled Landlock FS audit type %d", request->audit.type); break; }
if (!path) break;
if (!buf) { pathname = "<no_mem>"; } else if (request->audit.type == LSM_AUDIT_DATA_DENTRY) { /* No vfsmount: render the dentry path alone. */ pathname = dentry_path_raw( request->audit.u.dentry, buf, PATH_MAX); if (IS_ERR(pathname)) pathname = PTR_ERR(pathname) == -ENAMETOOLONG ? "<too_long>" : "<unreachable>"; } else { pathname = resolve_path_for_trace(path, buf); }
trace_landlock_deny_access_fs(youngest_denied, same_exec, logged, &blockers, path, pathname); } break; case LANDLOCK_REQUEST_NET_ACCESS: if (trace_landlock_deny_access_net_enabled()) { const struct landlock_net_trace *const trace_net = request->trace_net; const struct landlock_blockers blockers = { .access = missing, .type = request->type, }; struct sockaddr_storage address = {};
if (WARN_ON_ONCE(!trace_net || !trace_net->address)) return;
if (WARN_ON_ONCE( trace_net->addrlen < (int)offsetofend(struct sockaddr, sa_family) || trace_net->addrlen > (int)sizeof(address))) return;
memcpy(&address, trace_net->address, trace_net->addrlen); trace_landlock_deny_access_net( youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, trace_net->socket_family, &address, trace_net->addrlen); } break; case LANDLOCK_REQUEST_PTRACE: if (trace_landlock_deny_ptrace_enabled()) { const struct landlock_ptrace_trace *const trace_ptrace = request->trace_ptrace;
if (WARN_ON_ONCE(!trace_ptrace || !trace_ptrace->tracer)) return;
trace_landlock_deny_ptrace( youngest_denied, same_exec, logged, trace_ptrace->tracee_domain_id, request->audit.u.tsk, trace_ptrace->tracer); } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: if (trace_landlock_deny_scope_signal_enabled()) { const struct landlock_signal_trace *const trace_signal = request->trace_signal;
if (WARN_ON_ONCE(!trace_signal)) return;
trace_landlock_deny_scope_signal( youngest_denied, same_exec, logged, trace_signal->target_domain_id, request->audit.u.tsk, trace_signal->signal); } break; case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) trace_landlock_deny_scope_abstract_unix_socket( youngest_denied, same_exec, logged, request->other_domain_id, request->audit.u.net->sk); break; default: WARN_ONCE(1, "Unhandled Landlock request type %d", request->type); break; }}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_trace_seq_init(struct trace_seq *const seq, const size_t size){ memset(seq, 0, sizeof(*seq)); seq_buf_init(&seq->seq, seq->buffer, size);}
static void test_untrusted_str_data(struct kunit *const test){ const char binary[] = { 'a', '\0', '<' }; static const char ellipsis[] = "\xe2\x80\xa6"; struct trace_seq *const seq = kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); const char *output;
KUNIT_ASSERT_NOT_NULL(test, seq); test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, "<too_long>", 10); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, "<too_long>");
test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, binary, sizeof(binary)); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, "a\\000<");
/* Input ellipsis bytes are escaped and cannot mimic the raw marker. */ test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, ellipsis, sizeof(ellipsis) - 1); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, "\\342\\200\\246");}
static void test_untrusted_str_boundaries(struct kunit *const test){ static const char escaped_space[] = "\\040"; const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; const size_t escape_len = sizeof(escaped_space) - 1; const size_t exact_prefix_len = output_size - marker_len - 1 - escape_len; const size_t short_prefix_len = exact_prefix_len + 1; struct trace_seq *const seq = kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); char *const input = kunit_kmalloc(test, output_size + 1, GFP_KERNEL); char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); const char *output;
KUNIT_ASSERT_NOT_NULL(test, seq); KUNIT_ASSERT_NOT_NULL(test, input); KUNIT_ASSERT_NOT_NULL(test, expected);
/* The escaped string and its trailing NUL exactly fit the limit. */ memset(input, 'a', output_size - 1); test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, input, output_size - 1); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_EQ(test, seq->seq.len, output_size); KUNIT_EXPECT_EQ(test, memcmp(output, input, output_size - 1), 0);
/* Stop before a four-byte escape when only three bytes remain. */ memset(input, 'a', short_prefix_len); input[short_prefix_len] = ' '; memset(input + short_prefix_len + 1, 'b', 5); memset(expected, 'a', short_prefix_len); memcpy(expected + short_prefix_len, TRACE_TRUNCATION_MARKER, marker_len + 1); test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, input, short_prefix_len + 6); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, expected);
/* Include a four-byte escape that exactly fills the prefix capacity. */ memset(input, 'a', exact_prefix_len); input[exact_prefix_len] = ' '; memset(input + exact_prefix_len + 1, 'b', marker_len + 1); memset(expected, 'a', exact_prefix_len); memcpy(expected + exact_prefix_len, escaped_space, escape_len); memcpy(expected + exact_prefix_len + escape_len, TRACE_TRUNCATION_MARKER, marker_len + 1); test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, input, exact_prefix_len + marker_len + 2); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, expected);
/* Literal backslashes remain escaped in complete output. */ test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); output = __trace_print_untrusted_str(seq, "/\\000", 5); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, "/\\\\000");}
static void test_untrusted_str_cursor(struct kunit *const test){ const size_t padding_len = TRACE_SEQ_BUFFER_SIZE - TRACE_UNTRUSTED_STR_OUTPUT_SIZE + 1; struct trace_seq *const seq = kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); char *const padding = kunit_kzalloc(test, padding_len, GFP_KERNEL); const char *output;
KUNIT_ASSERT_NOT_NULL(test, seq); KUNIT_ASSERT_NOT_NULL(test, padding);
/* Accept available space exactly equal to the fixed reservation. */ test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); trace_seq_putmem(seq, padding, padding_len - 1); output = __trace_print_untrusted_str(seq, "/a", 2); KUNIT_ASSERT_NOT_NULL(test, output); KUNIT_EXPECT_STREQ(test, output, "/a"); KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len - 1 + sizeof("/a"));
/* Reject one byte less without changing the scratch cursor. */ test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); trace_seq_putmem(seq, padding, padding_len); output = __trace_print_untrusted_str(seq, "/a", 2); KUNIT_EXPECT_NULL(test, output); KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len);}
static void test_untrusted_str_composition(struct kunit *const test){ static const struct trace_print_flags flags[] = { { .mask = 1, .name = "read" }, }; const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; const size_t prefix_len = output_size - sizeof(TRACE_TRUNCATION_MARKER); struct trace_seq *const seq = kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); char *const path = kunit_kmalloc(test, output_size, GFP_KERNEL); const char *flags_output, *path_output;
KUNIT_ASSERT_NOT_NULL(test, seq); KUNIT_ASSERT_NOT_NULL(test, expected); KUNIT_ASSERT_NOT_NULL(test, path); memset(path, 'a', output_size); memset(expected, 'a', prefix_len); memcpy(expected + prefix_len, TRACE_TRUNCATION_MARKER, sizeof(TRACE_TRUNCATION_MARKER));
/* Exercise both legal TP_printk() sibling evaluation orders. */ test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); path_output = __trace_print_untrusted_str(seq, path, output_size); flags_output = trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); KUNIT_ASSERT_NOT_NULL(test, path_output); KUNIT_EXPECT_STREQ(test, path_output, expected); KUNIT_EXPECT_STREQ(test, flags_output, "read");
test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); flags_output = trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); path_output = __trace_print_untrusted_str(seq, path, output_size); KUNIT_ASSERT_NOT_NULL(test, path_output); KUNIT_EXPECT_STREQ(test, path_output, expected); KUNIT_EXPECT_STREQ(test, flags_output, "read");}
static struct kunit_case test_cases[] = { /* clang-format off */ KUNIT_CASE(test_untrusted_str_data), KUNIT_CASE(test_untrusted_str_boundaries), KUNIT_CASE(test_untrusted_str_cursor), KUNIT_CASE(test_untrusted_str_composition), {} /* clang-format on */};
static struct kunit_suite test_suite = { .name = "landlock_trace", .test_cases = test_cases,};
kunit_test_suite(test_suite);
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */