| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/20 23:54 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"SECURITY",
"SECURITY_LANDLOCK",
"TRACEPOINTS",
"INET"
],
"FocusSymbols": [
"landlock_trace_denial",
"landlock_append_fs_rule",
"landlock_append_net_rule",
"current_check_access_socket",
"hook_unix_stream_connect",
"hook_unix_may_send",
"hook_ptrace_access_check",
"hook_ptrace_traceme",
"hook_task_kill",
"hook_file_send_sigiotask"
],
"KMSANReasoning": "The patch series updates Landlock trace events and internal logging context:\n1. Renames trace events to match updated terminology (e.g. landlock_check_rule_fs -\u003e landlock_check_rule_inode, landlock_add_rule_fs -\u003e landlock_add_rule_path_beneath, etc.).\n2. Adds flags, socket_family, and tracer/signal details to tracepoint prototypes for BTF/raw tracepoint consumers.\n3. Introduces helper structs (landlock_blockers, landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace) and places trace-only pointers in a union within struct landlock_request.\n4. Refactors network denial tracepoint logic to extract the port from checked sockaddr data.\n\nMemory initialization and safety review:\n- All stack-allocated structures and compound literals (such as struct landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace, and landlock_blockers) are fully initialized using designated initializers.\n- The union in struct landlock_request is partitioned cleanly by request-\u003etype; each switch branch in landlock_trace_denial only accesses the union member corresponding to the request type and guards against NULL with WARN_ON_ONCE.\n- The address buffer in landlock_trace_denial() is explicitly zero-initialized (`struct sockaddr_storage address = {};`) before copying addrlen bytes, and `has_port` verifies that addrlen is at least `offsetofend(struct sockaddr_in, sin_port)` before inspecting the port.\n- No kernel memory or padding is exposed or copied to userspace via copy_to_user(), netlink, ioctl, or socket options. Tracepoint ring buffer records only store scalar values.\n\nSince the patch does not read or leak uninitialized memory and introduces no uninitialized memory risks, a dedicated KMSAN fuzzing session is not required; standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false,
"Reasoning": "The patch refactors Landlock trace events and modifies Landlock's denial logging data structures (introducing struct landlock_blockers and trace context structs passed through a union in struct landlock_request). It updates multiple reachable LSM hooks (hook_unix_stream_connect, hook_unix_may_send, hook_ptrace_access_check, hook_ptrace_traceme, hook_task_kill, hook_file_send_sigiotask, current_check_access_socket) and adds new runtime assertions including multiple WARN_ON_ONCE checks in landlock_trace_denial() and a lockdep_assert_held() check in get_socket_domain_id(). These modifications affect reachable security subsystem code and warrant fuzzing to ensure that invariants hold and no unintended regressions or assertion failures occur.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/20 23:54 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 07f6c65816aba6a93b7296aeb30665ea83d41747\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Sep 20 23:54:12 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst\nindex af9267cca47d9..304a8d06273ee 100644\n--- a/Documentation/trace/events-landlock.rst\n+++ b/Documentation/trace/events-landlock.rst\n@@ -6,7 +6,7 @@ Landlock Trace Events\n =====================\n \n :Author: Mickaël Salaün\n-:Date: August 2026\n+:Date: September 2026\n \n Landlock emits trace events for sandbox lifecycle operations and access\n denials. These events can be consumed by ftrace (for human-readable\n@@ -31,8 +31,8 @@ Landlock trace events are organized in four categories:\n **Syscall events** are emitted during Landlock system calls:\n \n - ``landlock_create_ruleset``: a new ruleset is created\n-- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset\n-- ``landlock_add_rule_net``: a network port rule is added to a ruleset\n+- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset\n+- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset\n - ``landlock_create_domain``: a new domain is created from a ruleset\n - ``landlock_enforce_domain``: a domain is enforced on a thread\n \n@@ -47,8 +47,8 @@ Landlock trace events are organized in four categories:\n \n **Rule evaluation events** are emitted during rule matching:\n \n-- ``landlock_check_rule_fs``: a filesystem rule is evaluated\n-- ``landlock_check_rule_net``: a network port rule is evaluated\n+- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated\n+- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated\n \n **Lifecycle events**:\n \n@@ -141,10 +141,10 @@ in some field formats:\n treated as untrusted input and escaped in the trace text output so it\n cannot inject field separators or control characters.\n \n-- **Other party's domain**: A scope or ptrace denial compares the\n- subject's denying domain (``domain=``, always the enforcing domain and\n- never the current task) with the other party's domain, so these\n- tracepoints also report the other party's domain as a scalar ID:\n+- **Other party's domain**: A scope or ptrace denial compares the subject's\n+ denying domain (``domain=``), which is the enforcing domain and not\n+ necessarily the current task's domain, with the other party's domain.\n+ These tracepoints also report the other party's domain as a scalar ID:\n ``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and\n ``peer_domain=`` (abstract unix socket). It is ``0`` when the other\n party is unsandboxed, and otherwise a domain ID that a consumer resolves\n@@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID.\n Interpreting check_rule events\n ==============================\n \n-The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer\n+The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer\n rule evaluation, which is useful for understanding *why* a specific\n access is allowed or denied.\n \n@@ -231,9 +231,10 @@ contribution, not the final decision:\n through another matching rule, or the right is denied and appears in the\n ``blockers=`` field of the corresponding ``deny_access`` event.\n \n-To reconstruct the decision for an object, aggregate the ``grants=``\n-groups of all ``check_rule`` events emitted for that object during the\n-check.\n+For an access check that a consumer can delimit, aggregate the ``grants=``\n+groups of all matching ``check_rule`` events. These events do not carry a\n+request ID; use their execution context and generic tracepoints to separate\n+concurrent or successive checks of the same object.\n \n .. note::\n \n@@ -244,29 +245,31 @@ check.\n \n For example, a program sandboxed with read and execute access to the\n whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the\n-read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires\n+read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires\n with the requested rights intersected against what that rule grants.\n The ``access_request=`` mask includes ``truncate`` because the file-open hook\n evaluates that optional right alongside the required access, but the\n rule does not grant it, so ``truncate`` never appears in ``grants=``::\n \n- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}\n- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}\n+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}\n+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}\n \n The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A\n single ``grants=`` group means the enforcing domain has one layer. With\n two nested sandboxes that each grant the same rights, the rule spans both\n layers, so ``grants=`` has one group per layer::\n \n- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}\n- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}\n+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}\n+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}\n \n eBPF access\n ===========\n \n-eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the\n-tracepoint arguments directly through BTF. The arguments include both\n-standard kernel objects and Landlock-internal objects:\n+BTF-enabled raw tracepoint programs attached through libbpf\n+``SEC(\"tp_btf/...\")`` sections receive typed callback arguments. The\n+event prototypes in `Event reference`_ document their argument layouts.\n+The arguments include both standard kernel objects and Landlock-internal\n+objects:\n \n - Standard kernel objects (``struct task_struct``, ``struct sock``,\n ``struct path``, ``struct dentry``) can be used with existing BPF\n@@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects:\n Internal struct layouts may change between kernel versions; use CO-RE\n for field relocation.\n \n-A stateful eBPF program observes the full event stream and maintains\n+A stateful eBPF program attached before sandbox construction can maintain\n per-domain state in BPF maps:\n \n 1. On ``landlock_create_domain``: record the domain ID and parent (the\n@@ -293,7 +296,10 @@ per-domain state in BPF maps:\n final statistics.\n \n This approach requires no kernel modification and no Landlock-specific\n-BPF helpers. The Landlock IDs serve as correlation keys across events.\n+BPF helpers. Landlock IDs serve as correlation keys within one boot.\n+Records exported through tracing or BPF buffers can be lost, and records\n+from different CPUs are not globally ordered, so consumers must detect and\n+reconcile incomplete state.\n \n Audit filtering equivalence\n ===========================\ndiff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h\nindex d05253afaf590..523ba5ea98700 100644\n--- a/include/trace/events/landlock.h\n+++ b/include/trace/events/landlock.h\n@@ -10,14 +10,19 @@\n #if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ)\n #define _TRACE_LANDLOCK_H\n \n+#include \u003clinux/in.h\u003e\n+#include \u003clinux/in6.h\u003e\n #include \u003clinux/landlock.h\u003e\n+#include \u003clinux/socket.h\u003e\n #include \u003clinux/string.h\u003e\n #include \u003clinux/string_helpers.h\u003e\n #include \u003clinux/tracepoint.h\u003e\n #include \u003clinux/trace_seq.h\u003e\n #include \u003cnet/af_unix.h\u003e\n \n+enum landlock_request_type;\n struct dentry;\n+struct landlock_blockers;\n struct landlock_domain;\n struct landlock_hierarchy;\n struct landlock_rule;\n@@ -26,6 +31,12 @@ struct path;\n struct sock;\n struct task_struct;\n \n+static_assert(sizeof(access_mask_t) \u003c= sizeof(u64));\n+\n+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY);\n+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS);\n+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS);\n+\n #ifdef CREATE_TRACE_POINTS\n \n /* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */\n@@ -108,7 +119,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)\n * Fills the dense per-domain-layer array layers (one access mask per layer,\n * indexed by level - 1) from rule's sparse layer stack, keeping only the\n * requested rights (access_request). Layers with no matching rule entry get\n- * a zero mask. Shared by the check_rule_fs and check_rule_net events.\n+ * a zero mask. Shared by the check_rule_inode and check_rule_net_port events.\n *\n * rule-\u003elayers is sorted by ascending level, with levels in the domain's\n * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps\n@@ -182,6 +193,9 @@ static inline const char *__trace_landlock_print_layers(\n /* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */\n #define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name }\n \n+#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \\\n+\t{ LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, \"change_topology\" }\n+\n /**\n * DOC: Landlock trace events\n *\n@@ -192,22 +206,22 @@ static inline const char *__trace_landlock_print_layers(\n * Decision context\n * ~~~~~~~~~~~~~~~~\n *\n- * A denial event, together with the lifecycle events, exposes the full\n- * set of inputs the verdict consumed, so a consumer that tracked domain\n- * creation (landlock_create_ruleset, landlock_create_domain) can verify\n- * or reproduce the Landlock decision rather than merely observe it\n- * happened. In who/what/why terms: who is the denying domain (the domain\n- * field, always the subject that enforced the policy, never the current\n- * task), what is the operation and its object, and why is every other\n- * input the verdict weighed.\n+ * A denial event identifies the domain whose policy denied the request, the\n+ * Landlock operation and policy object that were checked, and the blocker or\n+ * domain relationship responsible for the denial. When tracing starts with\n+ * sandbox construction, ruleset and domain events provide the policy history\n+ * needed to interpret these identifiers. The denying domain is the subject\n+ * that enforced the policy, not necessarily current. Generic tracepoints can\n+ * provide additional operational context.\n *\n * Lifecycle consistency\n * ~~~~~~~~~~~~~~~~~~~~~~\n *\n- * Lifecycle events are balanced: a creation event always has a matching\n- * deallocation event and vice versa, so an eBPF program can model object\n- * lifetimes from the trace stream without reconciliation logic. A creation\n- * event fires while the object is still private to the calling thread\n+ * Lifecycle emission is balanced: a creation event always has a matching\n+ * deallocation event and vice versa. A consumer that observes an object's\n+ * complete lifetime can model it from this pair; one that attaches late or\n+ * loses exported records must reconcile incomplete state. A creation event\n+ * fires while the object is still private to the calling thread\n * (landlock_create_ruleset fires before the ruleset's file descriptor is\n * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd\n * installation later fails and the ruleset is freed, free_ruleset still\n@@ -243,8 +257,8 @@ static inline const char *__trace_landlock_print_layers(\n * Field encoding\n * ~~~~~~~~~~~~~~\n *\n- * Fields that mirror the Landlock UAPI use the same C types and endianness\n- * (e.g. network ports are __u64 in host endianness, like\n+ * Fields that mirror the Landlock UAPI preserve their widths and endianness\n+ * (e.g. network ports are u64 in host endianness, like\n * landlock_net_port_attr.port). Per-event details, such as where a value\n * is byte-swapped, live in the field's own kdoc.\n *\n@@ -281,6 +295,16 @@ static inline const char *__trace_landlock_print_layers(\n * the two parties without kernel-internal state. The ID is a scalar\n * snapshot, not a live domain pointer that could dangle: an optional\n * relational referent is a scalar (0 sentinel), not a nullable pointer.\n+ * Nonzero IDs are unique within one boot. For ptrace, same_exec instead\n+ * describes the tracer, even for\n+ * PTRACE_TRACEME, and may differ from the current task.\n+ *\n+ * Blocker fields\n+ * ~~~~~~~~~~~~~~\n+ *\n+ * The filesystem and network blocker arguments identify the request type\n+ * and carry its final missing access subset when applicable. The type\n+ * determines how to interpret the access value.\n */\n \n /*\n@@ -319,8 +343,8 @@ TRACE_EVENT(landlock_create_ruleset,\n \tTP_ARGS(ruleset),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\truleset_id\t)\n-\t\t__field(\t__u32,\t\truleset_version\t)\n+\t\t__field(\tu64,\t\truleset_id\t)\n+\t\t__field(\tu32,\t\truleset_version\t)\n \t\t__field(\taccess_mask_t,\thandled_fs\t)\n \t\t__field(\taccess_mask_t,\thandled_net\t)\n \t\t__field(\taccess_mask_t,\tscoped\t\t)\n@@ -359,8 +383,8 @@ TRACE_EVENT(landlock_free_ruleset,\n \tTP_ARGS(ruleset),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\truleset_id\t)\n-\t\t__field(\t__u32,\t\truleset_version\t)\n+\t\t__field(\tu64,\t\truleset_id\t)\n+\t\t__field(\tu32,\t\truleset_version\t)\n \t),\n \n \tTP_fast_assign(\n@@ -373,12 +397,14 @@ TRACE_EVENT(landlock_free_ruleset,\n );\n \n /**\n- * landlock_add_rule_fs - Filesystem rule added to a ruleset\n+ * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset\n *\n * @ruleset: Source ruleset (never NULL).\n- * @access_rights: Effective access mask stored in the rule, not the raw\n- * sys_landlock_add_rule() argument (unhandled rights\n- * added).\n+ * @flags: Complete validated landlock_add_rule_flags value supplied by this\n+ * successful call, not the rule's accumulated quiet state.\n+ * @access_rights: Canonical per-call access mask passed to\n+ * landlock_insert_rule() after normalization, not the raw\n+ * sys_landlock_add_rule() argument or accumulated rule.\n * @path: Filesystem path for the rule (never NULL).\n * @pathname: Resolved absolute path string (never NULL; error placeholder\n * on resolution failure).\n@@ -387,17 +413,17 @@ TRACE_EVENT(landlock_free_ruleset,\n * the reported ruleset is a stable snapshot that no concurrent writer can\n * change.\n */\n-TRACE_EVENT(landlock_add_rule_fs,\n+TRACE_EVENT(landlock_add_rule_path_beneath,\n \n-\tTP_PROTO(const struct landlock_ruleset *ruleset,\n-\t\t access_mask_t access_rights, const struct path *path,\n+\tTP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,\n+\t\t u64 access_rights, const struct path *path,\n \t\t const char *pathname),\n \n-\tTP_ARGS(ruleset, access_rights, path, pathname),\n+\tTP_ARGS(ruleset, flags, access_rights, path, pathname),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\truleset_id\t)\n-\t\t__field(\t__u32,\t\truleset_version\t)\n+\t\t__field(\tu64,\t\truleset_id\t)\n+\t\t__field(\tu32,\t\truleset_version\t)\n \t\t__field(\taccess_mask_t,\taccess_rights\t)\n \t\t__field(\tdev_t,\t\tdev\t\t)\n \t\t__field(\tino_t,\t\tino\t\t)\n@@ -427,31 +453,33 @@ TRACE_EVENT(landlock_add_rule_fs,\n );\n \n /**\n- * landlock_add_rule_net - Network port rule added to a ruleset\n+ * landlock_add_rule_net_port - Network-port rule added to a ruleset\n *\n * @ruleset: Source ruleset (never NULL).\n- * @access_rights: Effective access mask stored in the rule, not the raw\n- * sys_landlock_add_rule() argument (unhandled rights\n- * added).\n- * @port: Network port, the landlock_net_port_attr.port UAPI value\n- * forwarded directly.\n+ * @flags: Complete validated landlock_add_rule_flags value supplied by this\n+ * successful call, not the rule's accumulated quiet state.\n+ * @access_rights: Canonical per-call access mask passed to\n+ * landlock_insert_rule() after normalization, not the raw\n+ * sys_landlock_add_rule() argument or accumulated rule.\n+ * @port: Network port in host endianness, forwarded directly from\n+ * \u0026landlock_net_port_attr.port.\n *\n * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so\n * the reported ruleset is a stable snapshot that no concurrent writer can\n * change.\n */\n-TRACE_EVENT(landlock_add_rule_net,\n+TRACE_EVENT(landlock_add_rule_net_port,\n \n-\tTP_PROTO(const struct landlock_ruleset *ruleset,\n-\t\t access_mask_t access_rights, __u64 port),\n+\tTP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,\n+\t\t u64 access_rights, u64 port),\n \n-\tTP_ARGS(ruleset, access_rights, port),\n+\tTP_ARGS(ruleset, flags, access_rights, port),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\truleset_id\t)\n-\t\t__field(\t__u32,\t\truleset_version\t)\n+\t\t__field(\tu64,\t\truleset_id\t)\n+\t\t__field(\tu32,\t\truleset_version\t)\n \t\t__field(\taccess_mask_t,\taccess_rights\t)\n-\t\t__field(\t__u64,\t\tport\t\t)\n+\t\t__field(\tu64,\t\tport\t\t)\n \t),\n \n \tTP_fast_assign(\n@@ -495,10 +523,10 @@ TRACE_EVENT(landlock_create_domain,\n \tTP_ARGS(domain, ruleset),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n-\t\t__field(\t__u64,\t\tparent_id\t)\n-\t\t__field(\t__u64,\t\truleset_id\t)\n-\t\t__field(\t__u32,\t\truleset_version\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tparent_id\t)\n+\t\t__field(\tu64,\t\truleset_id\t)\n+\t\t__field(\tu32,\t\truleset_version\t)\n \t),\n \n \tTP_fast_assign(\n@@ -557,7 +585,7 @@ TRACE_EVENT(landlock_enforce_domain,\n \tTP_ARGS(domain, complete, process_wide, no_new_privs),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tcomplete\t)\n \t\t__field(\tbool,\t\tprocess_wide\t)\n \t\t__field(\tbool,\t\tno_new_privs\t)\n@@ -595,8 +623,8 @@ TRACE_EVENT(landlock_free_domain,\n \tTP_ARGS(hierarchy),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n-\t\t__field(\t__u64,\t\tdenials\t\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdenials\t\t)\n \t),\n \n \tTP_fast_assign(\n@@ -609,7 +637,7 @@ TRACE_EVENT(landlock_free_domain,\n );\n \n /**\n- * landlock_check_rule_fs - Filesystem rule evaluated during access check\n+ * landlock_check_rule_inode - Inode rule evaluated during access check\n *\n * @domain: Enforcing domain (never NULL).\n * @rule: Matching rule with per-layer access masks (never NULL).\n@@ -622,16 +650,16 @@ TRACE_EVENT(landlock_free_domain,\n * domain layer. See Documentation/trace/events-landlock.rst for how to\n * interpret it.\n */\n-TRACE_EVENT(landlock_check_rule_fs,\n+TRACE_EVENT(landlock_check_rule_inode,\n \n \tTP_PROTO(const struct landlock_domain *domain,\n \t\t const struct landlock_rule *rule,\n-\t\t access_mask_t access_request, const struct dentry *dentry),\n+\t\t u64 access_request, const struct dentry *dentry),\n \n \tTP_ARGS(domain, rule, access_request, dentry),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\taccess_mask_t,\taccess_request\t)\n \t\t__field(\tdev_t,\t\tdev\t\t)\n \t\t__field(\tino_t,\t\tino\t\t)\n@@ -648,7 +676,8 @@ TRACE_EVENT(landlock_check_rule_fs,\n \t\t__trace_landlock_fill_layers(__get_dynamic_array(grants),\n \t\t\t\t\t __get_dynamic_array_len(grants) /\n \t\t\t\t\t\t sizeof(access_mask_t),\n-\t\t\t\t\t rule, access_request);\n+\t\t\t\t\t rule,\n+\t\t\t\t\t (access_mask_t)access_request);\n \t),\n \n \tTP_printk(\"domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s\",\n@@ -659,7 +688,7 @@ TRACE_EVENT(landlock_check_rule_fs,\n );\n \n /**\n- * landlock_check_rule_net - Network port rule evaluated during access check\n+ * landlock_check_rule_net_port - Network port rule evaluated\n *\n * @domain: Enforcing domain (never NULL).\n * @rule: Matching rule with per-layer access masks (never NULL).\n@@ -671,18 +700,18 @@ TRACE_EVENT(landlock_check_rule_fs,\n * layer. See Documentation/trace/events-landlock.rst for how to\n * interpret it.\n */\n-TRACE_EVENT(landlock_check_rule_net,\n+TRACE_EVENT(landlock_check_rule_net_port,\n \n \tTP_PROTO(const struct landlock_domain *domain,\n \t\t const struct landlock_rule *rule,\n-\t\t access_mask_t access_request, __u64 port),\n+\t\t u64 access_request, u64 port),\n \n \tTP_ARGS(domain, rule, access_request, port),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\taccess_mask_t,\taccess_request\t)\n-\t\t__field(\t__u64,\t\tport\t\t)\n+\t\t__field(\tu64,\t\tport\t\t)\n \t\t__dynamic_array(access_mask_t,\tgrants,\n \t\t\t\tdomain-\u003enum_layers)\n \t),\n@@ -695,7 +724,8 @@ TRACE_EVENT(landlock_check_rule_net,\n \t\t__trace_landlock_fill_layers(__get_dynamic_array(grants),\n \t\t\t\t\t __get_dynamic_array_len(grants) /\n \t\t\t\t\t\t sizeof(access_mask_t),\n-\t\t\t\t\t rule, access_request);\n+\t\t\t\t\t rule,\n+\t\t\t\t\t (access_mask_t)access_request);\n \t),\n \n \tTP_printk(\"domain=%llx access_request=%s port=%llu grants=%s\",\n@@ -712,8 +742,7 @@ TRACE_EVENT(landlock_check_rule_net,\n * domain field.\n * @same_exec: Whether the current task entered the denying domain itself.\n * @logged: The domain's audit-logging decision for this denial.\n- * @blockers: Access mask that was blocked (zero for a mount-topology\n- * change, whose only blocker is the operation itself).\n+ * @blockers: Request type and final missing access subset (never NULL).\n * @path: Filesystem path that was denied (never NULL).\n * @pathname: Resolved path string (never NULL; an error placeholder on\n * resolution failure).\n@@ -723,16 +752,17 @@ TRACE_EVENT(landlock_check_rule_net,\n TRACE_EVENT(landlock_deny_access_fs,\n \n \tTP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,\n-\t\t bool logged, access_mask_t blockers, const struct path *path,\n-\t\t const char *pathname),\n+\t\t bool logged, const struct landlock_blockers *blockers,\n+\t\t const struct path *path, const char *pathname),\n \n \tTP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tsame_exec\t)\n \t\t__field(\tbool,\t\tlogged\t\t)\n-\t\t__field(\taccess_mask_t,\tblockers\t)\n+\t\t__field(\tenum landlock_request_type, blockers_type\t)\n+\t\t__field(\taccess_mask_t,\tblockers_access\t)\n \t\t__field(\tdev_t,\t\tdev\t\t)\n \t\t__field(\tino_t,\t\tino\t\t)\n \t\t__string(\tpathname,\tpathname\t)\n@@ -744,7 +774,8 @@ TRACE_EVENT(landlock_deny_access_fs,\n \t\t__entry-\u003edomain_id\t= hierarchy-\u003eid;\n \t\t__entry-\u003esame_exec\t= same_exec;\n \t\t__entry-\u003elogged\t\t= logged;\n-\t\t__entry-\u003eblockers\t= blockers;\n+\t\t__entry-\u003eblockers_type\t= blockers-\u003etype;\n+\t\t__entry-\u003eblockers_access = blockers-\u003eaccess;\n \t\t__entry-\u003edev\t\t= path-\u003edentry-\u003ed_sb-\u003es_dev;\n \t\t/*\n \t\t * A negative dentry has no backing inode, so mirror the\n@@ -756,12 +787,20 @@ TRACE_EVENT(landlock_deny_access_fs,\n \n \tTP_printk(\"domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s\",\n \t\t__entry-\u003edomain_id, __entry-\u003esame_exec, __entry-\u003elogged,\n-\t\t__print_flags(__entry-\u003eblockers, \"|\", _LANDLOCK_ACCESS_FS_NAMES),\n+\t\t__entry-\u003eblockers_type == LANDLOCK_REQUEST_FS_ACCESS ?\n+\t\t\t__print_flags(__entry-\u003eblockers_access, \"|\", _LANDLOCK_ACCESS_FS_NAMES) :\n+\t\t\t__print_symbolic(__entry-\u003eblockers_type,\n+\t\t\t\t\t _LANDLOCK_FS_BLOCKER_TYPE_NAMES),\n \t\tMAJOR(__entry-\u003edev), MINOR(__entry-\u003edev), __entry-\u003eino,\n \t\t__trace_print_untrusted_str(p, __get_str(pathname),\n \t\t\t\t\t __get_dynamic_array_len(pathname) - 1))\n );\n \n+static_assert(offsetof(struct sockaddr_in, sin_port) ==\n+\t offsetof(struct sockaddr_in6, sin6_port));\n+static_assert(sizeof_field(struct sockaddr_in, sin_port) ==\n+\t sizeof_field(struct sockaddr_in6, sin6_port));\n+\n /**\n * landlock_deny_access_net - Network access denied\n *\n@@ -769,54 +808,69 @@ TRACE_EVENT(landlock_deny_access_fs,\n * domain field.\n * @same_exec: Whether the current task entered the denying domain itself.\n * @logged: The domain's audit-logging decision for this denial.\n- * @blockers: Access mask that was blocked.\n- * @sk: Socket object (never NULL), read without a socket lock, so its\n- * fields are a best-effort snapshot. The denied endpoint is not\n- * available: the hook runs before :manpage:`bind(2)` /\n- * :manpage:`connect(2)` sets the socket addresses.\n- * @sport: Source port in host endianness, set for bind denials (zero for\n- * an autobind/ephemeral port); zero for connect and send denials.\n- * @dport: Destination port in host endianness, set for connect and send\n- * denials; zero for bind denials, and also zero for a UDP send to\n- * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a\n- * real destination port 0). The bind-vs-connect direction is\n- * given by @blockers, not by which port is set.\n- *\n- * Emitted when a Landlock domain denies a network operation.\n- *\n- * The port fields are converted from the socket's network byte order to\n- * host endianness before emitting.\n+ * @blockers: Request type and final missing access subset (never NULL).\n+ * @sk: Socket object (never NULL), read without a socket lock, so its fields\n+ * are a best-effort snapshot.\n+ * @socket_family: Socket-family snapshot used by the verdict.\n+ * @address: Authoritative address checked by the verdict (never NULL).\n+ * The producer copies @addrlen bytes from the checked address and\n+ * zeroes the remaining storage before emission. The\n+ * \u0026sockaddr_in.sin_port or \u0026sockaddr_in6.sin6_port member, when\n+ * present, remains in network endianness.\n+ * @addrlen: Validated signed length of @address.\n+ *\n+ * Emitted when a Landlock domain denies a network operation. The blocker\n+ * identifies whether the address is a bind or connect/send policy object.\n+ * The flattened port field is converted from the checked address to host\n+ * endianness, or is -1 when no port was checked. Zero is a valid checked\n+ * port.\n */\n TRACE_EVENT(landlock_deny_access_net,\n \n \tTP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,\n-\t\t bool logged, access_mask_t blockers, const struct sock *sk,\n-\t\t __u64 sport, __u64 dport),\n+\t\t bool logged, const struct landlock_blockers *blockers,\n+\t\t const struct sock *sk, u16 socket_family,\n+\t\t const struct sockaddr_storage *address, int addrlen),\n \n-\tTP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport),\n+\tTP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family,\n+\t\taddress, addrlen),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tsame_exec\t)\n \t\t__field(\tbool,\t\tlogged\t\t)\n-\t\t__field(\taccess_mask_t,\tblockers\t)\n-\t\t__field(\t__u64,\t\tsport\t\t)\n-\t\t__field(\t__u64,\t\tdport\t\t)\n+\t\t__field(\tenum landlock_request_type, blockers_type\t)\n+\t\t__field(\taccess_mask_t,\tblockers_access\t)\n+\t\t__field(\ts64,\t\tport\t\t)\n \t),\n \n \tTP_fast_assign(\n+\t\tconst struct sockaddr *const addr =\n+\t\t\t(const struct sockaddr *)address;\n+\t\tconst bool has_port =\n+\t\t\taddrlen \u003e= (int)offsetofend(struct sockaddr_in, sin_port) \u0026\u0026\n+\t\t\t(addr-\u003esa_family == AF_INET ||\n+\t\t\t addr-\u003esa_family == AF_INET6 ||\n+\t\t\t (addr-\u003esa_family == AF_UNSPEC \u0026\u0026\n+\t\t\t socket_family == AF_INET));\n+\n \t\t__entry-\u003edomain_id\t= hierarchy-\u003eid;\n \t\t__entry-\u003esame_exec\t= same_exec;\n \t\t__entry-\u003elogged\t\t= logged;\n-\t\t__entry-\u003eblockers\t= blockers;\n-\t\t__entry-\u003esport\t\t= sport;\n-\t\t__entry-\u003edport\t\t= dport;\n+\t\t__entry-\u003eblockers_type\t= blockers-\u003etype;\n+\t\t__entry-\u003eblockers_access = blockers-\u003eaccess;\n+\t\t__entry-\u003eport\t\t=\n+\t\t\thas_port ?\n+\t\t\t\tntohs(((const struct sockaddr_in *)addr)-\u003esin_port) :\n+\t\t\t\t-1;\n \t),\n \n-\tTP_printk(\"domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu\",\n-\t\t__entry-\u003edomain_id, __entry-\u003esame_exec, __entry-\u003elogged,\n-\t\t__print_flags(__entry-\u003eblockers, \"|\", _LANDLOCK_ACCESS_NET_NAMES),\n-\t\t__entry-\u003esport, __entry-\u003edport)\n+\tTP_printk(\"domain=%llx same_exec=%d logged=%d blockers=%s port=%lld\",\n+\t\t __entry-\u003edomain_id, __entry-\u003esame_exec, __entry-\u003elogged,\n+\t\t __entry-\u003eblockers_type == LANDLOCK_REQUEST_NET_ACCESS ?\n+\t\t\t __print_flags(__entry-\u003eblockers_access, \"|\", _LANDLOCK_ACCESS_NET_NAMES) :\n+\t\t\t \"unknown\",\n+\t\t __entry-\u003eport)\n );\n \n /**\n@@ -824,12 +878,14 @@ TRACE_EVENT(landlock_deny_access_net,\n *\n * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the\n * domain field.\n- * @same_exec: Whether the current task entered the denying domain itself.\n+ * @same_exec: Whether the tracer entered the denying domain itself.\n * @logged: The domain's audit-logging decision for this denial.\n * @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee\n * is unsandboxed.\n * @tracee: The target task ptrace acted on (never NULL). tracee_pid is\n * the init-namespace TGID (like audit's opid).\n+ * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME\n+ * this is the parent, not the syscall caller.\n *\n * Emitted when a Landlock domain denies a ptrace operation.\n */\n@@ -837,15 +893,16 @@ TRACE_EVENT(landlock_deny_ptrace,\n \n \tTP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,\n \t\t bool logged, u64 tracee_domain_id,\n-\t\t const struct task_struct *tracee),\n+\t\t const struct task_struct *tracee,\n+\t\t const struct task_struct *tracer),\n \n-\tTP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee),\n+\tTP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tsame_exec\t)\n \t\t__field(\tbool,\t\tlogged\t\t)\n-\t\t__field(\t__u64,\t\ttracee_domain_id)\n+\t\t__field(\tu64,\t\ttracee_domain_id)\n \t\t__field(\tpid_t,\t\ttracee_pid\t)\n \t\t__string(\ttracee_comm,\ttracee-\u003ecomm\t)\n \t),\n@@ -872,12 +929,14 @@ TRACE_EVENT(landlock_deny_ptrace,\n *\n * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the\n * domain field.\n- * @same_exec: Whether the current task entered the denying domain itself.\n+ * @same_exec: Whether the policy subject entered the denying domain itself.\n * @logged: The domain's audit-logging decision for this denial.\n * @target_domain_id: The target's Landlock domain ID, or 0 if the target\n * is unsandboxed.\n * @target: The task the signal was aimed at (never NULL). target_pid is\n * the init-namespace TGID (like audit's opid).\n+ * @signal: The signal selected by the denied check. Zero is a permission\n+ * probe, not an absent value.\n *\n * Emitted when a Landlock domain denies signal delivery to a scoped-out\n * target.\n@@ -886,15 +945,15 @@ TRACE_EVENT(landlock_deny_scope_signal,\n \n \tTP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,\n \t\t bool logged, u64 target_domain_id,\n-\t\t const struct task_struct *target),\n+\t\t const struct task_struct *target, int signal),\n \n-\tTP_ARGS(hierarchy, same_exec, logged, target_domain_id, target),\n+\tTP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tsame_exec\t)\n \t\t__field(\tbool,\t\tlogged\t\t)\n-\t\t__field(\t__u64,\t\ttarget_domain_id)\n+\t\t__field(\tu64,\t\ttarget_domain_id)\n \t\t__field(\tpid_t,\t\ttarget_pid\t)\n \t\t__string(\ttarget_comm,\ttarget-\u003ecomm\t)\n \t),\n@@ -940,10 +999,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n \tTP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer),\n \n \tTP_STRUCT__entry(\n-\t\t__field(\t__u64,\t\tdomain_id\t)\n+\t\t__field(\tu64,\t\tdomain_id\t)\n \t\t__field(\tbool,\t\tsame_exec\t)\n \t\t__field(\tbool,\t\tlogged\t\t)\n-\t\t__field(\t__u64,\t\tpeer_domain_id\t)\n+\t\t__field(\tu64,\t\tpeer_domain_id\t)\n \t\t__field(\tpid_t,\t\tpeer_pid\t)\n \t\t/*\n \t\t * Abstract socket names are untrusted binary data from\n@@ -973,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n \t\t__entry-\u003elogged\t\t= logged;\n \t\t__entry-\u003epeer_domain_id\t= peer_domain_id;\n \t\t/*\n-\t\t * Best-effort (0 for a datagram peer). sk_peer_pid is\n-\t\t * canonically guarded by sk-\u003esk_peer_lock, but the target\n-\t\t * peer's peercred is set once and not updated concurrently in\n-\t\t * these hooks, so this READ_ONCE() is safe; sun_path is the\n-\t\t * reliable identifier.\n+\t\t * Best-effort (0 for a datagram peer). The caller holds the\n+\t\t * peer's AF_UNIX state lock, serializing published peercred\n+\t\t * updates. The peer socket keeps a reference to sk_peer_pid\n+\t\t * through pid_nr(); sun_path is the reliable identifier.\n \t\t */\n \t\tpeer_pid\t\t= READ_ONCE(peer-\u003esk_peer_pid);\n \t\t__entry-\u003epeer_pid\t= peer_pid ? pid_nr(peer_pid) : 0;\n@@ -991,6 +1049,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n \t\t\t\t\t __get_dynamic_array_len(sun_path) - 1))\n );\n \n+#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES\n #undef _LANDLOCK_NAME_ENTRY\n \n #endif /* _TRACE_LANDLOCK_H */\ndiff --git a/security/landlock/fs.c b/security/landlock/fs.c\nindex 330a1871bf94a..cab43892ec2f0 100644\n--- a/security/landlock/fs.c\n+++ b/security/landlock/fs.c\n@@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,\n \t * held for BTF consistency (enforced by lockdep_assert_held in\n \t * TP_fast_assign).\n \t */\n-\tif (!err \u0026\u0026 trace_landlock_add_rule_fs_enabled()) {\n+\tif (!err \u0026\u0026 trace_landlock_add_rule_path_beneath_enabled()) {\n \t\tchar *buffer __free(__putname) = __getname();\n \t\tconst char *pathname =\n \t\t\tbuffer ? resolve_path_for_trace(path, buffer) :\n \t\t\t\t \"\u003cno_mem\u003e\";\n \n-\t\ttrace_landlock_add_rule_fs(ruleset, access_rights, path,\n-\t\t\t\t\t pathname);\n+\t\ttrace_landlock_add_rule_path_beneath(\n+\t\t\truleset, flags, access_rights, path, pathname);\n \t}\n \tmutex_unlock(\u0026ruleset-\u003elock);\n \n@@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain,\n \n \tret = landlock_unmask_layers(domain, id, masks, \u0026rule);\n \tif (rule)\n-\t\ttrace_landlock_check_rule_fs(domain, rule, access_request,\n-\t\t\t\t\t dentry);\n+\t\ttrace_landlock_check_rule_inode(domain, rule, access_request,\n+\t\t\t\t\t\tdentry);\n \treturn ret;\n }\n \ndiff --git a/security/landlock/log.h b/security/landlock/log.h\nindex e0a6e44f3ddd3..faa30e26e42a3 100644\n--- a/security/landlock/log.h\n+++ b/security/landlock/log.h\n@@ -15,6 +15,8 @@\n \n struct landlock_cred_security;\n struct landlock_hierarchy;\n+struct sockaddr;\n+struct task_struct;\n \n enum landlock_request_type {\n \tLANDLOCK_REQUEST_PTRACE = 1,\n@@ -25,6 +27,31 @@ enum landlock_request_type {\n \tLANDLOCK_REQUEST_SCOPE_SIGNAL,\n };\n \n+struct landlock_blockers {\n+\taccess_mask_t access;\n+\tenum landlock_request_type type;\n+};\n+\n+#ifdef CONFIG_TRACEPOINTS\n+\n+struct landlock_net_trace {\n+\tconst struct sockaddr *address;\n+\tint addrlen;\n+\tu16 socket_family;\n+};\n+\n+struct landlock_ptrace_trace {\n+\tu64 tracee_domain_id;\n+\tconst struct task_struct *tracer;\n+};\n+\n+struct landlock_signal_trace {\n+\tu64 target_domain_id;\n+\tint signal;\n+};\n+\n+#endif /* CONFIG_TRACEPOINTS */\n+\n /*\n * We should be careful to only use a variable of this type for\n * landlock_log_denial(). This way, the compiler can remove it entirely if\n@@ -52,13 +79,24 @@ struct landlock_request {\n \tdeny_masks_t deny_masks;\n \toptional_access_t quiet_optional_accesses;\n \n-\t/*\n-\t * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if\n-\t * that party is unsandboxed. An ID, not a pointer: the other task can\n-\t * replace its credential and free the domain it referenced. Trace path\n-\t * only; audit ignores it.\n-\t */\n-\tu64 other_domain_id;\n+#ifdef CONFIG_TRACEPOINTS\n+\tunion {\n+\t\t/*\n+\t\t * Other-party domain ID for an abstract UNIX socket scope\n+\t\t * denial, or 0 if that party is unsandboxed. Store an ID, not\n+\t\t * a pointer: the other task can replace its credential and free\n+\t\t * the domain it referenced.\n+\t\t */\n+\t\tu64 other_domain_id;\n+\n+\t\t/* Synchronous context for a network denial. */\n+\t\tconst struct landlock_net_trace *trace_net;\n+\t\t/* Synchronous context for a ptrace denial. */\n+\t\tconst struct landlock_ptrace_trace *trace_ptrace;\n+\t\t/* Synchronous context for a signal denial. */\n+\t\tconst struct landlock_signal_trace *trace_signal;\n+\t};\n+#endif /* CONFIG_TRACEPOINTS */\n };\n \n #ifdef CONFIG_SECURITY_LANDLOCK_LOG\ndiff --git a/security/landlock/net.c b/security/landlock/net.c\nindex 8f2aaac54b334..6fe0dbde3b787 100644\n--- a/security/landlock/net.c\n+++ b/security/landlock/net.c\n@@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,\n \t * TP_fast_assign).\n \t */\n \tif (!err)\n-\t\ttrace_landlock_add_rule_net(ruleset, access_rights, port);\n+\t\ttrace_landlock_add_rule_net_port(ruleset, flags, access_rights,\n+\t\t\t\t\t\t port);\n \tmutex_unlock(\u0026ruleset-\u003elock);\n \n \treturn err;\n@@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain,\n \n \tret = landlock_unmask_layers(domain, id, masks, \u0026rule);\n \tif (rule)\n-\t\ttrace_landlock_check_rule_net(\n+\t\ttrace_landlock_check_rule_net_port(\n \t\t\tdomain, rule, access_request,\n \t\t\tntohs((__force __be16)id.key.data));\n \treturn ret;\n@@ -92,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock,\n \t\treturn 0;\n \n \t/* Checks for minimal header length to safely read sa_family. */\n-\tif (addrlen \u003c offsetofend(typeof(*address), sa_family))\n+\tif (addrlen \u003c (int)offsetofend(typeof(*address), sa_family))\n \t\treturn -EINVAL;\n \n \t/*\n@@ -144,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock,\n \t\t\t\t\t\t.audit.u.net = \u0026audit_net,\n \t\t\t\t\t\t.access = access_request,\n \t\t\t\t\t\t.layer_masks = \u0026layer_masks,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t\t\t\t\t.trace_net =\n+\t\t\t\t\t\t\t\u0026(struct landlock_net_trace){\n+\t\t\t\t\t\t\t\t.address =\n+\t\t\t\t\t\t\t\t\taddress,\n+\t\t\t\t\t\t\t\t.addrlen =\n+\t\t\t\t\t\t\t\t\taddrlen,\n+\t\t\t\t\t\t\t\t.socket_family =\n+\t\t\t\t\t\t\t\t\tsock_family,\n+\t\t\t\t\t\t\t},\n+#endif /* CONFIG_TRACEPOINTS */\n \t\t\t\t\t});\n \t\t\t\treturn -EACCES;\n \t\t\t}\n@@ -275,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock,\n \n \taudit_net.family = address-\u003esa_family;\n \taudit_net.sk = sock-\u003esk;\n-\tlandlock_log_denial(subject,\n-\t\t\t \u0026(struct landlock_request){\n-\t\t\t\t .type = LANDLOCK_REQUEST_NET_ACCESS,\n-\t\t\t\t .audit.type = LSM_AUDIT_DATA_NET,\n-\t\t\t\t .audit.u.net = \u0026audit_net,\n-\t\t\t\t .access = access_request,\n-\t\t\t\t .layer_masks = \u0026layer_masks,\n-\t\t\t });\n+\tlandlock_log_denial(\n+\t\tsubject, \u0026(struct landlock_request){\n+\t\t\t\t .type = LANDLOCK_REQUEST_NET_ACCESS,\n+\t\t\t\t .audit.type = LSM_AUDIT_DATA_NET,\n+\t\t\t\t .audit.u.net = \u0026audit_net,\n+\t\t\t\t .access = access_request,\n+\t\t\t\t .layer_masks = \u0026layer_masks,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t\t\t .trace_net =\n+\t\t\t\t\t \u0026(struct landlock_net_trace){\n+\t\t\t\t\t\t .address = address,\n+\t\t\t\t\t\t .addrlen = addrlen,\n+\t\t\t\t\t\t .socket_family = sock_family,\n+\t\t\t\t\t },\n+#endif /* CONFIG_TRACEPOINTS */\n+\t\t\t });\n \treturn -EACCES;\n }\n \ndiff --git a/security/landlock/task.c b/security/landlock/task.c\nindex 4491ce31ae04a..d9eae86fc5529 100644\n--- a/security/landlock/task.c\n+++ b/security/landlock/task.c\n@@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child,\n \t\t\t\t const unsigned int mode)\n {\n \tconst struct landlock_cred_security *parent_subject;\n+#ifdef CONFIG_TRACEPOINTS\n \tu64 tracee_domain_id = 0;\n+#endif /* CONFIG_TRACEPOINTS */\n \tint err;\n \n \t/* Quick return for non-landlocked tasks. */\n@@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child,\n \t\tconst struct landlock_domain *const child_dom =\n \t\t\tlandlock_get_task_domain(child);\n \t\terr = domain_ptrace(parent_subject-\u003edomain, child_dom);\n-#ifdef CONFIG_SECURITY_LANDLOCK_LOG\n+#ifdef CONFIG_TRACEPOINTS\n \t\tif (child_dom)\n \t\t\ttracee_domain_id = child_dom-\u003ehierarchy-\u003eid;\n-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n+#endif /* CONFIG_TRACEPOINTS */\n \t}\n \n \tif (!err)\n@@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child,\n \t\t\t\t.u.tsk = child,\n \t\t\t},\n \t\t\t.layer_plus_one = parent_subject-\u003edomain-\u003enum_layers,\n-\t\t\t.other_domain_id = tracee_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t\t.trace_ptrace = \u0026(struct landlock_ptrace_trace) {\n+\t\t\t\t.tracee_domain_id = tracee_domain_id,\n+\t\t\t\t.tracer = current,\n+\t\t\t},\n+#endif /* CONFIG_TRACEPOINTS */\n \t\t});\n \n \treturn err;\n@@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)\n {\n \tconst struct landlock_cred_security *parent_subject;\n \tconst struct landlock_domain *child_dom;\n-\tu64 tracee_domain_id = 0;\n \tint err;\n \n \tchild_dom = landlock_get_current_domain();\n@@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)\n \tif (!err)\n \t\treturn 0;\n \n-#ifdef CONFIG_SECURITY_LANDLOCK_LOG\n-\t/* The tracee is the current task; its domain is stable here. */\n-\tif (child_dom)\n-\t\ttracee_domain_id = child_dom-\u003ehierarchy-\u003eid;\n-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n-\n \t/*\n \t * For the ptrace_traceme case, we log the domain which is the cause of\n \t * the denial, which means the parent domain instead of the current\n@@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent)\n \t\t\t.u.tsk = current,\n \t\t},\n \t\t.layer_plus_one = parent_subject-\u003edomain-\u003enum_layers,\n-\t\t.other_domain_id = tracee_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t.trace_ptrace = \u0026(struct landlock_ptrace_trace) {\n+\t\t\t/* The current task's domain is stable here. */\n+\t\t\t.tracee_domain_id = child_dom ? child_dom-\u003ehierarchy-\u003eid : 0,\n+\t\t\t.tracer = parent,\n+\t\t},\n+#endif /* CONFIG_TRACEPOINTS */\n \t});\n \treturn err;\n }\n@@ -250,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client,\n }\n \n static bool sock_is_scoped(struct sock *const other,\n-\t\t\t const struct landlock_domain *const domain,\n-\t\t\t u64 *const peer_domain_id)\n+\t\t\t const struct landlock_domain *const domain)\n {\n \tconst struct landlock_domain *dom_other;\n \n@@ -269,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other,\n \t\treturn false;\n \n \tdom_other = landlock_cred(other-\u003esk_socket-\u003efile-\u003ef_cred)-\u003edomain;\n-#ifdef CONFIG_SECURITY_LANDLOCK_LOG\n-\t*peer_domain_id = dom_other ? dom_other-\u003ehierarchy-\u003eid : 0;\n-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n \treturn domain_is_scoped(domain, dom_other,\n \t\t\t\tLANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);\n }\n \n+#ifdef CONFIG_TRACEPOINTS\n+\n+static u64 get_socket_domain_id(const struct sock *const other)\n+{\n+\tconst struct landlock_domain *domain;\n+\n+\tlockdep_assert_held(\u0026unix_sk(other)-\u003elock);\n+\tdomain = landlock_cred(other-\u003esk_socket-\u003efile-\u003ef_cred)-\u003edomain;\n+\treturn domain ? domain-\u003ehierarchy-\u003eid : 0;\n+}\n+\n+#endif /* CONFIG_TRACEPOINTS */\n+\n static bool is_abstract_socket(struct sock *const sock)\n {\n \tstruct unix_address *addr = unix_sk(sock)-\u003eaddr;\n@@ -299,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock,\n \t\t\t\t struct sock *const newsk)\n {\n \tsize_t handle_layer;\n-\tu64 peer_domain_id = 0;\n \tconst struct landlock_cred_security *const subject =\n \t\tlandlock_get_applicable_subject(current_cred(), unix_scope,\n \t\t\t\t\t\t\u0026handle_layer);\n@@ -311,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock,\n \tif (!is_abstract_socket(other))\n \t\treturn 0;\n \n-\tif (!sock_is_scoped(other, subject-\u003edomain, \u0026peer_domain_id))\n+\tif (!sock_is_scoped(other, subject-\u003edomain))\n \t\treturn 0;\n \n \tlandlock_log_denial(subject, \u0026(struct landlock_request) {\n@@ -323,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock,\n \t\t\t},\n \t\t},\n \t\t.layer_plus_one = handle_layer + 1,\n-\t\t.other_domain_id = peer_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t.other_domain_id = get_socket_domain_id(other),\n+#endif /* CONFIG_TRACEPOINTS */\n \t});\n \treturn -EPERM;\n }\n@@ -332,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock,\n \t\t\t struct socket *const other)\n {\n \tsize_t handle_layer;\n-\tu64 peer_domain_id = 0;\n \tconst struct landlock_cred_security *const subject =\n \t\tlandlock_get_applicable_subject(current_cred(), unix_scope,\n \t\t\t\t\t\t\u0026handle_layer);\n@@ -350,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock,\n \tif (!is_abstract_socket(other-\u003esk))\n \t\treturn 0;\n \n-\tif (!sock_is_scoped(other-\u003esk, subject-\u003edomain, \u0026peer_domain_id))\n+\tif (!sock_is_scoped(other-\u003esk, subject-\u003edomain))\n \t\treturn 0;\n \n \tlandlock_log_denial(subject, \u0026(struct landlock_request) {\n@@ -362,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock,\n \t\t\t},\n \t\t},\n \t\t.layer_plus_one = handle_layer + 1,\n-\t\t.other_domain_id = peer_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t.other_domain_id = get_socket_domain_id(other-\u003esk),\n+#endif /* CONFIG_TRACEPOINTS */\n \t});\n \treturn -EPERM;\n }\n@@ -377,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p,\n {\n \tbool is_scoped;\n \tsize_t handle_layer;\n+#ifdef CONFIG_TRACEPOINTS\n \tu64 target_domain_id = 0;\n+#endif /* CONFIG_TRACEPOINTS */\n \tconst struct landlock_cred_security *subject;\n \n \tif (!cred) {\n@@ -409,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p,\n \n \t\tis_scoped = domain_is_scoped(subject-\u003edomain, other,\n \t\t\t\t\t signal_scope.scope);\n-#ifdef CONFIG_SECURITY_LANDLOCK_LOG\n+#ifdef CONFIG_TRACEPOINTS\n \t\tif (other)\n \t\t\ttarget_domain_id = other-\u003ehierarchy-\u003eid;\n-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n+#endif /* CONFIG_TRACEPOINTS */\n \t}\n \n \tif (!is_scoped)\n@@ -425,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p,\n \t\t\t.u.tsk = p,\n \t\t},\n \t\t.layer_plus_one = handle_layer + 1,\n-\t\t.other_domain_id = target_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t.trace_signal = \u0026(struct landlock_signal_trace) {\n+\t\t\t.target_domain_id = target_domain_id,\n+\t\t\t.signal = sig,\n+\t\t},\n+#endif /* CONFIG_TRACEPOINTS */\n \t});\n \treturn -EPERM;\n }\n@@ -435,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,\n {\n \tconst struct landlock_cred_security *subject;\n \tbool is_scoped = false;\n+#ifdef CONFIG_TRACEPOINTS\n \tu64 target_domain_id = 0;\n+#endif /* CONFIG_TRACEPOINTS */\n \n \t/* Lock already held by send_sigio() and send_sigurg(). */\n \tlockdep_assert_held(\u0026fown-\u003elock);\n@@ -468,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,\n \n \t\tis_scoped = domain_is_scoped(subject-\u003edomain, other,\n \t\t\t\t\t signal_scope.scope);\n-#ifdef CONFIG_SECURITY_LANDLOCK_LOG\n+#ifdef CONFIG_TRACEPOINTS\n \t\tif (other)\n \t\t\ttarget_domain_id = other-\u003ehierarchy-\u003eid;\n-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n+#endif /* CONFIG_TRACEPOINTS */\n \t}\n \n \tif (!is_scoped)\n@@ -486,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,\n #ifdef CONFIG_SECURITY_LANDLOCK_LOG\n \t\t.layer_plus_one = landlock_file(fown-\u003efile)-\u003efown_layer + 1,\n #endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n-\t\t.other_domain_id = target_domain_id,\n+#ifdef CONFIG_TRACEPOINTS\n+\t\t.trace_signal = \u0026(struct landlock_signal_trace) {\n+\t\t\t.target_domain_id = target_domain_id,\n+\t\t\t.signal = signum ? signum : SIGIO,\n+\t\t},\n+#endif /* CONFIG_TRACEPOINTS */\n \t});\n \treturn -EPERM;\n }\ndiff --git a/security/landlock/trace.c b/security/landlock/trace.c\nindex 8c21e5de6f0de..225dbf37bab09 100644\n--- a/security/landlock/trace.c\n+++ b/security/landlock/trace.c\n@@ -12,6 +12,7 @@\n #include \u003clinux/err.h\u003e\n #include \u003clinux/fs.h\u003e\n #include \u003clinux/lsm_audit.h\u003e\n+#include \u003clinux/socket.h\u003e\n #include \u003cnet/sock.h\u003e\n \n #include \"access.h\"\n@@ -61,8 +62,8 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy\n *\n * @request: Detail of the user space request.\n * @youngest_denied: The youngest hierarchy node that denied the access.\n- * @missing: The set of denied access rights.\n- * @same_exec: Whether the current task is the same executable that called\n+ * @missing: The final missing access subset, when applicable.\n+ * @same_exec: Whether the policy subject is the same executable that called\n * landlock_restrict_self() for the denying domain, as computed\n * by landlock_log_denial().\n * @logged: Whether the domain's policy selects this denial for logging, as\n@@ -83,6 +84,10 @@ void landlock_trace_denial(\n \tcase LANDLOCK_REQUEST_FS_ACCESS:\n \tcase LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:\n \t\tif (trace_landlock_deny_access_fs_enabled()) {\n+\t\t\tconst struct landlock_blockers blockers = {\n+\t\t\t\t.access = missing,\n+\t\t\t\t.type = request-\u003etype,\n+\t\t\t};\n \t\t\tchar *buf __free(__putname) = __getname();\n \t\t\tstruct path dentry_path;\n \t\t\tconst char *pathname;\n@@ -147,29 +152,67 @@ void landlock_trace_denial(\n \n \t\t\ttrace_landlock_deny_access_fs(youngest_denied,\n \t\t\t\t\t\t same_exec, logged,\n-\t\t\t\t\t\t missing, path, pathname);\n+\t\t\t\t\t\t \u0026blockers, path,\n+\t\t\t\t\t\t pathname);\n \t\t}\n \t\tbreak;\n \tcase LANDLOCK_REQUEST_NET_ACCESS:\n-\t\tif (trace_landlock_deny_access_net_enabled())\n+\t\tif (trace_landlock_deny_access_net_enabled()) {\n+\t\t\tconst struct landlock_net_trace *const trace_net =\n+\t\t\t\trequest-\u003etrace_net;\n+\t\t\tconst struct landlock_blockers blockers = {\n+\t\t\t\t.access = missing,\n+\t\t\t\t.type = request-\u003etype,\n+\t\t\t};\n+\t\t\tstruct sockaddr_storage address = {};\n+\n+\t\t\tif (WARN_ON_ONCE(!trace_net || !trace_net-\u003eaddress))\n+\t\t\t\treturn;\n+\n+\t\t\tif (WARN_ON_ONCE(\n+\t\t\t\t trace_net-\u003eaddrlen \u003c\n+\t\t\t\t\t (int)offsetofend(struct sockaddr,\n+\t\t\t\t\t\t\t sa_family) ||\n+\t\t\t\t trace_net-\u003eaddrlen \u003e (int)sizeof(address)))\n+\t\t\t\treturn;\n+\n+\t\t\tmemcpy(\u0026address, trace_net-\u003eaddress,\n+\t\t\t trace_net-\u003eaddrlen);\n \t\t\ttrace_landlock_deny_access_net(\n-\t\t\t\tyoungest_denied, same_exec, logged, missing,\n+\t\t\t\tyoungest_denied, same_exec, logged, \u0026blockers,\n \t\t\t\trequest-\u003eaudit.u.net-\u003esk,\n-\t\t\t\tntohs(request-\u003eaudit.u.net-\u003esport),\n-\t\t\t\tntohs(request-\u003eaudit.u.net-\u003edport));\n+\t\t\t\ttrace_net-\u003esocket_family, \u0026address,\n+\t\t\t\ttrace_net-\u003eaddrlen);\n+\t\t}\n \t\tbreak;\n \tcase LANDLOCK_REQUEST_PTRACE:\n-\t\tif (trace_landlock_deny_ptrace_enabled())\n-\t\t\ttrace_landlock_deny_ptrace(youngest_denied, same_exec,\n-\t\t\t\t\t\t logged,\n-\t\t\t\t\t\t request-\u003eother_domain_id,\n-\t\t\t\t\t\t request-\u003eaudit.u.tsk);\n+\t\tif (trace_landlock_deny_ptrace_enabled()) {\n+\t\t\tconst struct landlock_ptrace_trace *const trace_ptrace =\n+\t\t\t\trequest-\u003etrace_ptrace;\n+\n+\t\t\tif (WARN_ON_ONCE(!trace_ptrace ||\n+\t\t\t\t\t !trace_ptrace-\u003etracer))\n+\t\t\t\treturn;\n+\n+\t\t\ttrace_landlock_deny_ptrace(\n+\t\t\t\tyoungest_denied, same_exec, logged,\n+\t\t\t\ttrace_ptrace-\u003etracee_domain_id,\n+\t\t\t\trequest-\u003eaudit.u.tsk, trace_ptrace-\u003etracer);\n+\t\t}\n \t\tbreak;\n \tcase LANDLOCK_REQUEST_SCOPE_SIGNAL:\n-\t\tif (trace_landlock_deny_scope_signal_enabled())\n+\t\tif (trace_landlock_deny_scope_signal_enabled()) {\n+\t\t\tconst struct landlock_signal_trace *const trace_signal =\n+\t\t\t\trequest-\u003etrace_signal;\n+\n+\t\t\tif (WARN_ON_ONCE(!trace_signal))\n+\t\t\t\treturn;\n+\n \t\t\ttrace_landlock_deny_scope_signal(\n \t\t\t\tyoungest_denied, same_exec, logged,\n-\t\t\t\trequest-\u003eother_domain_id, request-\u003eaudit.u.tsk);\n+\t\t\t\ttrace_signal-\u003etarget_domain_id,\n+\t\t\t\trequest-\u003eaudit.u.tsk, trace_signal-\u003esignal);\n+\t\t}\n \t\tbreak;\n \tcase LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:\n \t\tif (trace_landlock_deny_scope_abstract_unix_socket_enabled())\ndiff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c\nindex 18dbdb99aebab..6e979cef884d1 100644\n--- a/tools/testing/selftests/landlock/fs_test.c\n+++ b/tools/testing/selftests/landlock/fs_test.c\n@@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1)\n }\n \n /*\n- * Verifies that check_rule_fs events include correct field values: domain, dev,\n- * ino, access_request, and grants. All values are verified against stat() of\n- * the rule path on a deterministic tmpfs layout.\n+ * Verifies that check_rule_inode events include correct field values: domain,\n+ * dev, ino, access_request, and grants. All values are verified against stat()\n+ * of the rule path on a deterministic tmpfs layout.\n */\n TEST_F(trace_layout1, check_rule_fs_fields)\n {\n@@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields)\n \tEXPECT_EQ(1,\n \t\t tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)))\n \t{\n-\t\tTH_LOG(\"Expected 1 check_rule_fs event\\n%s\", buf);\n+\t\tTH_LOG(\"Expected 1 check_rule_inode event\\n%s\", buf);\n \t}\n \n \tASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),\n@@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields)\n }\n \n /*\n- * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and\n- * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the\n+ * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1\n+ * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the\n * pathwalk short-circuits after the first rule fully unmasks the single layer.\n */\n TEST_F(trace_layout1, check_rule_fs_multiple_rules)\n@@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules)\n \tASSERT_NE(NULL, buf);\n \n \t/*\n-\t * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the\n-\t * single layer, so the pathwalk short-circuits before reaching the\n+\t * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks\n+\t * the single layer, so the pathwalk short-circuits before reaching the\n \t * dir_s1d1 rule.\n \t */\n \tcount = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));\n \tEXPECT_EQ(1, count)\n \t{\n-\t\tTH_LOG(\"Expected 1 check_rule_fs event, got %d\\n%s\", count,\n+\t\tTH_LOG(\"Expected 1 check_rule_inode event, got %d\\n%s\", count,\n \t\t buf);\n \t}\n \n@@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)\n \tcount = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));\n \tEXPECT_EQ(1, count)\n \t{\n-\t\tTH_LOG(\"Expected 1 check_rule_fs event, got %d\\n%s\", count,\n+\t\tTH_LOG(\"Expected 1 check_rule_inode event, got %d\\n%s\", count,\n \t\t buf);\n \t}\n \n@@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)\n }\n \n /*\n- * Verifies that check_rule_fs fires for a rule that matches the inode even when\n+ * Verifies that check_rule_inode fires for a rule matching the inode even when\n * it grants none of the requested rights, so the grants set is empty. Landlock\n * cannot know a rule ignores the request before reading it, so the event is\n * still emitted (grants={}), which lets a tracer see that the rule matched.\n@@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)\n \tcount = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));\n \tEXPECT_EQ(2, count)\n \t{\n-\t\tTH_LOG(\"Expected 2 check_rule_fs events, got %d\\n%s\", count,\n+\t\tTH_LOG(\"Expected 2 check_rule_inode events, got %d\\n%s\", count,\n \t\t buf);\n \t}\n \n@@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)\n \t\ttracefs_count_matches(\n \t\t\tbuf,\n \t\t\tTRACE_PREFIX(\n-\t\t\t\tTRACE_TASK) \"landlock_check_rule_fs: domain=[0-9a-f]\\\\+ \"\n+\t\t\t\tTRACE_TASK) \"landlock_check_rule_inode: domain=[0-9a-f]\\\\+ \"\n \t\t\t\t\t \"access_request=read_dir \"\n \t\t\t\t\t \"dev=[0-9]\\\\+:[0-9]\\\\+ ino=[0-9]\\\\+ \"\n \t\t\t\t\t \"grants={}$\"))\n@@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)\n \t\ttracefs_count_matches(\n \t\t\tbuf,\n \t\t\tTRACE_PREFIX(\n-\t\t\t\tTRACE_TASK) \"landlock_check_rule_fs: domain=[0-9a-f]\\\\+ \"\n+\t\t\t\tTRACE_TASK) \"landlock_check_rule_inode: domain=[0-9a-f]\\\\+ \"\n \t\t\t\t\t \"access_request=read_dir \"\n \t\t\t\t\t \"dev=[0-9]\\\\+:[0-9]\\\\+ ino=[0-9]\\\\+ \"\n \t\t\t\t\t \"grants={read_dir}$\"))\ndiff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c\nindex a18761e0fd82f..16afbfdf06bb2 100644\n--- a/tools/testing/selftests/landlock/net_test.c\n+++ b/tools/testing/selftests/landlock/net_test.c\n@@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind)\n }\n \n /*\n- * Anchors the denial fields shared by every deny_access_net event so a field\n- * test proves more than sport/dport: the denying domain, the same-exec bit, the\n- * audit-logging verdict, and the blocked access all stay populated.\n+ * Anchors the denial fields shared by every deny_access_net event so a port\n+ * test also proves the denying domain, execution status, logging verdict, and\n+ * exact blocked access.\n */\n static void\n expect_net_deny_common_fields(struct __test_metadata *const _metadata,\n-\t\t\t const char *const buf)\n+\t\t\t const char *const buf,\n+\t\t\t const char *const expected_blockers)\n {\n \tchar field[64];\n \n@@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata,\n \tASSERT_EQ(0,\n \t\t tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),\n \t\t\t\t\t\"blockers\", field, sizeof(field)));\n-\tEXPECT_STRNE(\"\", field);\n+\tEXPECT_STREQ(expected_blockers, field);\n }\n \n-/* Connect and field-check tests use a separate fixture without variants. */\n+enum trace_net_operation {\n+\tTRACE_NET_BIND,\n+\tTRACE_NET_SEND,\n+};\n \n /* clang-format off */\n-FIXTURE(trace_net_connect) {\n+FIXTURE(trace_net_address) {\n \t/* clang-format on */\n \tint tracefs_ok;\n };\n \n-FIXTURE_SETUP(trace_net_connect)\n+FIXTURE_SETUP(trace_net_address)\n {\n \tint ret;\n \n@@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect)\n \tclear_cap(_metadata, CAP_SYS_ADMIN);\n }\n \n-FIXTURE_TEARDOWN(trace_net_connect)\n+FIXTURE_TEARDOWN(trace_net_address)\n {\n \tif (!self-\u003etracefs_ok)\n \t\treturn;\n@@ -3559,160 +3563,183 @@ FIXTURE_TEARDOWN(trace_net_connect)\n }\n \n /* clang-format off */\n-FIXTURE_VARIANT(trace_net_connect) {\n+FIXTURE_VARIANT(trace_net_address) {\n \t/* clang-format on */\n-\t/* handled_access_net, also the access allowed on the base port. */\n-\t__u64 handled;\n-\t/* Bind the allowed base port before the denied operation. */\n-\tbool bind_base_first;\n-\t/* Denied operation on the next port: connect (true) or bind (false). */\n-\tbool deny_connect;\n+\tint socket_family;\n+\tint socket_type;\n+\tenum trace_net_operation operation;\n+\tint address_family;\n+\tsocklen_t addrlen;\n+\t__u64 handled_access;\n+\tconst char *expected_blockers;\n+\tbool address_port_zero;\n+\tbool expected_address_port;\n+\tint expected_port;\n };\n \n /* clang-format off */\n-\n-/* Denied connect(): sport=0, dport=\u003cdenied port\u003e. */\n-FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) {\n-\t.handled = LANDLOCK_ACCESS_NET_CONNECT_TCP,\n-\t.bind_base_first = false,\n-\t.deny_connect = true,\n+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) {\n+\t/* clang-format on */\n+\t.socket_family = AF_INET,\n+\t.socket_type = SOCK_STREAM,\n+\t.operation = TRACE_NET_BIND,\n+\t.address_family = AF_INET,\n+\t.addrlen = sizeof(struct sockaddr_in),\n+\t.handled_access = LANDLOCK_ACCESS_NET_BIND_TCP,\n+\t.expected_blockers = \"bind_tcp\",\n+\t.expected_address_port = true,\n };\n \n-/* Denied bind(): sport=\u003cdenied port\u003e, dport=0. */\n-FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) {\n-\t.handled = LANDLOCK_ACCESS_NET_BIND_TCP,\n-\t.bind_base_first = false,\n-\t.deny_connect = false,\n+/* Explicit bind(0) has a checked zero port. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) {\n+\t/* clang-format on */\n+\t.socket_family = AF_INET,\n+\t.socket_type = SOCK_DGRAM,\n+\t.operation = TRACE_NET_BIND,\n+\t.address_family = AF_INET,\n+\t.addrlen = sizeof(struct sockaddr_in),\n+\t.handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,\n+\t.expected_blockers = \"bind_udp\",\n+\t.address_port_zero = true,\n+\t.expected_port = 0,\n+};\n+\n+/* A UDP send can deny its synthetic unspecified bind endpoint. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) {\n+\t/* clang-format on */\n+\t.socket_family = AF_INET6,\n+\t.socket_type = SOCK_DGRAM,\n+\t.operation = TRACE_NET_SEND,\n+\t.address_family = AF_INET6,\n+\t.addrlen = sizeof(struct sockaddr_in6),\n+\t.handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,\n+\t.expected_blockers = \"bind_udp\",\n+\t.expected_port = 0,\n };\n \n-/* Denied connect() after an allowed bind(): the connect fields (sport=0). */\n-FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) {\n-\t.handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP,\n-\t.bind_base_first = true,\n-\t.deny_connect = true,\n+/* A family-only address has no checked port. */\n+/* clang-format off */\n+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) {\n+\t/* clang-format on */\n+\t.socket_family = AF_INET6,\n+\t.socket_type = SOCK_DGRAM,\n+\t.operation = TRACE_NET_SEND,\n+\t.address_family = AF_UNSPEC,\n+\t.addrlen = sizeof(sa_family_t),\n+\t.handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,\n+\t.expected_blockers = \"connect_send_udp\",\n+\t.expected_port = -1,\n };\n \n-/* clang-format on */\n+static void set_trace_net_address(struct sockaddr_storage *const storage,\n+\t\t\t\t const int socket_family,\n+\t\t\t\t const int address_family,\n+\t\t\t\t const unsigned short port)\n+{\n+\tmemset(storage, 0, sizeof(*storage));\n \n-/*\n- * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port\n- * is reported in the field matching the denied operation, in host endianness\n- * (the UAPI landlock_net_port_attr.port convention): a connect denial reports\n- * sport=0 dport=\u003cport\u003e, a bind denial reports sport=\u003cport\u003e dport=0, so a\n- * byte-order or field-swap bug is caught. A prior allowed bind\n- * (connect_after_bind) does not change the connect denial's fields.\n- */\n-TEST_F(trace_net_connect, deny_access_net)\n+\tif (socket_family == AF_INET) {\n+\t\tstruct sockaddr_in *const addr4 = (struct sockaddr_in *)storage;\n+\n+\t\taddr4-\u003esin_family = address_family;\n+\t\taddr4-\u003esin_port = htons(port);\n+\t\taddr4-\u003esin_addr.s_addr = htonl(INADDR_LOOPBACK);\n+\t} else {\n+\t\tstruct sockaddr_in6 *const addr6 =\n+\t\t\t(struct sockaddr_in6 *)storage;\n+\n+\t\taddr6-\u003esin6_family = address_family;\n+\t\taddr6-\u003esin6_port = htons(port);\n+\t\taddr6-\u003esin6_addr = in6addr_loopback;\n+\t}\n+}\n+\n+/* Verifies the actionable signed port for representative checked shapes. */\n+TEST_F(trace_net_address, deny_access_net)\n {\n-\tpid_t child;\n-\tint status;\n-\tchar *buf;\n+\tconst char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK);\n+\tconst unsigned short address_port =\n+\t\tvariant-\u003eaddress_port_zero ? 0 : sock_port_start + 1;\n+\tconst int expected_port = variant-\u003eexpected_address_port ?\n+\t\t\t\t\t address_port :\n+\t\t\t\t\t variant-\u003eexpected_port;\n+\tconst struct landlock_ruleset_attr ruleset_attr = {\n+\t\t.handled_access_net = variant-\u003ehandled_access,\n+\t};\n+\tstruct sockaddr_storage address;\n \tchar field[64], expected[16];\n+\tchar *buf;\n+\tint count, ret, ruleset_fd, socket_fd, status;\n+\tpid_t child;\n \n \tif (!self-\u003etracefs_ok)\n \t\tSKIP(return, \"tracefs not available\");\n \n+\tset_trace_net_address(\u0026address, variant-\u003esocket_family,\n+\t\t\t variant-\u003eaddress_family, address_port);\n+\tsocket_fd = socket(variant-\u003esocket_family,\n+\t\t\t variant-\u003esocket_type | SOCK_CLOEXEC, 0);\n+\tASSERT_LE(0, socket_fd);\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ruleset_attr, sizeof(ruleset_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, tracefs_clear_buf());\n+\n \tchild = fork();\n \tASSERT_LE(0, child);\n-\n \tif (child == 0) {\n-\t\tstruct landlock_ruleset_attr ruleset_attr = {\n-\t\t\t.handled_access_net = variant-\u003ehandled,\n-\t\t};\n-\t\tstruct landlock_net_port_attr port_attr = {\n-\t\t\t.allowed_access = variant-\u003ehandled,\n-\t\t\t.port = sock_port_start,\n-\t\t};\n-\t\tstruct sockaddr_in addr = {\n-\t\t\t.sin_family = AF_INET,\n-\t\t\t.sin_addr.s_addr = htonl(INADDR_LOOPBACK),\n-\t\t};\n-\t\tint ruleset_fd, sock_fd, optval = 1, ret;\n-\n-\t\truleset_fd = landlock_create_ruleset(\u0026ruleset_attr,\n-\t\t\t\t\t\t sizeof(ruleset_attr), 0);\n-\t\tif (ruleset_fd \u003c 0)\n+\t\tif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))\n \t\t\t_exit(1);\n-\t\tif (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,\n-\t\t\t\t \u0026port_attr, 0)) {\n-\t\t\tclose(ruleset_fd);\n-\t\t\t_exit(1);\n-\t\t}\n-\t\tprctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);\n-\t\tif (landlock_restrict_self(ruleset_fd, 0)) {\n-\t\t\tclose(ruleset_fd);\n-\t\t\t_exit(1);\n-\t\t}\n+\t\tif (landlock_restrict_self(ruleset_fd, 0))\n+\t\t\t_exit(2);\n \t\tclose(ruleset_fd);\n \n-\t\tsock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);\n-\t\tif (sock_fd \u003c 0)\n-\t\t\t_exit(1);\n-\n-\t\t/* Bind the allowed base port first (succeeds, no event). */\n-\t\tif (variant-\u003ebind_base_first) {\n-\t\t\tsetsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, \u0026optval,\n-\t\t\t\t sizeof(optval));\n-\t\t\taddr.sin_port = htons(sock_port_start);\n-\t\t\tif (bind(sock_fd, (struct sockaddr *)\u0026addr,\n-\t\t\t\t sizeof(addr))) {\n-\t\t\t\tclose(sock_fd);\n-\t\t\t\t_exit(1);\n-\t\t\t}\n-\t\t}\n-\n-\t\t/* Denied operation on the next port. */\n-\t\taddr.sin_port = htons(sock_port_start + 1);\n-\t\tif (variant-\u003edeny_connect)\n-\t\t\tret = connect(sock_fd, (struct sockaddr *)\u0026addr,\n-\t\t\t\t sizeof(addr));\n-\t\telse\n-\t\t\tret = bind(sock_fd, (struct sockaddr *)\u0026addr,\n-\t\t\t\t sizeof(addr));\n-\t\tif (ret == 0) {\n-\t\t\tclose(sock_fd);\n-\t\t\t_exit(2);\n-\t\t}\n-\t\tif (errno != EACCES) {\n-\t\t\tclose(sock_fd);\n+\t\tswitch (variant-\u003eoperation) {\n+\t\tcase TRACE_NET_BIND:\n+\t\t\tret = bind(socket_fd, (const struct sockaddr *)\u0026address,\n+\t\t\t\t variant-\u003eaddrlen);\n+\t\t\tbreak;\n+\t\tcase TRACE_NET_SEND:\n+\t\t\tret = sendto(socket_fd, \"A\", 1, MSG_NOSIGNAL,\n+\t\t\t\t (const struct sockaddr *)\u0026address,\n+\t\t\t\t variant-\u003eaddrlen);\n+\t\t\tbreak;\n+\t\tdefault:\n \t\t\t_exit(3);\n \t\t}\n-\t\tclose(sock_fd);\n+\t\tif (ret \u003e= 0 || errno != EACCES)\n+\t\t\t_exit(4);\n+\t\tclose(socket_fd);\n+\n \t\t_exit(0);\n \t}\n+\tclose(ruleset_fd);\n+\tclose(socket_fd);\n \n \tASSERT_EQ(child, waitpid(child, \u0026status, 0));\n \tASSERT_TRUE(WIFEXITED(status));\n-\tEXPECT_EQ(0, WEXITSTATUS(status));\n+\tASSERT_EQ(0, WEXITSTATUS(status));\n \n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n-\n-\tEXPECT_EQ(1, tracefs_count_matches(buf,\n-\t\t\t\t\t REGEX_DENY_ACCESS_NET(TRACE_TASK)));\n-\n-\texpect_net_deny_common_fields(_metadata, buf);\n-\n-\t/*\n-\t * The denied operation's port field carries the port; the other is 0.\n-\t */\n-\tsnprintf(expected, sizeof(expected), \"%llu\",\n-\t\t (unsigned long long)(sock_port_start + 1));\n-\n-\tASSERT_EQ(0,\n-\t\t tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),\n-\t\t\t\t\t\"sport\", field, sizeof(field)));\n-\tEXPECT_STREQ(variant-\u003edeny_connect ? \"0\" : expected, field);\n-\n-\tASSERT_EQ(0,\n-\t\t tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),\n-\t\t\t\t\t\"dport\", field, sizeof(field)));\n-\tEXPECT_STREQ(variant-\u003edeny_connect ? expected : \"0\", field);\n+\tcount = tracefs_count_matches(buf, event_regex);\n+\tif (count != 1)\n+\t\tTH_LOG(\"Expected 1 denial event, got %d\\n%s\", count, buf);\n+\tASSERT_EQ(1, count);\n+\texpect_net_deny_common_fields(_metadata, buf,\n+\t\t\t\t variant-\u003eexpected_blockers);\n+\n+\tASSERT_EQ(0, tracefs_extract_field(buf, event_regex, \"port\", field,\n+\t\t\t\t\t sizeof(field)));\n+\tsnprintf(expected, sizeof(expected), \"%d\", expected_port);\n+\tEXPECT_STREQ(expected, field);\n \n \tfree(buf);\n }\n \n-/* Field verification for the check_rule_net event on an allowed access. */\n+/* Field verification for the check_rule_net_port event on an allowed access. */\n \n /* clang-format off */\n FIXTURE(trace_net_check_rule) {\n@@ -3757,10 +3784,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule)\n \n /*\n * Verifies that an allowed bind matching a net-port rule emits exactly one\n- * landlock_check_rule_net event with the enforcing domain, the requested\n+ * landlock_check_rule_net_port event with the enforcing domain, the requested\n * access, the checked port (host endianness), and the per-layer grants. The\n- * whole event is anchored to exact values so a revert of the check_rule_net\n- * emit (or a byte-order or field-plumbing regression) fails the test.\n+ * whole event is anchored to exact values so removing the check_rule_net_port\n+ * emission or introducing a byte-order or field-plumbing regression fails the\n+ * test.\n */\n TEST_F(trace_net_check_rule, check_rule_net_fields)\n {\n@@ -3832,7 +3860,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)\n \tEXPECT_EQ(1,\n \t\t tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK)))\n \t{\n-\t\tTH_LOG(\"Expected 1 check_rule_net event\\n%s\", buf);\n+\t\tTH_LOG(\"Expected 1 check_rule_net_port event\\n%s\", buf);\n \t}\n \n \tASSERT_EQ(0,\n@@ -3866,11 +3894,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)\n \tfree(buf);\n }\n \n-/*\n- * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses\n- * the same current_check_access_socket() code path as IPv4, validated by the\n- * audit tests in this file. The trace events use the same blockers/sport/dport\n- * fields regardless of address family.\n- */\n-\n TEST_HARNESS_MAIN\ndiff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h\nindex ba0c5e92001f1..2ec8633621732 100644\n--- a/tools/testing/selftests/landlock/trace.h\n+++ b/tools/testing/selftests/landlock/trace.h\n@@ -27,14 +27,14 @@\n \tTRACEFS_LANDLOCK_DIR \"/landlock_create_domain/enable\"\n #define TRACEFS_ENFORCE_DOMAIN_ENABLE \\\n \tTRACEFS_LANDLOCK_DIR \"/landlock_enforce_domain/enable\"\n-#define TRACEFS_ADD_RULE_FS_ENABLE \\\n-\tTRACEFS_LANDLOCK_DIR \"/landlock_add_rule_fs/enable\"\n-#define TRACEFS_ADD_RULE_NET_ENABLE \\\n-\tTRACEFS_LANDLOCK_DIR \"/landlock_add_rule_net/enable\"\n+#define TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE \\\n+\tTRACEFS_LANDLOCK_DIR \"/landlock_add_rule_path_beneath/enable\"\n+#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \\\n+\tTRACEFS_LANDLOCK_DIR \"/landlock_add_rule_net_port/enable\"\n #define TRACEFS_CHECK_RULE_FS_ENABLE \\\n-\tTRACEFS_LANDLOCK_DIR \"/landlock_check_rule_fs/enable\"\n+\tTRACEFS_LANDLOCK_DIR \"/landlock_check_rule_inode/enable\"\n #define TRACEFS_CHECK_RULE_NET_ENABLE \\\n-\tTRACEFS_LANDLOCK_DIR \"/landlock_check_rule_net/enable\"\n+\tTRACEFS_LANDLOCK_DIR \"/landlock_check_rule_net_port/enable\"\n #define TRACEFS_DENY_ACCESS_FS_ENABLE \\\n \tTRACEFS_LANDLOCK_DIR \"/landlock_deny_access_fs/enable\"\n #define TRACEFS_DENY_ACCESS_NET_ENABLE \\\n@@ -79,18 +79,18 @@\n */\n #define KWORKER_TASK \"kworker/[0-9]\\\\+:[0-9]\\\\+\"\n \n-#define REGEX_ADD_RULE_FS(task) \\\n-\tTRACE_PREFIX(task) \\\n-\t\"landlock_add_rule_fs: \" \\\n-\t\"ruleset=[0-9a-f]\\\\+\\\\.[0-9]\\\\+ \" \\\n-\t\"access_rights=[a-z_|]* \" \\\n-\t\"dev=[0-9]\\\\+:[0-9]\\\\+ \" \\\n-\t\"ino=[0-9]\\\\+ \" \\\n+#define REGEX_ADD_RULE_PATH_BENEATH(task) \\\n+\tTRACE_PREFIX(task) \\\n+\t\"landlock_add_rule_path_beneath: \" \\\n+\t\"ruleset=[0-9a-f]\\\\+\\\\.[0-9]\\\\+ \" \\\n+\t\"access_rights=[a-z_|]* \" \\\n+\t\"dev=[0-9]\\\\+:[0-9]\\\\+ \" \\\n+\t\"ino=[0-9]\\\\+ \" \\\n \t\"path=[^ ]\\\\+$\"\n \n-#define REGEX_ADD_RULE_NET(task) \\\n+#define REGEX_ADD_RULE_NET_PORT(task) \\\n \tTRACE_PREFIX(task) \\\n-\t\"landlock_add_rule_net: \" \\\n+\t\"landlock_add_rule_net_port: \" \\\n \t\"ruleset=[0-9a-f]\\\\+\\\\.[0-9]\\\\+ \" \\\n \t\"access_rights=[a-z_|]* \" \\\n \t\"port=[0-9]\\\\+$\"\n@@ -110,21 +110,21 @@\n \t\"parent=[0-9a-f]\\\\+ \" \\\n \t\"ruleset=[0-9a-f]\\\\+\\\\.[0-9]\\\\+$\"\n \n-#define REGEX_CHECK_RULE_FS(task) \\\n-\tTRACE_PREFIX(task) \\\n-\t\"landlock_check_rule_fs: \" \\\n-\t\"domain=[0-9a-f]\\\\+ \" \\\n-\t\"access_request=[a-z_|]* \" \\\n-\t\"dev=[0-9]\\\\+:[0-9]\\\\+ \" \\\n-\t\"ino=[0-9]\\\\+ \" \\\n+#define REGEX_CHECK_RULE_FS(task) \\\n+\tTRACE_PREFIX(task) \\\n+\t\"landlock_check_rule_inode: \" \\\n+\t\"domain=[0-9a-f]\\\\+ \" \\\n+\t\"access_request=[a-z_|]* \" \\\n+\t\"dev=[0-9]\\\\+:[0-9]\\\\+ \" \\\n+\t\"ino=[0-9]\\\\+ \" \\\n \t\"grants={[a-z_|,]*}$\"\n \n-#define REGEX_CHECK_RULE_NET(task) \\\n-\tTRACE_PREFIX(task) \\\n-\t\"landlock_check_rule_net: \" \\\n-\t\"domain=[0-9a-f]\\\\+ \" \\\n-\t\"access_request=[a-z_|]* \" \\\n-\t\"port=[0-9]\\\\+ \" \\\n+#define REGEX_CHECK_RULE_NET(task) \\\n+\tTRACE_PREFIX(task) \\\n+\t\"landlock_check_rule_net_port: \" \\\n+\t\"domain=[0-9a-f]\\\\+ \" \\\n+\t\"access_request=[a-z_|]* \" \\\n+\t\"port=[0-9]\\\\+ \" \\\n \t\"grants={[a-z_|,]*}$\"\n \n #define REGEX_DENY_ACCESS_FS(task) \\\n@@ -145,8 +145,7 @@\n \t\"same_exec=[01] \" \\\n \t\"logged=[01] \" \\\n \t\"blockers=[a-z_|]* \" \\\n-\t\"sport=[0-9]\\\\+ \" \\\n-\t\"dport=[0-9]\\\\+$\"\n+\t\"port=-\\\\?[0-9]\\\\+$\"\n \n #define REGEX_DENY_PTRACE(task) \\\n \tTRACE_PREFIX(task) \\\ndiff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c\nindex 4543a25c1f559..64014ade3a0e9 100644\n--- a/tools/testing/selftests/landlock/trace_fs_test.c\n+++ b/tools/testing/selftests/landlock/trace_fs_test.c\n@@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs)\n \t}\n \tself-\u003etracefs_ok = 1;\n \n-\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));\n+\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,\n+\t\t\t\t\t true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));\n \tASSERT_EQ(0, tracefs_clear());\n@@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs)\n \t\treturn;\n \n \tset_cap(_metadata, CAP_SYS_ADMIN);\n-\ttracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);\n+\ttracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);\n \ttracefs_fixture_teardown();\n@@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed)\n }\n \n /*\n- * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace\n- * event with the expected path and field values: ruleset ID is non-zero,\n- * access_rights is non-zero, and path matches.\n+ * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath\n+ * event with the expected path and field values: the ruleset ID and\n+ * access_rights are non-zero, and the path matches.\n */\n-TEST_F(trace_fs, add_rule_fs)\n+TEST_F(trace_fs, add_rule_path_beneath)\n {\n \tstruct landlock_ruleset_attr ruleset_attr = {\n \t\t.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |\n@@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs)\n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n \n-\tcount = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));\n+\tcount = tracefs_count_matches(buf,\n+\t\t\t\t REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));\n \tEXPECT_EQ(1, count)\n \t{\n-\t\tTH_LOG(\"Expected 1 add_rule_fs event, got %d\\n%s\", count, buf);\n+\t\tTH_LOG(\"Expected 1 add_rule_path_beneath event, got %d\\n%s\",\n+\t\t count, buf);\n \t}\n \n \t/* Ruleset ID should be non-zero. */\n-\tASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),\n-\t\t\t\t\t \"ruleset\", field_buf,\n-\t\t\t\t\t sizeof(field_buf)));\n+\tASSERT_EQ(0, tracefs_extract_field(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),\n+\t\t\t \"ruleset\", field_buf, sizeof(field_buf)));\n \tEXPECT_STRNE(\"0\", field_buf);\n \n \t/* Access rights should be non-zero. */\n-\tASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),\n-\t\t\t\t\t \"access_rights\", field_buf,\n-\t\t\t\t\t sizeof(field_buf)));\n+\tASSERT_EQ(0, tracefs_extract_field(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),\n+\t\t\t \"access_rights\", field_buf, sizeof(field_buf)));\n \tEXPECT_STRNE(\"\", field_buf);\n \n \t/* Path should be /usr. */\n-\tASSERT_EQ(0,\n-\t\t tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),\n-\t\t\t\t\t\"path\", field_buf, sizeof(field_buf)));\n+\tASSERT_EQ(0, tracefs_extract_field(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),\n+\t\t\t \"path\", field_buf, sizeof(field_buf)));\n \tEXPECT_STREQ(\"/usr\", field_buf);\n \n \tfree(buf);\n@@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs)\n * Verifies that a path whose escaping exceeds the trace scratch sequence does\n * not corrupt a sibling symbolic field.\n */\n-TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)\n+TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow)\n {\n \tstatic const char access_prefix[] = \"execute|write_file|read_file|\";\n \tstatic const char access_suffix[] = \"|ioctl_dev|resolve_unix\";\n@@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)\n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n \n-\tcount = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));\n+\tcount = tracefs_count_matches(buf,\n+\t\t\t\t REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));\n \tEXPECT_EQ(1, count)\n \t{\n-\t\tTH_LOG(\"Expected 1 add_rule_fs event, got %d\\n%s\", count, buf);\n+\t\tTH_LOG(\"Expected 1 add_rule_path_beneath event, got %d\\n%s\",\n+\t\t count, buf);\n \t}\n \n \t/*\n@@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)\n \t * field also catches scratch-sequence poisoning when the compiler\n \t * evaluates the overflowing path first, as GCC currently does.\n \t */\n-\tASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),\n-\t\t\t\t\t \"access_rights\", field_buf,\n-\t\t\t\t\t sizeof(field_buf)));\n+\tASSERT_EQ(0, tracefs_extract_field(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),\n+\t\t\t \"access_rights\", field_buf, sizeof(field_buf)));\n \tEXPECT_EQ(0,\n \t\t strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1));\n \tEXPECT_EQ(NULL, strstr(field_buf, \"|refer|\"));\n@@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)\n \tASSERT_LE(sizeof(access_suffix) - 1, field_len);\n \tEXPECT_STREQ(access_suffix,\n \t\t field_buf + field_len - (sizeof(access_suffix) - 1));\n-\texpect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK));\n+\texpect_truncated_path(_metadata, buf,\n+\t\t\t REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));\n \n \tfree(buf);\n }\n@@ -542,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested)\n */\n TEST_F(trace_fs, deny_access_fs_denied)\n {\n-\tchar *buf;\n+\tconst char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);\n+\tchar *buf, blockers[64];\n \tint count;\n \n \tASSERT_EQ(0, tracefs_clear_buf());\n@@ -558,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied)\n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n \n-\tcount = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));\n-\tEXPECT_LE(1, count);\n+\tcount = tracefs_count_matches(buf, event_regex);\n+\tEXPECT_EQ(1, count)\n+\t{\n+\t\tTH_LOG(\"Expected 1 access denial, got %d\\n%s\", count, buf);\n+\t}\n+\tASSERT_EQ(0, tracefs_extract_field(buf, event_regex, \"blockers\",\n+\t\t\t\t\t blockers, sizeof(blockers)));\n+\tEXPECT_STREQ(\"read_dir\", blockers);\n+\n+\tfree(buf);\n+}\n+\n+/*\n+ * Verifies that a denied mount reports the singleton topology blocker rather\n+ * than an empty access mask.\n+ */\n+TEST_F(trace_fs, deny_change_topology)\n+{\n+\tconst char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);\n+\tconst struct landlock_ruleset_attr ruleset_attr = {\n+\t\t.handled_access_fs = LANDLOCK_ACCESS_FS_REFER,\n+\t};\n+\tchar *buf, blockers[64];\n+\tint count, ruleset_fd, status;\n+\tpid_t pid;\n+\n+\truleset_fd =\n+\t\tlandlock_create_ruleset(\u0026ruleset_attr, sizeof(ruleset_attr), 0);\n+\tASSERT_LE(0, ruleset_fd);\n+\tASSERT_EQ(0, tracefs_clear_buf());\n+\n+\t/* Ensure that Landlock is the only expected mount denial. */\n+\tset_cap(_metadata, CAP_SYS_ADMIN);\n+\tpid = fork();\n+\tASSERT_LE(0, pid);\n+\tif (pid == 0) {\n+\t\tif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {\n+\t\t\tclose(ruleset_fd);\n+\t\t\t_exit(1);\n+\t\t}\n+\t\tif (landlock_restrict_self(ruleset_fd, 0)) {\n+\t\t\tclose(ruleset_fd);\n+\t\t\t_exit(2);\n+\t\t}\n+\t\tclose(ruleset_fd);\n+\n+\t\tif (mount(NULL, \"/\", NULL, MS_PRIVATE | MS_REC, NULL) != -1)\n+\t\t\t_exit(3);\n+\n+\t\tif (errno != EPERM)\n+\t\t\t_exit(4);\n+\n+\t\t_exit(0);\n+\t}\n+\tclose(ruleset_fd);\n+\tclear_cap(_metadata, CAP_SYS_ADMIN);\n+\n+\tASSERT_EQ(pid, waitpid(pid, \u0026status, 0));\n+\tASSERT_TRUE(WIFEXITED(status));\n+\tEXPECT_EQ(0, WEXITSTATUS(status));\n+\n+\tbuf = tracefs_read_buf();\n+\tASSERT_NE(NULL, buf);\n+\tcount = tracefs_count_matches(buf, event_regex);\n+\tEXPECT_EQ(1, count)\n+\t{\n+\t\tTH_LOG(\"Expected 1 topology denial, got %d\\n%s\", count, buf);\n+\t}\n+\tASSERT_EQ(0, tracefs_extract_field(buf, event_regex, \"blockers\",\n+\t\t\t\t\t blockers, sizeof(blockers)));\n+\tEXPECT_STREQ(\"change_topology\", blockers);\n \n \tfree(buf);\n }\ndiff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c\nindex afdaf8511b3a1..f9b293a9dd565 100644\n--- a/tools/testing/selftests/landlock/trace_test.c\n+++ b/tools/testing/selftests/landlock/trace_test.c\n@@ -49,8 +49,10 @@ FIXTURE_SETUP(trace)\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));\n-\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));\n-\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true));\n+\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,\n+\t\t\t\t\t true));\n+\tASSERT_EQ(0,\n+\t\t tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));\n@@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace)\n \ttracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);\n-\ttracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);\n-\ttracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false);\n+\ttracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);\n+\ttracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);\n \ttracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);\n@@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled)\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));\n \tASSERT_EQ(0,\n \t\t tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));\n-\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false));\n-\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false));\n+\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,\n+\t\t\t\t\t false));\n+\tASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE,\n+\t\t\t\t\t false));\n \tASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));\n \tASSERT_EQ(0,\n \t\t tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false));\n@@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version)\n \tASSERT_NE(0, !!dot);\n \tEXPECT_STREQ(\"0\", dot + 1);\n \n-\t/* Verify 2 add_rule_fs events were emitted. */\n-\tEXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))\n+\t/* Verify two add_rule_path_beneath events were emitted. */\n+\tEXPECT_EQ(2, tracefs_count_matches(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))\n \t{\n-\t\tTH_LOG(\"Expected 2 add_rule_fs events\\n%s\", buf);\n+\t\tTH_LOG(\"Expected 2 add_rule_path_beneath events\\n%s\", buf);\n \t}\n \n \t/*\n@@ -373,7 +378,7 @@ TEST_F(trace, create_domain)\n \t\ttracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));\n \tASSERT_LE(1, check_count)\n \t{\n-\t\tTH_LOG(\"Expected check_rule_fs events\\n%s\", buf);\n+\t\tTH_LOG(\"Expected check_rule_inode events\\n%s\", buf);\n \t}\n \n \tEXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),\n@@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd)\n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n \n-\tEXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))\n+\tEXPECT_EQ(0, tracefs_count_matches(\n+\t\t\t buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))\n \t{\n-\t\tTH_LOG(\"No add_rule_fs event expected on invalid fd\\n%s\", buf);\n+\t\tTH_LOG(\"No add_rule_path_beneath event expected on invalid fd\\n%s\",\n+\t\t buf);\n \t}\n \n \tfree(buf);\n@@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting)\n }\n \n /*\n- * Verifies that landlock_add_rule_net emits a trace event with the correct port\n- * and allowed access mask fields.\n+ * Verifies that landlock_add_rule_net_port emits a trace event with the correct\n+ * port and allowed access mask fields.\n */\n-TEST_F(trace, add_rule_net_fields)\n+TEST_F(trace, add_rule_net_port_fields)\n {\n \tstruct landlock_ruleset_attr ruleset_attr = {\n \t\t.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,\n@@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields)\n \tbuf = tracefs_read_buf();\n \tASSERT_NE(NULL, buf);\n \n-\tEXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK)))\n+\tEXPECT_EQ(1, tracefs_count_matches(buf,\n+\t\t\t\t\t REGEX_ADD_RULE_NET_PORT(TRACE_TASK)))\n \t{\n-\t\tTH_LOG(\"Expected 1 add_rule_net event\\n%s\", buf);\n+\t\tTH_LOG(\"Expected 1 add_rule_net_port event\\n%s\", buf);\n \t}\n \n \t/*\n@@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields)\n \t * (landlock_net_port_attr.port). On little-endian, htons(8080) is\n \t * 36895, so this comparison catches byte-order bugs.\n \t */\n-\tEXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),\n+\tEXPECT_EQ(0, tracefs_extract_field(buf,\n+\t\t\t\t\t REGEX_ADD_RULE_NET_PORT(TRACE_TASK),\n \t\t\t\t\t \"port\", field, sizeof(field)));\n \tEXPECT_STREQ(\"8080\", field);\n \t/*\n@@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields)\n \t * net access bits are unhandled because the ruleset only handles\n \t * BIND_TCP).\n \t */\n-\tEXPECT_EQ(0,\n-\t\t tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),\n-\t\t\t\t\t\"access_rights\", field, sizeof(field)));\n+\tEXPECT_EQ(0, tracefs_extract_field(\n+\t\t\t buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK),\n+\t\t\t \"access_rights\", field, sizeof(field)));\n \tEXPECT_STREQ(\"bind_tcp|connect_tcp|bind_udp|connect_send_udp\", field);\n \n \tfree(buf);\n"
} |
| 2/1 | 2026/09/20 23:54 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"SECURITY",
"SECURITY_LANDLOCK",
"TRACEPOINTS",
"INET"
],
"FocusSymbols": [
"landlock_trace_denial",
"landlock_append_fs_rule",
"landlock_append_net_rule",
"current_check_access_socket",
"hook_unix_stream_connect",
"hook_unix_may_send",
"hook_ptrace_access_check",
"hook_ptrace_traceme",
"hook_task_kill",
"hook_file_send_sigiotask"
],
"Reasoning": "The patch refactors Landlock trace events and modifies Landlock's denial logging data structures (introducing struct landlock_blockers and trace context structs passed through a union in struct landlock_request). It updates multiple reachable LSM hooks (hook_unix_stream_connect, hook_unix_may_send, hook_ptrace_access_check, hook_ptrace_traceme, hook_task_kill, hook_file_send_sigiotask, current_check_access_socket) and adds new runtime assertions including multiple WARN_ON_ONCE checks in landlock_trace_denial() and a lockdep_assert_held() check in get_socket_domain_id(). These modifications affect reachable security subsystem code and warrant fuzzing to ensure that invariants hold and no unintended regressions or assertion failures occur.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 07f6c65816aba6a93b7296aeb30665ea83d41747
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 20 23:54:12 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst
index af9267cca47d9..304a8d06273ee 100644
--- a/Documentation/trace/events-landlock.rst
+++ b/Documentation/trace/events-landlock.rst
@@ -6,7 +6,7 @@ Landlock Trace Events
=====================
:Author: Mickaël Salaün
-:Date: August 2026
+:Date: September 2026
Landlock emits trace events for sandbox lifecycle operations and access
denials. These events can be consumed by ftrace (for human-readable
@@ -31,8 +31,8 @@ Landlock trace events are organized in four categories:
**Syscall events** are emitted during Landlock system calls:
- ``landlock_create_ruleset``: a new ruleset is created
-- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset
-- ``landlock_add_rule_net``: a network port rule is added to a ruleset
+- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset
+- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset
- ``landlock_create_domain``: a new domain is created from a ruleset
- ``landlock_enforce_domain``: a domain is enforced on a thread
@@ -47,8 +47,8 @@ Landlock trace events are organized in four categories:
**Rule evaluation events** are emitted during rule matching:
-- ``landlock_check_rule_fs``: a filesystem rule is evaluated
-- ``landlock_check_rule_net``: a network port rule is evaluated
+- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated
+- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated
**Lifecycle events**:
@@ -141,10 +141,10 @@ in some field formats:
treated as untrusted input and escaped in the trace text output so it
cannot inject field separators or control characters.
-- **Other party's domain**: A scope or ptrace denial compares the
- subject's denying domain (``domain=``, always the enforcing domain and
- never the current task) with the other party's domain, so these
- tracepoints also report the other party's domain as a scalar ID:
+- **Other party's domain**: A scope or ptrace denial compares the subject's
+ denying domain (``domain=``), which is the enforcing domain and not
+ necessarily the current task's domain, with the other party's domain.
+ These tracepoints also report the other party's domain as a scalar ID:
``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and
``peer_domain=`` (abstract unix socket). It is ``0`` when the other
party is unsandboxed, and otherwise a domain ID that a consumer resolves
@@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID.
Interpreting check_rule events
==============================
-The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer
+The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer
rule evaluation, which is useful for understanding *why* a specific
access is allowed or denied.
@@ -231,9 +231,10 @@ contribution, not the final decision:
through another matching rule, or the right is denied and appears in the
``blockers=`` field of the corresponding ``deny_access`` event.
-To reconstruct the decision for an object, aggregate the ``grants=``
-groups of all ``check_rule`` events emitted for that object during the
-check.
+For an access check that a consumer can delimit, aggregate the ``grants=``
+groups of all matching ``check_rule`` events. These events do not carry a
+request ID; use their execution context and generic tracepoints to separate
+concurrent or successive checks of the same object.
.. note::
@@ -244,29 +245,31 @@ check.
For example, a program sandboxed with read and execute access to the
whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the
-read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires
+read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires
with the requested rights intersected against what that rule grants.
The ``access_request=`` mask includes ``truncate`` because the file-open hook
evaluates that optional right alongside the required access, but the
rule does not grant it, so ``truncate`` never appears in ``grants=``::
- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}
- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}
+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}
+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}
The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A
single ``grants=`` group means the enforcing domain has one layer. With
two nested sandboxes that each grant the same rights, the rule spans both
layers, so ``grants=`` has one group per layer::
- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}
- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}
+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}
+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}
eBPF access
===========
-eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the
-tracepoint arguments directly through BTF. The arguments include both
-standard kernel objects and Landlock-internal objects:
+BTF-enabled raw tracepoint programs attached through libbpf
+``SEC("tp_btf/...")`` sections receive typed callback arguments. The
+event prototypes in `Event reference`_ document their argument layouts.
+The arguments include both standard kernel objects and Landlock-internal
+objects:
- Standard kernel objects (``struct task_struct``, ``struct sock``,
``struct path``, ``struct dentry``) can be used with existing BPF
@@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects:
Internal struct layouts may change between kernel versions; use CO-RE
for field relocation.
-A stateful eBPF program observes the full event stream and maintains
+A stateful eBPF program attached before sandbox construction can maintain
per-domain state in BPF maps:
1. On ``landlock_create_domain``: record the domain ID and parent (the
@@ -293,7 +296,10 @@ per-domain state in BPF maps:
final statistics.
This approach requires no kernel modification and no Landlock-specific
-BPF helpers. The Landlock IDs serve as correlation keys across events.
+BPF helpers. Landlock IDs serve as correlation keys within one boot.
+Records exported through tracing or BPF buffers can be lost, and records
+from different CPUs are not globally ordered, so consumers must detect and
+reconcile incomplete state.
Audit filtering equivalence
===========================
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index d05253afaf590..523ba5ea98700 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -10,14 +10,19 @@
#if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_LANDLOCK_H
+#include <linux/in.h>
+#include <linux/in6.h>
#include <linux/landlock.h>
+#include <linux/socket.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/tracepoint.h>
#include <linux/trace_seq.h>
#include <net/af_unix.h>
+enum landlock_request_type;
struct dentry;
+struct landlock_blockers;
struct landlock_domain;
struct landlock_hierarchy;
struct landlock_rule;
@@ -26,6 +31,12 @@ struct path;
struct sock;
struct task_struct;
+static_assert(sizeof(access_mask_t) <= sizeof(u64));
+
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY);
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS);
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS);
+
#ifdef CREATE_TRACE_POINTS
/* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */
@@ -108,7 +119,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)
* Fills the dense per-domain-layer array layers (one access mask per layer,
* indexed by level - 1) from rule's sparse layer stack, keeping only the
* requested rights (access_request). Layers with no matching rule entry get
- * a zero mask. Shared by the check_rule_fs and check_rule_net events.
+ * a zero mask. Shared by the check_rule_inode and check_rule_net_port events.
*
* rule->layers is sorted by ascending level, with levels in the domain's
* [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps
@@ -182,6 +193,9 @@ static inline const char *__trace_landlock_print_layers(
/* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */
#define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name }
+#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \
+ { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" }
+
/**
* DOC: Landlock trace events
*
@@ -192,22 +206,22 @@ static inline const char *__trace_landlock_print_layers(
* Decision context
* ~~~~~~~~~~~~~~~~
*
- * A denial event, together with the lifecycle events, exposes the full
- * set of inputs the verdict consumed, so a consumer that tracked domain
- * creation (landlock_create_ruleset, landlock_create_domain) can verify
- * or reproduce the Landlock decision rather than merely observe it
- * happened. In who/what/why terms: who is the denying domain (the domain
- * field, always the subject that enforced the policy, never the current
- * task), what is the operation and its object, and why is every other
- * input the verdict weighed.
+ * A denial event identifies the domain whose policy denied the request, the
+ * Landlock operation and policy object that were checked, and the blocker or
+ * domain relationship responsible for the denial. When tracing starts with
+ * sandbox construction, ruleset and domain events provide the policy history
+ * needed to interpret these identifiers. The denying domain is the subject
+ * that enforced the policy, not necessarily current. Generic tracepoints can
+ * provide additional operational context.
*
* Lifecycle consistency
* ~~~~~~~~~~~~~~~~~~~~~~
*
- * Lifecycle events are balanced: a creation event always has a matching
- * deallocation event and vice versa, so an eBPF program can model object
- * lifetimes from the trace stream without reconciliation logic. A creation
- * event fires while the object is still private to the calling thread
+ * Lifecycle emission is balanced: a creation event always has a matching
+ * deallocation event and vice versa. A consumer that observes an object's
+ * complete lifetime can model it from this pair; one that attaches late or
+ * loses exported records must reconcile incomplete state. A creation event
+ * fires while the object is still private to the calling thread
* (landlock_create_ruleset fires before the ruleset's file descriptor is
* installed, so it cannot race a concurrent :manpage:`close(2)`); if fd
* installation later fails and the ruleset is freed, free_ruleset still
@@ -243,8 +257,8 @@ static inline const char *__trace_landlock_print_layers(
* Field encoding
* ~~~~~~~~~~~~~~
*
- * Fields that mirror the Landlock UAPI use the same C types and endianness
- * (e.g. network ports are __u64 in host endianness, like
+ * Fields that mirror the Landlock UAPI preserve their widths and endianness
+ * (e.g. network ports are u64 in host endianness, like
* landlock_net_port_attr.port). Per-event details, such as where a value
* is byte-swapped, live in the field's own kdoc.
*
@@ -281,6 +295,16 @@ static inline const char *__trace_landlock_print_layers(
* the two parties without kernel-internal state. The ID is a scalar
* snapshot, not a live domain pointer that could dangle: an optional
* relational referent is a scalar (0 sentinel), not a nullable pointer.
+ * Nonzero IDs are unique within one boot. For ptrace, same_exec instead
+ * describes the tracer, even for
+ * PTRACE_TRACEME, and may differ from the current task.
+ *
+ * Blocker fields
+ * ~~~~~~~~~~~~~~
+ *
+ * The filesystem and network blocker arguments identify the request type
+ * and carry its final missing access subset when applicable. The type
+ * determines how to interpret the access value.
*/
/*
@@ -319,8 +343,8 @@ TRACE_EVENT(landlock_create_ruleset,
TP_ARGS(ruleset),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, handled_fs )
__field( access_mask_t, handled_net )
__field( access_mask_t, scoped )
@@ -359,8 +383,8 @@ TRACE_EVENT(landlock_free_ruleset,
TP_ARGS(ruleset),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
),
TP_fast_assign(
@@ -373,12 +397,14 @@ TRACE_EVENT(landlock_free_ruleset,
);
/**
- * landlock_add_rule_fs - Filesystem rule added to a ruleset
+ * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
- * @access_rights: Effective access mask stored in the rule, not the raw
- * sys_landlock_add_rule() argument (unhandled rights
- * added).
+ * @flags: Complete validated landlock_add_rule_flags value supplied by this
+ * successful call, not the rule's accumulated quiet state.
+ * @access_rights: Canonical per-call access mask passed to
+ * landlock_insert_rule() after normalization, not the raw
+ * sys_landlock_add_rule() argument or accumulated rule.
* @path: Filesystem path for the rule (never NULL).
* @pathname: Resolved absolute path string (never NULL; error placeholder
* on resolution failure).
@@ -387,17 +413,17 @@ TRACE_EVENT(landlock_free_ruleset,
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
-TRACE_EVENT(landlock_add_rule_fs,
+TRACE_EVENT(landlock_add_rule_path_beneath,
- TP_PROTO(const struct landlock_ruleset *ruleset,
- access_mask_t access_rights, const struct path *path,
+ TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
+ u64 access_rights, const struct path *path,
const char *pathname),
- TP_ARGS(ruleset, access_rights, path, pathname),
+ TP_ARGS(ruleset, flags, access_rights, path, pathname),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, access_rights )
__field( dev_t, dev )
__field( ino_t, ino )
@@ -427,31 +453,33 @@ TRACE_EVENT(landlock_add_rule_fs,
);
/**
- * landlock_add_rule_net - Network port rule added to a ruleset
+ * landlock_add_rule_net_port - Network-port rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
- * @access_rights: Effective access mask stored in the rule, not the raw
- * sys_landlock_add_rule() argument (unhandled rights
- * added).
- * @port: Network port, the landlock_net_port_attr.port UAPI value
- * forwarded directly.
+ * @flags: Complete validated landlock_add_rule_flags value supplied by this
+ * successful call, not the rule's accumulated quiet state.
+ * @access_rights: Canonical per-call access mask passed to
+ * landlock_insert_rule() after normalization, not the raw
+ * sys_landlock_add_rule() argument or accumulated rule.
+ * @port: Network port in host endianness, forwarded directly from
+ * &landlock_net_port_attr.port.
*
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
-TRACE_EVENT(landlock_add_rule_net,
+TRACE_EVENT(landlock_add_rule_net_port,
- TP_PROTO(const struct landlock_ruleset *ruleset,
- access_mask_t access_rights, __u64 port),
+ TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
+ u64 access_rights, u64 port),
- TP_ARGS(ruleset, access_rights, port),
+ TP_ARGS(ruleset, flags, access_rights, port),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, access_rights )
- __field( __u64, port )
+ __field( u64, port )
),
TP_fast_assign(
@@ -495,10 +523,10 @@ TRACE_EVENT(landlock_create_domain,
TP_ARGS(domain, ruleset),
TP_STRUCT__entry(
- __field( __u64, domain_id )
- __field( __u64, parent_id )
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, domain_id )
+ __field( u64, parent_id )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
),
TP_fast_assign(
@@ -557,7 +585,7 @@ TRACE_EVENT(landlock_enforce_domain,
TP_ARGS(domain, complete, process_wide, no_new_privs),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, complete )
__field( bool, process_wide )
__field( bool, no_new_privs )
@@ -595,8 +623,8 @@ TRACE_EVENT(landlock_free_domain,
TP_ARGS(hierarchy),
TP_STRUCT__entry(
- __field( __u64, domain_id )
- __field( __u64, denials )
+ __field( u64, domain_id )
+ __field( u64, denials )
),
TP_fast_assign(
@@ -609,7 +637,7 @@ TRACE_EVENT(landlock_free_domain,
);
/**
- * landlock_check_rule_fs - Filesystem rule evaluated during access check
+ * landlock_check_rule_inode - Inode rule evaluated during access check
*
* @domain: Enforcing domain (never NULL).
* @rule: Matching rule with per-layer access masks (never NULL).
@@ -622,16 +650,16 @@ TRACE_EVENT(landlock_free_domain,
* domain layer. See Documentation/trace/events-landlock.rst for how to
* interpret it.
*/
-TRACE_EVENT(landlock_check_rule_fs,
+TRACE_EVENT(landlock_check_rule_inode,
TP_PROTO(const struct landlock_domain *domain,
const struct landlock_rule *rule,
- access_mask_t access_request, const struct dentry *dentry),
+ u64 access_request, const struct dentry *dentry),
TP_ARGS(domain, rule, access_request, dentry),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( access_mask_t, access_request )
__field( dev_t, dev )
__field( ino_t, ino )
@@ -648,7 +676,8 @@ TRACE_EVENT(landlock_check_rule_fs,
__trace_landlock_fill_layers(__get_dynamic_array(grants),
__get_dynamic_array_len(grants) /
sizeof(access_mask_t),
- rule, access_request);
+ rule,
+ (access_mask_t)access_request);
),
TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s",
@@ -659,7 +688,7 @@ TRACE_EVENT(landlock_check_rule_fs,
);
/**
- * landlock_check_rule_net - Network port rule evaluated during access check
+ * landlock_check_rule_net_port - Network port rule evaluated
*
* @domain: Enforcing domain (never NULL).
* @rule: Matching rule with per-layer access masks (never NULL).
@@ -671,18 +700,18 @@ TRACE_EVENT(landlock_check_rule_fs,
* layer. See Documentation/trace/events-landlock.rst for how to
* interpret it.
*/
-TRACE_EVENT(landlock_check_rule_net,
+TRACE_EVENT(landlock_check_rule_net_port,
TP_PROTO(const struct landlock_domain *domain,
const struct landlock_rule *rule,
- access_mask_t access_request, __u64 port),
+ u64 access_request, u64 port),
TP_ARGS(domain, rule, access_request, port),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( access_mask_t, access_request )
- __field( __u64, port )
+ __field( u64, port )
__dynamic_array(access_mask_t, grants,
domain->num_layers)
),
@@ -695,7 +724,8 @@ TRACE_EVENT(landlock_check_rule_net,
__trace_landlock_fill_layers(__get_dynamic_array(grants),
__get_dynamic_array_len(grants) /
sizeof(access_mask_t),
- rule, access_request);
+ rule,
+ (access_mask_t)access_request);
),
TP_printk("domain=%llx access_request=%s port=%llu grants=%s",
@@ -712,8 +742,7 @@ TRACE_EVENT(landlock_check_rule_net,
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
- * @blockers: Access mask that was blocked (zero for a mount-topology
- * change, whose only blocker is the operation itself).
+ * @blockers: Request type and final missing access subset (never NULL).
* @path: Filesystem path that was denied (never NULL).
* @pathname: Resolved path string (never NULL; an error placeholder on
* resolution failure).
@@ -723,16 +752,17 @@ TRACE_EVENT(landlock_check_rule_net,
TRACE_EVENT(landlock_deny_access_fs,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
- bool logged, access_mask_t blockers, const struct path *path,
- const char *pathname),
+ bool logged, const struct landlock_blockers *blockers,
+ const struct path *path, const char *pathname),
TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( access_mask_t, blockers )
+ __field( enum landlock_request_type, blockers_type )
+ __field( access_mask_t, blockers_access )
__field( dev_t, dev )
__field( ino_t, ino )
__string( pathname, pathname )
@@ -744,7 +774,8 @@ TRACE_EVENT(landlock_deny_access_fs,
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
- __entry->blockers = blockers;
+ __entry->blockers_type = blockers->type;
+ __entry->blockers_access = blockers->access;
__entry->dev = path->dentry->d_sb->s_dev;
/*
* A negative dentry has no backing inode, so mirror the
@@ -756,12 +787,20 @@ TRACE_EVENT(landlock_deny_access_fs,
TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s",
__entry->domain_id, __entry->same_exec, __entry->logged,
- __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES),
+ __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ?
+ __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) :
+ __print_symbolic(__entry->blockers_type,
+ _LANDLOCK_FS_BLOCKER_TYPE_NAMES),
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
__trace_print_untrusted_str(p, __get_str(pathname),
__get_dynamic_array_len(pathname) - 1))
);
+static_assert(offsetof(struct sockaddr_in, sin_port) ==
+ offsetof(struct sockaddr_in6, sin6_port));
+static_assert(sizeof_field(struct sockaddr_in, sin_port) ==
+ sizeof_field(struct sockaddr_in6, sin6_port));
+
/**
* landlock_deny_access_net - Network access denied
*
@@ -769,54 +808,69 @@ TRACE_EVENT(landlock_deny_access_fs,
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
- * @blockers: Access mask that was blocked.
- * @sk: Socket object (never NULL), read without a socket lock, so its
- * fields are a best-effort snapshot. The denied endpoint is not
- * available: the hook runs before :manpage:`bind(2)` /
- * :manpage:`connect(2)` sets the socket addresses.
- * @sport: Source port in host endianness, set for bind denials (zero for
- * an autobind/ephemeral port); zero for connect and send denials.
- * @dport: Destination port in host endianness, set for connect and send
- * denials; zero for bind denials, and also zero for a UDP send to
- * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a
- * real destination port 0). The bind-vs-connect direction is
- * given by @blockers, not by which port is set.
- *
- * Emitted when a Landlock domain denies a network operation.
- *
- * The port fields are converted from the socket's network byte order to
- * host endianness before emitting.
+ * @blockers: Request type and final missing access subset (never NULL).
+ * @sk: Socket object (never NULL), read without a socket lock, so its fields
+ * are a best-effort snapshot.
+ * @socket_family: Socket-family snapshot used by the verdict.
+ * @address: Authoritative address checked by the verdict (never NULL).
+ * The producer copies @addrlen bytes from the checked address and
+ * zeroes the remaining storage before emission. The
+ * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when
+ * present, remains in network endianness.
+ * @addrlen: Validated signed length of @address.
+ *
+ * Emitted when a Landlock domain denies a network operation. The blocker
+ * identifies whether the address is a bind or connect/send policy object.
+ * The flattened port field is converted from the checked address to host
+ * endianness, or is -1 when no port was checked. Zero is a valid checked
+ * port.
*/
TRACE_EVENT(landlock_deny_access_net,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
- bool logged, access_mask_t blockers, const struct sock *sk,
- __u64 sport, __u64 dport),
+ bool logged, const struct landlock_blockers *blockers,
+ const struct sock *sk, u16 socket_family,
+ const struct sockaddr_storage *address, int addrlen),
- TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport),
+ TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family,
+ address, addrlen),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( access_mask_t, blockers )
- __field( __u64, sport )
- __field( __u64, dport )
+ __field( enum landlock_request_type, blockers_type )
+ __field( access_mask_t, blockers_access )
+ __field( s64, port )
),
TP_fast_assign(
+ const struct sockaddr *const addr =
+ (const struct sockaddr *)address;
+ const bool has_port =
+ addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) &&
+ (addr->sa_family == AF_INET ||
+ addr->sa_family == AF_INET6 ||
+ (addr->sa_family == AF_UNSPEC &&
+ socket_family == AF_INET));
+
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
- __entry->blockers = blockers;
- __entry->sport = sport;
- __entry->dport = dport;
+ __entry->blockers_type = blockers->type;
+ __entry->blockers_access = blockers->access;
+ __entry->port =
+ has_port ?
+ ntohs(((const struct sockaddr_in *)addr)->sin_port) :
+ -1;
),
- TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu",
- __entry->domain_id, __entry->same_exec, __entry->logged,
- __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES),
- __entry->sport, __entry->dport)
+ TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld",
+ __entry->domain_id, __entry->same_exec, __entry->logged,
+ __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ?
+ __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) :
+ "unknown",
+ __entry->port)
);
/**
@@ -824,12 +878,14 @@ TRACE_EVENT(landlock_deny_access_net,
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
- * @same_exec: Whether the current task entered the denying domain itself.
+ * @same_exec: Whether the tracer entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
* @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee
* is unsandboxed.
* @tracee: The target task ptrace acted on (never NULL). tracee_pid is
* the init-namespace TGID (like audit's opid).
+ * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME
+ * this is the parent, not the syscall caller.
*
* Emitted when a Landlock domain denies a ptrace operation.
*/
@@ -837,15 +893,16 @@ TRACE_EVENT(landlock_deny_ptrace,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, u64 tracee_domain_id,
- const struct task_struct *tracee),
+ const struct task_struct *tracee,
+ const struct task_struct *tracer),
- TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee),
+ TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, tracee_domain_id)
+ __field( u64, tracee_domain_id)
__field( pid_t, tracee_pid )
__string( tracee_comm, tracee->comm )
),
@@ -872,12 +929,14 @@ TRACE_EVENT(landlock_deny_ptrace,
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
- * @same_exec: Whether the current task entered the denying domain itself.
+ * @same_exec: Whether the policy subject entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
* @target_domain_id: The target's Landlock domain ID, or 0 if the target
* is unsandboxed.
* @target: The task the signal was aimed at (never NULL). target_pid is
* the init-namespace TGID (like audit's opid).
+ * @signal: The signal selected by the denied check. Zero is a permission
+ * probe, not an absent value.
*
* Emitted when a Landlock domain denies signal delivery to a scoped-out
* target.
@@ -886,15 +945,15 @@ TRACE_EVENT(landlock_deny_scope_signal,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, u64 target_domain_id,
- const struct task_struct *target),
+ const struct task_struct *target, int signal),
- TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target),
+ TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, target_domain_id)
+ __field( u64, target_domain_id)
__field( pid_t, target_pid )
__string( target_comm, target->comm )
),
@@ -940,10 +999,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, peer_domain_id )
+ __field( u64, peer_domain_id )
__field( pid_t, peer_pid )
/*
* Abstract socket names are untrusted binary data from
@@ -973,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
__entry->logged = logged;
__entry->peer_domain_id = peer_domain_id;
/*
- * Best-effort (0 for a datagram peer). sk_peer_pid is
- * canonically guarded by sk->sk_peer_lock, but the target
- * peer's peercred is set once and not updated concurrently in
- * these hooks, so this READ_ONCE() is safe; sun_path is the
- * reliable identifier.
+ * Best-effort (0 for a datagram peer). The caller holds the
+ * peer's AF_UNIX state lock, serializing published peercred
+ * updates. The peer socket keeps a reference to sk_peer_pid
+ * through pid_nr(); sun_path is the reliable identifier.
*/
peer_pid = READ_ONCE(peer->sk_peer_pid);
__entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0;
@@ -991,6 +1049,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
__get_dynamic_array_len(sun_path) - 1))
);
+#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES
#undef _LANDLOCK_NAME_ENTRY
#endif /* _TRACE_LANDLOCK_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 330a1871bf94a..cab43892ec2f0 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
* held for BTF consistency (enforced by lockdep_assert_held in
* TP_fast_assign).
*/
- if (!err && trace_landlock_add_rule_fs_enabled()) {
+ if (!err && trace_landlock_add_rule_path_beneath_enabled()) {
char *buffer __free(__putname) = __getname();
const char *pathname =
buffer ? resolve_path_for_trace(path, buffer) :
"<no_mem>";
- trace_landlock_add_rule_fs(ruleset, access_rights, path,
- pathname);
+ trace_landlock_add_rule_path_beneath(
+ ruleset, flags, access_rights, path, pathname);
}
mutex_unlock(&ruleset->lock);
@@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain,
ret = landlock_unmask_layers(domain, id, masks, &rule);
if (rule)
- trace_landlock_check_rule_fs(domain, rule, access_request,
- dentry);
+ trace_landlock_check_rule_inode(domain, rule, access_request,
+ dentry);
return ret;
}
diff --git a/security/landlock/log.h b/security/landlock/log.h
index e0a6e44f3ddd3..faa30e26e42a3 100644
--- a/security/landlock/log.h
+++ b/security/landlock/log.h
@@ -15,6 +15,8 @@
struct landlock_cred_security;
struct landlock_hierarchy;
+struct sockaddr;
+struct task_struct;
enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
@@ -25,6 +27,31 @@ enum landlock_request_type {
LANDLOCK_REQUEST_SCOPE_SIGNAL,
};
+struct landlock_blockers {
+ access_mask_t access;
+ enum landlock_request_type type;
+};
+
+#ifdef CONFIG_TRACEPOINTS
+
+struct landlock_net_trace {
+ const struct sockaddr *address;
+ int addrlen;
+ u16 socket_family;
+};
+
+struct landlock_ptrace_trace {
+ u64 tracee_domain_id;
+ const struct task_struct *tracer;
+};
+
+struct landlock_signal_trace {
+ u64 target_domain_id;
+ int signal;
+};
+
+#endif /* CONFIG_TRACEPOINTS */
+
/*
* We should be careful to only use a variable of this type for
* landlock_log_denial(). This way, the compiler can remove it entirely if
@@ -52,13 +79,24 @@ struct landlock_request {
deny_masks_t deny_masks;
optional_access_t quiet_optional_accesses;
- /*
- * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if
- * that party is unsandboxed. An ID, not a pointer: the other task can
- * replace its credential and free the domain it referenced. Trace path
- * only; audit ignores it.
- */
- u64 other_domain_id;
+#ifdef CONFIG_TRACEPOINTS
+ union {
+ /*
+ * Other-party domain ID for an abstract UNIX socket scope
+ * denial, or 0 if that party is unsandboxed. Store an ID, not
+ * a pointer: the other task can replace its credential and free
+ * the domain it referenced.
+ */
+ u64 other_domain_id;
+
+ /* Synchronous context for a network denial. */
+ const struct landlock_net_trace *trace_net;
+ /* Synchronous context for a ptrace denial. */
+ const struct landlock_ptrace_trace *trace_ptrace;
+ /* Synchronous context for a signal denial. */
+ const struct landlock_signal_trace *trace_signal;
+ };
+#endif /* CONFIG_TRACEPOINTS */
};
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 8f2aaac54b334..6fe0dbde3b787 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
* TP_fast_assign).
*/
if (!err)
- trace_landlock_add_rule_net(ruleset, access_rights, port);
+ trace_landlock_add_rule_net_port(ruleset, flags, access_rights,
+ port);
mutex_unlock(&ruleset->lock);
return err;
@@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain,
ret = landlock_unmask_layers(domain, id, masks, &rule);
if (rule)
- trace_landlock_check_rule_net(
+ trace_landlock_check_rule_net_port(
domain, rule, access_request,
ntohs((__force __be16)id.key.data));
return ret;
@@ -92,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock,
return 0;
/* Checks for minimal header length to safely read sa_family. */
- if (addrlen < offsetofend(typeof(*address), sa_family))
+ if (addrlen < (int)offsetofend(typeof(*address), sa_family))
return -EINVAL;
/*
@@ -144,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock,
.audit.u.net = &audit_net,
.access = access_request,
.layer_masks = &layer_masks,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_net =
+ &(struct landlock_net_trace){
+ .address =
+ address,
+ .addrlen =
+ addrlen,
+ .socket_family =
+ sock_family,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EACCES;
}
@@ -275,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock,
audit_net.family = address->sa_family;
audit_net.sk = sock->sk;
- landlock_log_denial(subject,
- &(struct landlock_request){
- .type = LANDLOCK_REQUEST_NET_ACCESS,
- .audit.type = LSM_AUDIT_DATA_NET,
- .audit.u.net = &audit_net,
- .access = access_request,
- .layer_masks = &layer_masks,
- });
+ landlock_log_denial(
+ subject, &(struct landlock_request){
+ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ .audit.type = LSM_AUDIT_DATA_NET,
+ .audit.u.net = &audit_net,
+ .access = access_request,
+ .layer_masks = &layer_masks,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_net =
+ &(struct landlock_net_trace){
+ .address = address,
+ .addrlen = addrlen,
+ .socket_family = sock_family,
+ },
+#endif /* CONFIG_TRACEPOINTS */
+ });
return -EACCES;
}
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 4491ce31ae04a..d9eae86fc5529 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child,
const unsigned int mode)
{
const struct landlock_cred_security *parent_subject;
+#ifdef CONFIG_TRACEPOINTS
u64 tracee_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
int err;
/* Quick return for non-landlocked tasks. */
@@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child,
const struct landlock_domain *const child_dom =
landlock_get_task_domain(child);
err = domain_ptrace(parent_subject->domain, child_dom);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (child_dom)
tracee_domain_id = child_dom->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!err)
@@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child,
.u.tsk = child,
},
.layer_plus_one = parent_subject->domain->num_layers,
- .other_domain_id = tracee_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_ptrace = &(struct landlock_ptrace_trace) {
+ .tracee_domain_id = tracee_domain_id,
+ .tracer = current,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return err;
@@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
{
const struct landlock_cred_security *parent_subject;
const struct landlock_domain *child_dom;
- u64 tracee_domain_id = 0;
int err;
child_dom = landlock_get_current_domain();
@@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
if (!err)
return 0;
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- /* The tracee is the current task; its domain is stable here. */
- if (child_dom)
- tracee_domain_id = child_dom->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
-
/*
* For the ptrace_traceme case, we log the domain which is the cause of
* the denial, which means the parent domain instead of the current
@@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
.u.tsk = current,
},
.layer_plus_one = parent_subject->domain->num_layers,
- .other_domain_id = tracee_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_ptrace = &(struct landlock_ptrace_trace) {
+ /* The current task's domain is stable here. */
+ .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0,
+ .tracer = parent,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return err;
}
@@ -250,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client,
}
static bool sock_is_scoped(struct sock *const other,
- const struct landlock_domain *const domain,
- u64 *const peer_domain_id)
+ const struct landlock_domain *const domain)
{
const struct landlock_domain *dom_other;
@@ -269,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other,
return false;
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return domain_is_scoped(domain, dom_other,
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
}
+#ifdef CONFIG_TRACEPOINTS
+
+static u64 get_socket_domain_id(const struct sock *const other)
+{
+ const struct landlock_domain *domain;
+
+ lockdep_assert_held(&unix_sk(other)->lock);
+ domain = landlock_cred(other->sk_socket->file->f_cred)->domain;
+ return domain ? domain->hierarchy->id : 0;
+}
+
+#endif /* CONFIG_TRACEPOINTS */
+
static bool is_abstract_socket(struct sock *const sock)
{
struct unix_address *addr = unix_sk(sock)->addr;
@@ -299,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const newsk)
{
size_t handle_layer;
- u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -311,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
if (!is_abstract_socket(other))
return 0;
- if (!sock_is_scoped(other, subject->domain, &peer_domain_id))
+ if (!sock_is_scoped(other, subject->domain))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -323,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = peer_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .other_domain_id = get_socket_domain_id(other),
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -332,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock,
struct socket *const other)
{
size_t handle_layer;
- u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -350,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock,
if (!is_abstract_socket(other->sk))
return 0;
- if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id))
+ if (!sock_is_scoped(other->sk, subject->domain))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -362,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = peer_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .other_domain_id = get_socket_domain_id(other->sk),
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -377,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p,
{
bool is_scoped;
size_t handle_layer;
+#ifdef CONFIG_TRACEPOINTS
u64 target_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
const struct landlock_cred_security *subject;
if (!cred) {
@@ -409,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p,
is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (other)
target_domain_id = other->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!is_scoped)
@@ -425,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p,
.u.tsk = p,
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = target_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_signal = &(struct landlock_signal_trace) {
+ .target_domain_id = target_domain_id,
+ .signal = sig,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -435,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
{
const struct landlock_cred_security *subject;
bool is_scoped = false;
+#ifdef CONFIG_TRACEPOINTS
u64 target_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
/* Lock already held by send_sigio() and send_sigurg(). */
lockdep_assert_held(&fown->lock);
@@ -468,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (other)
target_domain_id = other->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!is_scoped)
@@ -486,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
- .other_domain_id = target_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_signal = &(struct landlock_signal_trace) {
+ .target_domain_id = target_domain_id,
+ .signal = signum ? signum : SIGIO,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
diff --git a/security/landlock/trace.c b/security/landlock/trace.c
index 8c21e5de6f0de..225dbf37bab09 100644
--- a/security/landlock/trace.c
+++ b/security/landlock/trace.c
@@ -12,6 +12,7 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/lsm_audit.h>
+#include <linux/socket.h>
#include <net/sock.h>
#include "access.h"
@@ -61,8 +62,8 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy
*
* @request: Detail of the user space request.
* @youngest_denied: The youngest hierarchy node that denied the access.
- * @missing: The set of denied access rights.
- * @same_exec: Whether the current task is the same executable that called
+ * @missing: The final missing access 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
@@ -83,6 +84,10 @@ void landlock_trace_denial(
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;
@@ -147,29 +152,67 @@ void landlock_trace_denial(
trace_landlock_deny_access_fs(youngest_denied,
same_exec, logged,
- missing, path, pathname);
+ &blockers, path,
+ pathname);
}
break;
case LANDLOCK_REQUEST_NET_ACCESS:
- if (trace_landlock_deny_access_net_enabled())
+ 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, missing,
+ youngest_denied, same_exec, logged, &blockers,
request->audit.u.net->sk,
- ntohs(request->audit.u.net->sport),
- ntohs(request->audit.u.net->dport));
+ trace_net->socket_family, &address,
+ trace_net->addrlen);
+ }
break;
case LANDLOCK_REQUEST_PTRACE:
- if (trace_landlock_deny_ptrace_enabled())
- trace_landlock_deny_ptrace(youngest_denied, same_exec,
- logged,
- request->other_domain_id,
- request->audit.u.tsk);
+ 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())
+ 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,
- request->other_domain_id, request->audit.u.tsk);
+ 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())
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 18dbdb99aebab..6e979cef884d1 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1)
}
/*
- * Verifies that check_rule_fs events include correct field values: domain, dev,
- * ino, access_request, and grants. All values are verified against stat() of
- * the rule path on a deterministic tmpfs layout.
+ * Verifies that check_rule_inode events include correct field values: domain,
+ * dev, ino, access_request, and grants. All values are verified against stat()
+ * of the rule path on a deterministic tmpfs layout.
*/
TEST_F(trace_layout1, check_rule_fs_fields)
{
@@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields)
EXPECT_EQ(1,
tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)))
{
- TH_LOG("Expected 1 check_rule_fs event\n%s", buf);
+ TH_LOG("Expected 1 check_rule_inode event\n%s", buf);
}
ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
@@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields)
}
/*
- * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and
- * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the
+ * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1
+ * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the
* pathwalk short-circuits after the first rule fully unmasks the single layer.
*/
TEST_F(trace_layout1, check_rule_fs_multiple_rules)
@@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules)
ASSERT_NE(NULL, buf);
/*
- * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the
- * single layer, so the pathwalk short-circuits before reaching the
+ * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks
+ * the single layer, so the pathwalk short-circuits before reaching the
* dir_s1d1 rule.
*/
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count,
+ TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count,
buf);
}
@@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count,
+ TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count,
buf);
}
@@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)
}
/*
- * Verifies that check_rule_fs fires for a rule that matches the inode even when
+ * Verifies that check_rule_inode fires for a rule matching the inode even when
* it grants none of the requested rights, so the grants set is empty. Landlock
* cannot know a rule ignores the request before reading it, so the event is
* still emitted (grants={}), which lets a tracer see that the rule matched.
@@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(2, count)
{
- TH_LOG("Expected 2 check_rule_fs events, got %d\n%s", count,
+ TH_LOG("Expected 2 check_rule_inode events, got %d\n%s", count,
buf);
}
@@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
tracefs_count_matches(
buf,
TRACE_PREFIX(
- TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ "
+ TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ "
"access_request=read_dir "
"dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ "
"grants={}$"))
@@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
tracefs_count_matches(
buf,
TRACE_PREFIX(
- TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ "
+ TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ "
"access_request=read_dir "
"dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ "
"grants={read_dir}$"))
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index a18761e0fd82f..16afbfdf06bb2 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind)
}
/*
- * Anchors the denial fields shared by every deny_access_net event so a field
- * test proves more than sport/dport: the denying domain, the same-exec bit, the
- * audit-logging verdict, and the blocked access all stay populated.
+ * Anchors the denial fields shared by every deny_access_net event so a port
+ * test also proves the denying domain, execution status, logging verdict, and
+ * exact blocked access.
*/
static void
expect_net_deny_common_fields(struct __test_metadata *const _metadata,
- const char *const buf)
+ const char *const buf,
+ const char *const expected_blockers)
{
char field[64];
@@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata,
ASSERT_EQ(0,
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
"blockers", field, sizeof(field)));
- EXPECT_STRNE("", field);
+ EXPECT_STREQ(expected_blockers, field);
}
-/* Connect and field-check tests use a separate fixture without variants. */
+enum trace_net_operation {
+ TRACE_NET_BIND,
+ TRACE_NET_SEND,
+};
/* clang-format off */
-FIXTURE(trace_net_connect) {
+FIXTURE(trace_net_address) {
/* clang-format on */
int tracefs_ok;
};
-FIXTURE_SETUP(trace_net_connect)
+FIXTURE_SETUP(trace_net_address)
{
int ret;
@@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect)
clear_cap(_metadata, CAP_SYS_ADMIN);
}
-FIXTURE_TEARDOWN(trace_net_connect)
+FIXTURE_TEARDOWN(trace_net_address)
{
if (!self->tracefs_ok)
return;
@@ -3559,160 +3563,183 @@ FIXTURE_TEARDOWN(trace_net_connect)
}
/* clang-format off */
-FIXTURE_VARIANT(trace_net_connect) {
+FIXTURE_VARIANT(trace_net_address) {
/* clang-format on */
- /* handled_access_net, also the access allowed on the base port. */
- __u64 handled;
- /* Bind the allowed base port before the denied operation. */
- bool bind_base_first;
- /* Denied operation on the next port: connect (true) or bind (false). */
- bool deny_connect;
+ int socket_family;
+ int socket_type;
+ enum trace_net_operation operation;
+ int address_family;
+ socklen_t addrlen;
+ __u64 handled_access;
+ const char *expected_blockers;
+ bool address_port_zero;
+ bool expected_address_port;
+ int expected_port;
};
/* clang-format off */
-
-/* Denied connect(): sport=0, dport=<denied port>. */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) {
- .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP,
- .bind_base_first = false,
- .deny_connect = true,
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) {
+ /* clang-format on */
+ .socket_family = AF_INET,
+ .socket_type = SOCK_STREAM,
+ .operation = TRACE_NET_BIND,
+ .address_family = AF_INET,
+ .addrlen = sizeof(struct sockaddr_in),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+ .expected_blockers = "bind_tcp",
+ .expected_address_port = true,
};
-/* Denied bind(): sport=<denied port>, dport=0. */
-FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) {
- .handled = LANDLOCK_ACCESS_NET_BIND_TCP,
- .bind_base_first = false,
- .deny_connect = false,
+/* Explicit bind(0) has a checked zero port. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) {
+ /* clang-format on */
+ .socket_family = AF_INET,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_BIND,
+ .address_family = AF_INET,
+ .addrlen = sizeof(struct sockaddr_in),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+ .expected_blockers = "bind_udp",
+ .address_port_zero = true,
+ .expected_port = 0,
+};
+
+/* A UDP send can deny its synthetic unspecified bind endpoint. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) {
+ /* clang-format on */
+ .socket_family = AF_INET6,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_SEND,
+ .address_family = AF_INET6,
+ .addrlen = sizeof(struct sockaddr_in6),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+ .expected_blockers = "bind_udp",
+ .expected_port = 0,
};
-/* Denied connect() after an allowed bind(): the connect fields (sport=0). */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) {
- .handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP,
- .bind_base_first = true,
- .deny_connect = true,
+/* A family-only address has no checked port. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) {
+ /* clang-format on */
+ .socket_family = AF_INET6,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_SEND,
+ .address_family = AF_UNSPEC,
+ .addrlen = sizeof(sa_family_t),
+ .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ .expected_blockers = "connect_send_udp",
+ .expected_port = -1,
};
-/* clang-format on */
+static void set_trace_net_address(struct sockaddr_storage *const storage,
+ const int socket_family,
+ const int address_family,
+ const unsigned short port)
+{
+ memset(storage, 0, sizeof(*storage));
-/*
- * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port
- * is reported in the field matching the denied operation, in host endianness
- * (the UAPI landlock_net_port_attr.port convention): a connect denial reports
- * sport=0 dport=<port>, a bind denial reports sport=<port> dport=0, so a
- * byte-order or field-swap bug is caught. A prior allowed bind
- * (connect_after_bind) does not change the connect denial's fields.
- */
-TEST_F(trace_net_connect, deny_access_net)
+ if (socket_family == AF_INET) {
+ struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage;
+
+ addr4->sin_family = address_family;
+ addr4->sin_port = htons(port);
+ addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else {
+ struct sockaddr_in6 *const addr6 =
+ (struct sockaddr_in6 *)storage;
+
+ addr6->sin6_family = address_family;
+ addr6->sin6_port = htons(port);
+ addr6->sin6_addr = in6addr_loopback;
+ }
+}
+
+/* Verifies the actionable signed port for representative checked shapes. */
+TEST_F(trace_net_address, deny_access_net)
{
- pid_t child;
- int status;
- char *buf;
+ const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK);
+ const unsigned short address_port =
+ variant->address_port_zero ? 0 : sock_port_start + 1;
+ const int expected_port = variant->expected_address_port ?
+ address_port :
+ variant->expected_port;
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = variant->handled_access,
+ };
+ struct sockaddr_storage address;
char field[64], expected[16];
+ char *buf;
+ int count, ret, ruleset_fd, socket_fd, status;
+ pid_t child;
if (!self->tracefs_ok)
SKIP(return, "tracefs not available");
+ set_trace_net_address(&address, variant->socket_family,
+ variant->address_family, address_port);
+ socket_fd = socket(variant->socket_family,
+ variant->socket_type | SOCK_CLOEXEC, 0);
+ ASSERT_LE(0, socket_fd);
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, tracefs_clear_buf());
+
child = fork();
ASSERT_LE(0, child);
-
if (child == 0) {
- struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = variant->handled,
- };
- struct landlock_net_port_attr port_attr = {
- .allowed_access = variant->handled,
- .port = sock_port_start,
- };
- struct sockaddr_in addr = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
- };
- int ruleset_fd, sock_fd, optval = 1, ret;
-
- ruleset_fd = landlock_create_ruleset(&ruleset_attr,
- sizeof(ruleset_attr), 0);
- if (ruleset_fd < 0)
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
_exit(1);
- if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &port_attr, 0)) {
- close(ruleset_fd);
- _exit(1);
- }
- prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
- if (landlock_restrict_self(ruleset_fd, 0)) {
- close(ruleset_fd);
- _exit(1);
- }
+ if (landlock_restrict_self(ruleset_fd, 0))
+ _exit(2);
close(ruleset_fd);
- sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
- if (sock_fd < 0)
- _exit(1);
-
- /* Bind the allowed base port first (succeeds, no event). */
- if (variant->bind_base_first) {
- setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
- sizeof(optval));
- addr.sin_port = htons(sock_port_start);
- if (bind(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr))) {
- close(sock_fd);
- _exit(1);
- }
- }
-
- /* Denied operation on the next port. */
- addr.sin_port = htons(sock_port_start + 1);
- if (variant->deny_connect)
- ret = connect(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr));
- else
- ret = bind(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr));
- if (ret == 0) {
- close(sock_fd);
- _exit(2);
- }
- if (errno != EACCES) {
- close(sock_fd);
+ switch (variant->operation) {
+ case TRACE_NET_BIND:
+ ret = bind(socket_fd, (const struct sockaddr *)&address,
+ variant->addrlen);
+ break;
+ case TRACE_NET_SEND:
+ ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL,
+ (const struct sockaddr *)&address,
+ variant->addrlen);
+ break;
+ default:
_exit(3);
}
- close(sock_fd);
+ if (ret >= 0 || errno != EACCES)
+ _exit(4);
+ close(socket_fd);
+
_exit(0);
}
+ close(ruleset_fd);
+ close(socket_fd);
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_TRUE(WIFEXITED(status));
- EXPECT_EQ(0, WEXITSTATUS(status));
+ ASSERT_EQ(0, WEXITSTATUS(status));
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
-
- EXPECT_EQ(1, tracefs_count_matches(buf,
- REGEX_DENY_ACCESS_NET(TRACE_TASK)));
-
- expect_net_deny_common_fields(_metadata, buf);
-
- /*
- * The denied operation's port field carries the port; the other is 0.
- */
- snprintf(expected, sizeof(expected), "%llu",
- (unsigned long long)(sock_port_start + 1));
-
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
- "sport", field, sizeof(field)));
- EXPECT_STREQ(variant->deny_connect ? "0" : expected, field);
-
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
- "dport", field, sizeof(field)));
- EXPECT_STREQ(variant->deny_connect ? expected : "0", field);
+ count = tracefs_count_matches(buf, event_regex);
+ if (count != 1)
+ TH_LOG("Expected 1 denial event, got %d\n%s", count, buf);
+ ASSERT_EQ(1, count);
+ expect_net_deny_common_fields(_metadata, buf,
+ variant->expected_blockers);
+
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field,
+ sizeof(field)));
+ snprintf(expected, sizeof(expected), "%d", expected_port);
+ EXPECT_STREQ(expected, field);
free(buf);
}
-/* Field verification for the check_rule_net event on an allowed access. */
+/* Field verification for the check_rule_net_port event on an allowed access. */
/* clang-format off */
FIXTURE(trace_net_check_rule) {
@@ -3757,10 +3784,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule)
/*
* Verifies that an allowed bind matching a net-port rule emits exactly one
- * landlock_check_rule_net event with the enforcing domain, the requested
+ * landlock_check_rule_net_port event with the enforcing domain, the requested
* access, the checked port (host endianness), and the per-layer grants. The
- * whole event is anchored to exact values so a revert of the check_rule_net
- * emit (or a byte-order or field-plumbing regression) fails the test.
+ * whole event is anchored to exact values so removing the check_rule_net_port
+ * emission or introducing a byte-order or field-plumbing regression fails the
+ * test.
*/
TEST_F(trace_net_check_rule, check_rule_net_fields)
{
@@ -3832,7 +3860,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)
EXPECT_EQ(1,
tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK)))
{
- TH_LOG("Expected 1 check_rule_net event\n%s", buf);
+ TH_LOG("Expected 1 check_rule_net_port event\n%s", buf);
}
ASSERT_EQ(0,
@@ -3866,11 +3894,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)
free(buf);
}
-/*
- * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses
- * the same current_check_access_socket() code path as IPv4, validated by the
- * audit tests in this file. The trace events use the same blockers/sport/dport
- * fields regardless of address family.
- */
-
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h
index ba0c5e92001f1..2ec8633621732 100644
--- a/tools/testing/selftests/landlock/trace.h
+++ b/tools/testing/selftests/landlock/trace.h
@@ -27,14 +27,14 @@
TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable"
#define TRACEFS_ENFORCE_DOMAIN_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_enforce_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_ADD_RULE_PATH_BENEATH_ENABLE \
+ TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable"
+#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \
+ TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable"
#define TRACEFS_CHECK_RULE_FS_ENABLE \
- TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable"
+ TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable"
#define TRACEFS_CHECK_RULE_NET_ENABLE \
- TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable"
+ TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable"
#define TRACEFS_DENY_ACCESS_FS_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable"
#define TRACEFS_DENY_ACCESS_NET_ENABLE \
@@ -79,18 +79,18 @@
*/
#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]\\+ " \
+#define REGEX_ADD_RULE_PATH_BENEATH(task) \
+ TRACE_PREFIX(task) \
+ "landlock_add_rule_path_beneath: " \
+ "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) \
+#define REGEX_ADD_RULE_NET_PORT(task) \
TRACE_PREFIX(task) \
- "landlock_add_rule_net: " \
+ "landlock_add_rule_net_port: " \
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
"access_rights=[a-z_|]* " \
"port=[0-9]\\+$"
@@ -110,21 +110,21 @@
"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]\\+ " \
+#define REGEX_CHECK_RULE_FS(task) \
+ TRACE_PREFIX(task) \
+ "landlock_check_rule_inode: " \
+ "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]\\+ " \
+#define REGEX_CHECK_RULE_NET(task) \
+ TRACE_PREFIX(task) \
+ "landlock_check_rule_net_port: " \
+ "domain=[0-9a-f]\\+ " \
+ "access_request=[a-z_|]* " \
+ "port=[0-9]\\+ " \
"grants={[a-z_|,]*}$"
#define REGEX_DENY_ACCESS_FS(task) \
@@ -145,8 +145,7 @@
"same_exec=[01] " \
"logged=[01] " \
"blockers=[a-z_|]* " \
- "sport=[0-9]\\+ " \
- "dport=[0-9]\\+$"
+ "port=-\\?[0-9]\\+$"
#define REGEX_DENY_PTRACE(task) \
TRACE_PREFIX(task) \
diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c
index 4543a25c1f559..64014ade3a0e9 100644
--- a/tools/testing/selftests/landlock/trace_fs_test.c
+++ b/tools/testing/selftests/landlock/trace_fs_test.c
@@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs)
}
self->tracefs_ok = 1;
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_clear());
@@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs)
return;
set_cap(_metadata, CAP_SYS_ADMIN);
- tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
tracefs_fixture_teardown();
@@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed)
}
/*
- * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace
- * event with the expected path and field values: ruleset ID is non-zero,
- * access_rights is non-zero, and path matches.
+ * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath
+ * event with the expected path and field values: the ruleset ID and
+ * access_rights are non-zero, and the path matches.
*/
-TEST_F(trace_fs, add_rule_fs)
+TEST_F(trace_fs, add_rule_path_beneath)
{
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
@@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ count = tracefs_count_matches(buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf);
+ TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s",
+ count, buf);
}
/* Ruleset ID should be non-zero. */
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "ruleset", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "ruleset", field_buf, sizeof(field_buf)));
EXPECT_STRNE("0", field_buf);
/* Access rights should be non-zero. */
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "access_rights", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "access_rights", field_buf, sizeof(field_buf)));
EXPECT_STRNE("", field_buf);
/* Path should be /usr. */
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "path", field_buf, sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "path", field_buf, sizeof(field_buf)));
EXPECT_STREQ("/usr", field_buf);
free(buf);
@@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs)
* Verifies that a path whose escaping exceeds the trace scratch sequence does
* not corrupt a sibling symbolic field.
*/
-TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
+TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow)
{
static const char access_prefix[] = "execute|write_file|read_file|";
static const char access_suffix[] = "|ioctl_dev|resolve_unix";
@@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ count = tracefs_count_matches(buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf);
+ TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s",
+ count, buf);
}
/*
@@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
* field also catches scratch-sequence poisoning when the compiler
* evaluates the overflowing path first, as GCC currently does.
*/
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "access_rights", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "access_rights", field_buf, sizeof(field_buf)));
EXPECT_EQ(0,
strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1));
EXPECT_EQ(NULL, strstr(field_buf, "|refer|"));
@@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
ASSERT_LE(sizeof(access_suffix) - 1, field_len);
EXPECT_STREQ(access_suffix,
field_buf + field_len - (sizeof(access_suffix) - 1));
- expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ expect_truncated_path(_metadata, buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
free(buf);
}
@@ -542,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested)
*/
TEST_F(trace_fs, deny_access_fs_denied)
{
- char *buf;
+ const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+ char *buf, blockers[64];
int count;
ASSERT_EQ(0, tracefs_clear_buf());
@@ -558,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
- EXPECT_LE(1, count);
+ count = tracefs_count_matches(buf, event_regex);
+ EXPECT_EQ(1, count)
+ {
+ TH_LOG("Expected 1 access denial, got %d\n%s", count, buf);
+ }
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+ blockers, sizeof(blockers)));
+ EXPECT_STREQ("read_dir", blockers);
+
+ free(buf);
+}
+
+/*
+ * Verifies that a denied mount reports the singleton topology blocker rather
+ * than an empty access mask.
+ */
+TEST_F(trace_fs, deny_change_topology)
+{
+ const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ };
+ char *buf, blockers[64];
+ int count, ruleset_fd, status;
+ pid_t pid;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, tracefs_clear_buf());
+
+ /* Ensure that Landlock is the only expected mount denial. */
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ pid = fork();
+ ASSERT_LE(0, pid);
+ if (pid == 0) {
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ close(ruleset_fd);
+ _exit(1);
+ }
+ if (landlock_restrict_self(ruleset_fd, 0)) {
+ close(ruleset_fd);
+ _exit(2);
+ }
+ close(ruleset_fd);
+
+ if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1)
+ _exit(3);
+
+ if (errno != EPERM)
+ _exit(4);
+
+ _exit(0);
+ }
+ close(ruleset_fd);
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+
+ ASSERT_EQ(pid, waitpid(pid, &status, 0));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ buf = tracefs_read_buf();
+ ASSERT_NE(NULL, buf);
+ count = tracefs_count_matches(buf, event_regex);
+ EXPECT_EQ(1, count)
+ {
+ TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf);
+ }
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+ blockers, sizeof(blockers)));
+ EXPECT_STREQ("change_topology", blockers);
free(buf);
}
diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c
index afdaf8511b3a1..f9b293a9dd565 100644
--- a/tools/testing/selftests/landlock/trace_test.c
+++ b/tools/testing/selftests/landlock/trace_test.c
@@ -49,8 +49,10 @@ FIXTURE_SETUP(trace)
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ true));
+ ASSERT_EQ(0,
+ tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
@@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace)
tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
- tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
- tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);
tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
@@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled)
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));
ASSERT_EQ(0,
tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ false));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE,
+ false));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));
ASSERT_EQ(0,
tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false));
@@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version)
ASSERT_NE(0, !!dot);
EXPECT_STREQ("0", dot + 1);
- /* Verify 2 add_rule_fs events were emitted. */
- EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
+ /* Verify two add_rule_path_beneath events were emitted. */
+ EXPECT_EQ(2, tracefs_count_matches(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
{
- TH_LOG("Expected 2 add_rule_fs events\n%s", buf);
+ TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf);
}
/*
@@ -373,7 +378,7 @@ TEST_F(trace, create_domain)
tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
ASSERT_LE(1, check_count)
{
- TH_LOG("Expected check_rule_fs events\n%s", buf);
+ TH_LOG("Expected check_rule_inode events\n%s", buf);
}
EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
@@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
+ EXPECT_EQ(0, tracefs_count_matches(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
{
- TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf);
+ TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s",
+ buf);
}
free(buf);
@@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting)
}
/*
- * Verifies that landlock_add_rule_net emits a trace event with the correct port
- * and allowed access mask fields.
+ * Verifies that landlock_add_rule_net_port emits a trace event with the correct
+ * port and allowed access mask fields.
*/
-TEST_F(trace, add_rule_net_fields)
+TEST_F(trace, add_rule_net_port_fields)
{
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
@@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK)))
+ EXPECT_EQ(1, tracefs_count_matches(buf,
+ REGEX_ADD_RULE_NET_PORT(TRACE_TASK)))
{
- TH_LOG("Expected 1 add_rule_net event\n%s", buf);
+ TH_LOG("Expected 1 add_rule_net_port event\n%s", buf);
}
/*
@@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields)
* (landlock_net_port_attr.port). On little-endian, htons(8080) is
* 36895, so this comparison catches byte-order bugs.
*/
- EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
+ EXPECT_EQ(0, tracefs_extract_field(buf,
+ REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
"port", field, sizeof(field)));
EXPECT_STREQ("8080", field);
/*
@@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields)
* net access bits are unhandled because the ruleset only handles
* BIND_TCP).
*/
- EXPECT_EQ(0,
- tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
- "access_rights", field, sizeof(field)));
+ EXPECT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
+ "access_rights", field, sizeof(field)));
EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field);
free(buf);
|
| 3/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 33670 output: 31 thoughts: 1366 Thoughts: **Fuzzing Evaluation: Landlock Patch Series** Okay, so I'm looking at this Landlock patch series and the question is, does this warrant a syzkaller fuzzing run? I need to figure out if it introduces any exploitable bugs. First things first, I see the patch touches a bunch of Landlock files, including documentation, headers, and code within `security/landlock/`. Let me analyze it. The patch seems to be refactoring tracepoints, which is generally fine, but I know better than to take things at face value. Specifically, there is a rename of several trace events: looks benign. However, I need to dig deeper. I see changes to a few `landlock_deny_access_*` functions. These now take additional arguments like `blockers`, addresses, and a `tracer`. In `security/landlock/log.h`, I see additions of `struct landlock_blockers` and tracing structs, and the `struct landlock_request` union is modified. That suggests there's a modification in data structures, which is something I need to pay close attention to. But the real kicker is the code in `security/landlock/task.c`. I spot a call to `get_socket_domain_id`, and that function has a `lockdep_assert_held` and a bunch of pointer dereferences, including `other->sk_socket->file->f_cred`. I immediately get a red flag: what happens if any of those pointers are null? I also have to check the usage of `sock_is_scoped`. This seems like a potential memory safety hazard, so it's a critical area to analyze. Then there's the `security/landlock/trace.c` file. I see the addition of `WARN_ON_ONCE` checks, particularly on `trace_net` and `trace_ptrace`, and `trace_signal` which means it alters control flow or data structures. The presence of these runtime checks really piques my interest. It triggers the fuzzing guidelines: "NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED" and "CORE SUBSYSTEMS". Landlock fits this category. So the million dollar question is, am I just looking at tracepoint modifications, or is this actually touching kernel logic? The code I saw in `task.c`, the new structs, the `WARN_ON_ONCE` checks—it’s not purely decorative logging. It's definitely kernel code with new assertions and potential for new invariants. I have to check `security/landlock/` to be sure. My initial assessment is that this patch series *does* warrant fuzzing with syzkaller. The introduction of `WARN_ON_ONCE` checks, coupled with the potential null pointer dereferences and changes to data structures in a security-sensitive subsystem like Landlock, means there's a real possibility of uncovering subtle bugs. I need to take a closer look at the actual code and how the new arguments are used. |
| 4/2 | 2026/09/20 23:54 | tool | grepper |
0mArgs:{
"Expression": "landlock_trace_denial",
"PathPrefix": "security/landlock"
}Results: {
"Output": "security/landlock/log.c=496=void landlock_log_denial(const struct landlock_cred_security *const subject,\n--\nsecurity/landlock/log.c-546-\nsecurity/landlock/log.c:547:\tlandlock_trace_denial(request, youngest_denied, missing, same_exec,\nsecurity/landlock/log.c-548-\t\t\t logged);\n--\nsecurity/landlock/trace.c=47=void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy)\n--\nsecurity/landlock/trace.c-60-/**\nsecurity/landlock/trace.c:61: * landlock_trace_denial - Emit a tracepoint for a denied access request\nsecurity/landlock/trace.c-62- *\n--\nsecurity/landlock/trace.c-77- */\nsecurity/landlock/trace.c:78:void landlock_trace_denial(\nsecurity/landlock/trace.c-79-\tconst struct landlock_request *const request,\n--\nsecurity/landlock/trace.h=19=void landlock_trace_free_domain(\n--\nsecurity/landlock/trace.h-21-\nsecurity/landlock/trace.h:22:void landlock_trace_denial(\nsecurity/landlock/trace.h-23-\tconst struct landlock_request *const request,\n--\nsecurity/landlock/trace.h=34=static inline void\nsecurity/landlock/trace.h:35:landlock_trace_denial(const struct landlock_request *const request,\nsecurity/landlock/trace.h-36-\t\t const struct landlock_hierarchy *const youngest_denied,\n"
} |
| 5/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36151 output: 29 thoughts: 45 Thoughts: Okay, let's dive in. **Landlock Kernel Configuration Analysis** Right, time to get my hands dirty. I need to understand the configuration options related to Landlock, particularly those found in the kernel code. Specifically, I'm going to focus on `security/landlock/trace.c` and `security/landlock/task.c` and Kconfig. My goal is to understand how these files are built, which options enable or disable different aspects of Landlock, and how this relates to its overall security posture. I'll be looking for dependencies and potential conflicts, digging into the build process and identifying any conditional compilation that might be happening. I need to fully grasp the Landlock Kconfig architecture. |
| 6/2 | 2026/09/20 23:54 | tool | grepper |
0mArgs:{
"Expression": "config SECURITY_LANDLOCK",
"PathPrefix": "security/landlock"
}Results: {
"Output": "security/landlock/Kconfig-2-\nsecurity/landlock/Kconfig:3:config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\n--\nsecurity/landlock/Kconfig-23-\nsecurity/landlock/Kconfig:24:config SECURITY_LANDLOCK_LOG\nsecurity/landlock/Kconfig-25-\tbool\n--\nsecurity/landlock/Kconfig-28-\nsecurity/landlock/Kconfig:29:config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-30-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n"
} |
| 7/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36547 output: 33 thoughts: 21 Thoughts: Here's my summary, as though I'm thinking it: **Examining the Landlock Configuration** Okay, time to dive into the Landlock kernel configuration. I need to get a clear picture of its dependencies and the various configuration flags available. Let's start by cracking open `security/landlock/Kconfig`. This is where the core of the configuration lies, and I'll be able to glean all the information I need, including the build options. I'm hoping it's well-documented... always helps when trying to understand a new feature or module! Let's see what we can find. |
| 8/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "security/landlock/Kconfig",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t\n 3:\tconfig SECURITY_LANDLOCK\n 4:\t\tbool \"Landlock support\"\n 5:\t\tdepends on SECURITY\n 6:\t\tselect SECURITY_NETWORK\n 7:\t\tselect SECURITY_PATH\n 8:\t\thelp\n 9:\t\t Landlock is a sandboxing mechanism that enables processes to restrict\n 10:\t\t themselves (and their future children) by gradually enforcing\n 11:\t\t tailored access control policies. A Landlock security policy is a\n 12:\t\t set of access rights (e.g. open a file in read-only, make a\n 13:\t\t directory, etc.) tied to a file hierarchy. Such policy can be\n 14:\t\t configured and enforced by any processes for themselves using the\n 15:\t\t dedicated system calls: landlock_create_ruleset(),\n 16:\t\t landlock_add_rule(), and landlock_restrict_self().\n 17:\t\n 18:\t\t See Documentation/userspace-api/landlock.rst for further information.\n 19:\t\n 20:\t\t If you are unsure how to answer this question, answer N. Otherwise,\n 21:\t\t you should also prepend \"landlock,\" to the content of CONFIG_LSM to\n 22:\t\t enable Landlock at boot time.\n 23:\t\n 24:\tconfig SECURITY_LANDLOCK_LOG\n 25:\t\tbool\n 26:\t\tdepends on SECURITY_LANDLOCK\n 27:\t\tdefault y if AUDIT || TRACEPOINTS\n 28:\t\n 29:\tconfig SECURITY_LANDLOCK_KUNIT_TEST\n 30:\t\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n 31:\t\tdepends on KUNIT=y\n 32:\t\tdepends on SECURITY_LANDLOCK\n 33:\t\tdefault KUNIT_ALL_TESTS\n 34:\t\thelp\n 35:\t\t Build KUnit tests for Landlock.\n 36:\t\n 37:\t\t See the KUnit documentation in Documentation/dev-tools/kunit\n 38:\t\n 39:\t\t Run all KUnit tests for Landlock with:\n 40:\t\t ./tools/testing/kunit/kunit.py run --kunitconfig security/landlock\n"
} |
| 9/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37319 output: 32 thoughts: 13 Thoughts: Here's my take on those thoughts: **Checking the Landlock Makefile** Okay, so I'm thinking, "Let's make absolutely certain we haven't overlooked anything in the `security/landlock/Makefile`." It's a key piece, and even if I *think* I know it inside and out, a quick re-examination can surface something subtle or even just refresh my mental map of that critical area. A fresh pair of eyes never hurts, especially when dealing with something as foundational as Landlock's build process. There could be a configuration option, a subtle dependency, or a build flag that needs attention in light of [context of expertise]. I need to ensure the Makefile isn't inadvertently introducing a vulnerability or hindering performance in some way. |
| 10/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "security/landlock/Makefile",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\tobj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o\n 2:\t\n 3:\tlandlock-y := \\\n 4:\t\tsetup.o \\\n 5:\t\tsyscalls.o \\\n 6:\t\tobject.o \\\n 7:\t\truleset.o \\\n 8:\t\tcred.o \\\n 9:\t\ttask.o \\\n 10:\t\tfs.o \\\n 11:\t\ttsync.o \\\n 12:\t\tdomain.o\n 13:\t\n 14:\tlandlock-$(CONFIG_INET) += net.o\n 15:\t\n 16:\tlandlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \\\n 17:\t\tid.o \\\n 18:\t\tlog.o\n 19:\t\n 20:\tlandlock-$(CONFIG_AUDIT) += audit.o\n"
} |
| 11/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37756 output: 33 thoughts: 15 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the Landlock Makefile** Okay, let's get down to brass tacks. I've been working with the Landlock security module, and I need to understand the build process intimately. I've already glanced at the initial parts, but the real meat is likely further down in the `security/landlock/Makefile`. I need to see how the object files are linked, any special compiler flags being used, and crucially, how the Landlock LSM hooks are integrated into the kernel build. I'm looking for the intricacies of its dependencies, the rules for generating the Landlock tools and tests, and any potential customization options the build system provides. I'm hoping to discover the key pieces that define the Landlock module's lifecycle and its integration into the greater kernel build infrastructure. What are the specific build steps for the different Landlock components? How is this security module configured and built in a modular fashion? Understanding this Makefile is the critical path to understanding how Landlock works. |
| 12/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "security/landlock/Makefile",
"FirstLine": 21,
"LineCount": 10
}Results: {
"Contents": " 21:\t\n 22:\tlandlock-$(CONFIG_TRACEPOINTS) += trace.o\n"
} |
| 13/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 38067 output: 30 thoughts: 1384 Thoughts: **Fuzzing Decision for Landlock Patch** Okay, let's break this down. We're looking at a patch related to `CONFIG_SECURITY_LANDLOCK`, `CONFIG_TRACEPOINTS`, and `CONFIG_INET`, specifically where `CONFIG_SECURITY_LANDLOCK_LOG` is automatically selected with `AUDIT` or `TRACEPOINTS`. The question is, is this patch worth fuzzing? I'll apply the criteria. The initial thought was to use the negative criteria to determine if fuzzing could be skipped. My immediate focus was on that negative criterion, "Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints that do not alter control flow or data structures." That seemed the most likely reason to *not* fuzz. So, first, I checked `security/landlock/log.h`. I've found there are new structures added in with `CONFIG_TRACEPOINTS` defined, related to tracing network, ptrace, and signal events. So, the patch *does* alter data structures related to these logging mechanisms; this suggests fuzzing *might* be required. But, I needed to check control flow or logic. Specifically, I'm now looking at `security/landlock/task.c`. Now, the important thing here is the addition of `get_socket_domain_id` and, more importantly, its contents. I see `lockdep_assert_held(&unix_sk(other)->lock);` within the `get_socket_domain_id` function. And that's the kicker! Now, the important thing is that `lockdep_assert_held` is a runtime assertion. My expert knowledge screams that "NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED". This adds a new invariant, and even if the author *believes* it always holds, we need to verify it. So, I need to know where `get_socket_domain_id` is called from. I see it's called from `hook_unix_stream_connect` and `hook_unix_may_send`. Now, the question is: are the required locks *guaranteed* to be held when these functions are called? That needs to be explicitly verified. This means fuzzing is required. This has significantly changed the logic and/or control flow. I need to understand the locking context of these functions to determine if the assertion can be triggered. |
| 14/2 | 2026/09/20 23:54 | tool | grepper |
0mError:no matches Args: {
"Expression": "unix_may_send",
"PathPrefix": "net/af_unix"
}Results: {
"error": "no matches"
} |
| 15/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40018 output: 32 thoughts: 15 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing the Callers of `security_unix_may_send`** Okay, so the initial thought is to identify all the places where `security_unix_may_send` is called. Pretty straightforward. My initial reaction is, this is likely to be a security-related check, given the function name. The "unix" part suggests that it is related to UNIX domain sockets or related IPC mechanisms. Therefore, I want to trace the call graph. I'll need to know where it is being used to understand its context. Knowing the call sites helps me determine the potential attack surface. Are we looking at privilege escalation, information leaks, or something else entirely? A quick grep might be enough to get a list of the locations where this function is called. Then, I can start looking at the code around those points to understand the logic. Are there any unusual conditions or edge cases? Are there any obvious flaws in the way this function is being used? Time to dive in. |
| 16/2 | 2026/09/20 23:54 | tool | grepper |
0mError:no matches Args: {
"Expression": "security_unix_may_send",
"PathPrefix": "net/af_unix"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40300 output: 19 thoughts: 18 Thoughts: Okay, here's my summary, presented as a first-person account, assuming I'm an expert in kernel development: **Hunting Down `unix_may_send` Callers** Alright, time to get to work. My immediate thought is, let's track down *every* place in the kernel that's calling `unix_may_send`. This isn't just a casual query; I need a comprehensive, all-encompassing search. I need to understand the usage patterns of this function and, depending on the reason for the investigation, potentially modify its behavior, audit its security, or refactor related code. The goal is a complete picture. No stone left unturned. This search is the foundation for whatever analysis I have in mind. I need the full context. So, let's get that search going! |
| 18/2 | 2026/09/20 23:54 | tool | grepper |
0mArgs:{
"Expression": "unix_may_send"
}Results: {
"Output": "include/linux/lsm_hook_defs.h=331=LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,\ninclude/linux/lsm_hook_defs.h-332-\t struct sock *newsk)\ninclude/linux/lsm_hook_defs.h:333:LSM_HOOK(int, 0, unix_may_send, struct socket *sock, struct socket *other)\ninclude/linux/lsm_hook_defs.h-334-LSM_HOOK(int, 0, socket_create, int family, int type, int protocol, int kern)\n--\ninclude/linux/security.h=1666=int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);\ninclude/linux/security.h:1667:int security_unix_may_send(struct socket *sock, struct socket *other);\ninclude/linux/security.h-1668-int security_socket_create(int family, int type, int protocol, int kern);\n--\ninclude/linux/security.h=1726=static inline int security_unix_stream_connect(struct sock *sock,\n--\ninclude/linux/security.h-1732-\ninclude/linux/security.h:1733:static inline int security_unix_may_send(struct socket *sock,\ninclude/linux/security.h-1734-\t\t\t\t\t struct socket *other)\n--\ninclude/trace/events/landlock.h=994=TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n--\ninclude/trace/events/landlock.h-1012-\t\t * the caller (hook_unix_stream_connect or\ninclude/trace/events/landlock.h:1013:\t\t * hook_unix_may_send) holds unix_state_lock(peer).\ninclude/trace/events/landlock.h-1014-\t\t */\n--\nnet/unix/af_unix.c=277=static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)\n--\nnet/unix/af_unix.c-282-\nnet/unix/af_unix.c:283:static inline int unix_may_send(struct sock *sk, struct sock *osk)\nnet/unix/af_unix.c-284-{\n--\nnet/unix/af_unix.c=1515=static int unix_dgram_connect(struct socket *sock, struct sockaddr_unsized *addr,\n--\nnet/unix/af_unix.c-1558-\t\terr = -EPERM;\nnet/unix/af_unix.c:1559:\t\tif (!unix_may_send(sk, other))\nnet/unix/af_unix.c-1560-\t\t\tgoto out_unlock;\nnet/unix/af_unix.c-1561-\nnet/unix/af_unix.c:1562:\t\terr = security_unix_may_send(sk-\u003esk_socket, other-\u003esk_socket);\nnet/unix/af_unix.c-1563-\t\tif (err)\n--\nnet/unix/af_unix.c=2090=static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2193-\nnet/unix/af_unix.c:2194:\tif (!unix_may_send(sk, other)) {\nnet/unix/af_unix.c-2195-\t\terr = -EPERM;\n--\nnet/unix/af_unix.c-2250-\tif (sk-\u003esk_type != SOCK_SEQPACKET) {\nnet/unix/af_unix.c:2251:\t\terr = security_unix_may_send(sk-\u003esk_socket, other-\u003esk_socket);\nnet/unix/af_unix.c-2252-\t\tif (err)\n--\nsecurity/apparmor/af_unix.c=489=int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr,\n--\nsecurity/apparmor/af_unix.c-518- * unix connections are covered by the\nsecurity/apparmor/af_unix.c:519: * - unix_stream_connect (stream) and unix_may_send hooks (dgram)\nsecurity/apparmor/af_unix.c-520- * - fs connect is handled by open\n--\nsecurity/apparmor/af_unix.c=552=int aa_unix_accept_perm(struct socket *sock, struct socket *newsock)\n--\nsecurity/apparmor/af_unix.c-572-/*\nsecurity/apparmor/af_unix.c:573: * dgram handled by unix_may_sendmsg, right to send on stream done at connect\nsecurity/apparmor/af_unix.c-574- * could do per msg unix_stream here, but connect + socket transfer is\n--\nsecurity/apparmor/af_unix.c=606=static int unix_peer_perm(const struct cred *subj_cred,\n--\nsecurity/apparmor/af_unix.c-626- * Requires: lock held on both @sk and @peer_sk\nsecurity/apparmor/af_unix.c:627: * called by unix_stream_connect, unix_may_send\nsecurity/apparmor/af_unix.c-628- */\n--\nsecurity/apparmor/lsm.c=1188=static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk,\n--\nsecurity/apparmor/lsm.c-1218-/**\nsecurity/apparmor/lsm.c:1219: * apparmor_unix_may_send - check perms before conn or sending unix dgrams\nsecurity/apparmor/lsm.c-1220- * @sock: socket sending the message\n--\nsecurity/apparmor/lsm.c-1233- */\nsecurity/apparmor/lsm.c:1234:static int apparmor_unix_may_send(struct socket *sock, struct socket *peer)\nsecurity/apparmor/lsm.c-1235-{\n--\nsecurity/apparmor/lsm.c=1389=static int apparmor_socket_connect(struct socket *sock,\n--\nsecurity/apparmor/lsm.c-1396-\nsecurity/apparmor/lsm.c:1397:\t/* PF_UNIX goes through unix_stream_connect \u0026\u0026 unix_may_send */\nsecurity/apparmor/lsm.c-1398-\tswitch (sock-\u003esk-\u003esk_family) {\n--\nsecurity/apparmor/lsm.c=1445=static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,\n--\nsecurity/apparmor/lsm.c-1452-\nsecurity/apparmor/lsm.c:1453:\t/* PF_UNIX goes through unix_may_send */\nsecurity/apparmor/lsm.c-1454-\tswitch (sock-\u003esk-\u003esk_family) {\n--\nsecurity/apparmor/lsm.c=1767=static struct security_hook_list apparmor_hooks[] __ro_after_init = {\n--\nsecurity/apparmor/lsm.c-1809-\tLSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect),\nsecurity/apparmor/lsm.c:1810:\tLSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send),\nsecurity/apparmor/lsm.c-1811-\n--\nsecurity/landlock/task.c=312=static int hook_unix_stream_connect(struct sock *const sock,\n--\nsecurity/landlock/task.c-346-\nsecurity/landlock/task.c:347:static int hook_unix_may_send(struct socket *const sock,\nsecurity/landlock/task.c-348-\t\t\t struct socket *const other)\n--\nsecurity/landlock/task.c=525=static struct security_hook_list landlock_hooks[] __ro_after_init = {\n--\nsecurity/landlock/task.c-529-\tLSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),\nsecurity/landlock/task.c:530:\tLSM_HOOK_INIT(unix_may_send, hook_unix_may_send),\nsecurity/landlock/task.c-531-\n--\nsecurity/security.c=4119=int security_netlink_send(struct sock *sk, struct sk_buff *skb)\n--\nsecurity/security.c-4132- *\nsecurity/security.c:4133: * The @unix_stream_connect and @unix_may_send hooks were necessary because\nsecurity/security.c-4134- * Linux provides an alternative to the conventional file name space for Unix\n--\nsecurity/security.c=4150=EXPORT_SYMBOL(security_unix_stream_connect);\n--\nsecurity/security.c-4152-/**\nsecurity/security.c:4153: * security_unix_may_send() - Check if AF_UNIX socket can send datagrams\nsecurity/security.c-4154- * @sock: originating sock\n--\nsecurity/security.c-4159- *\nsecurity/security.c:4160: * The @unix_stream_connect and @unix_may_send hooks were necessary because\nsecurity/security.c-4161- * Linux provides an alternative to the conventional file name space for Unix\n--\nsecurity/security.c-4171- */\nsecurity/security.c:4172:int security_unix_may_send(struct socket *sock, struct socket *other)\nsecurity/security.c-4173-{\nsecurity/security.c:4174:\treturn call_int_hook(unix_may_send, sock, other);\nsecurity/security.c-4175-}\nsecurity/security.c:4176:EXPORT_SYMBOL(security_unix_may_send);\nsecurity/security.c-4177-\n--\nsecurity/selinux/hooks.c=5334=static int selinux_socket_unix_stream_connect(struct sock *sock,\n--\nsecurity/selinux/hooks.c-5365-\nsecurity/selinux/hooks.c:5366:static int selinux_socket_unix_may_send(struct socket *sock,\nsecurity/selinux/hooks.c-5367-\t\t\t\t\tstruct socket *other)\n--\nsecurity/selinux/hooks.c=7544=static struct security_hook_list selinux_hooks[] __ro_after_init = {\n--\nsecurity/selinux/hooks.c-7693-\tLSM_HOOK_INIT(unix_stream_connect, selinux_socket_unix_stream_connect),\nsecurity/selinux/hooks.c:7694:\tLSM_HOOK_INIT(unix_may_send, selinux_socket_unix_may_send),\nsecurity/selinux/hooks.c-7695-\n--\nsecurity/smack/smack_lsm.c=3954=static int smack_unix_stream_connect(struct sock *sock,\n--\nsecurity/smack/smack_lsm.c-4003-/**\nsecurity/smack/smack_lsm.c:4004: * smack_unix_may_send - Smack access on UDS\nsecurity/smack/smack_lsm.c-4005- * @sock: one socket\n--\nsecurity/smack/smack_lsm.c-4010- */\nsecurity/smack/smack_lsm.c:4011:static int smack_unix_may_send(struct socket *sock, struct socket *other)\nsecurity/smack/smack_lsm.c-4012-{\n--\nsecurity/smack/smack_lsm.c=5155=static struct security_hook_list smack_hooks[] __ro_after_init = {\n--\nsecurity/smack/smack_lsm.c-5257-\tLSM_HOOK_INIT(unix_stream_connect, smack_unix_stream_connect),\nsecurity/smack/smack_lsm.c:5258:\tLSM_HOOK_INIT(unix_may_send, smack_unix_may_send),\nsecurity/smack/smack_lsm.c-5259-\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c=69=FIXTURE_TEARDOWN(scoped_domains)\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-73-/*\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c:74: * Test unix_stream_connect() and unix_may_send() for a child connecting to its\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-75- * parent, when they have scoped domain or no domain.\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c=77=TEST_F(scoped_domains, connect_to_parent)\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-169-/*\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c:170: * Test unix_stream_connect() and unix_may_send() for a parent connecting to\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-171- * its child, when they have scoped domain or no domain.\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c=470=FIXTURE_TEARDOWN(scoped_vs_unscoped)\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-474-/*\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c:475: * Test unix_stream_connect and unix_may_send for parent, child and\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-476- * grand child processes when they can have scoped or non-scoped domains.\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c=693=FIXTURE_TEARDOWN(outside_socket)\n--\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-697-/*\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c:698: * Test unix_stream_connect and unix_may_send for parent and child processes\ntools/testing/selftests/landlock/scoped_abstract_unix_test.c-699- * when connecting socket has different domain than the process using it.\n"
} |
| 19/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44276 output: 38 thoughts: 53 Thoughts: Okay, let's dive into this. I need to figure out if we're properly protecting access to that `unix_sk(other)->lock`. My initial thought is to check two specific code sections: `net/unix/af_unix.c` line 1555-1565 and 2245-2255. That's where I should be looking to see whether the relevant lock is acquired and held correctly before interacting with that variable. This should give me a good indication if there's a potential race condition or some other concurrency issue lurking. These lines likely handle the core interaction or potential synchronization points. Let's start there. |
| 20/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "net/unix/af_unix.c",
"FirstLine": 1550,
"LineCount": 25
}Results: {
"Contents": "1550:\t\n1551:\t\t\t/* Apparently VFS overslept socket death. Retry. */\n1552:\t\t\tif (sock_flag(other, SOCK_DEAD)) {\n1553:\t\t\t\tunix_state_double_unlock(sk, other);\n1554:\t\t\t\tsock_put(other);\n1555:\t\t\t\tgoto restart;\n1556:\t\t\t}\n1557:\t\n1558:\t\t\terr = -EPERM;\n1559:\t\t\tif (!unix_may_send(sk, other))\n1560:\t\t\t\tgoto out_unlock;\n1561:\t\n1562:\t\t\terr = security_unix_may_send(sk-\u003esk_socket, other-\u003esk_socket);\n1563:\t\t\tif (err)\n1564:\t\t\t\tgoto out_unlock;\n1565:\t\n1566:\t\t\tWRITE_ONCE(sk-\u003esk_state, TCP_ESTABLISHED);\n1567:\t\t\tWRITE_ONCE(other-\u003esk_state, TCP_ESTABLISHED);\n1568:\t\t} else {\n1569:\t\t\t/*\n1570:\t\t\t *\t1003.1g breaking connected state with AF_UNSPEC\n1571:\t\t\t */\n1572:\t\t\tother = NULL;\n1573:\t\t\tunix_state_double_lock(sk, other);\n1574:\t\t}\n"
} |
| 21/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44871 output: 38 thoughts: 21 Thoughts: Here's a summary of my thought process, keeping in mind my field of expertise: **Diving Deep into the Network Layer** Alright, I'm digging into this code, specifically looking at the networking aspect, and I'm honing in on the function responsible for sending datagrams. I need to understand this `unix_dgram_sendmsg` function. I should take a very close look around line 2240 to see how it's implemented. Given my knowledge, I'm expecting to find interesting details regarding socket address handling, buffer management, error checking, and interaction with the underlying network stack. I'm keen on how this handles the transfer of data and the signaling. |
| 22/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "net/unix/af_unix.c",
"FirstLine": 2240,
"LineCount": 25
}Results: {
"Contents": "2240:\t\tif (other-\u003esk_shutdown \u0026 RCV_SHUTDOWN) {\n2241:\t\t\terr = -EPIPE;\n2242:\t\t\tgoto out_unlock;\n2243:\t\t}\n2244:\t\n2245:\t\tif (UNIXCB(skb).fp \u0026\u0026 !other-\u003esk_scm_rights) {\n2246:\t\t\terr = -EPERM;\n2247:\t\t\tgoto out_unlock;\n2248:\t\t}\n2249:\t\n2250:\t\tif (sk-\u003esk_type != SOCK_SEQPACKET) {\n2251:\t\t\terr = security_unix_may_send(sk-\u003esk_socket, other-\u003esk_socket);\n2252:\t\t\tif (err)\n2253:\t\t\t\tgoto out_unlock;\n2254:\t\t}\n2255:\t\n2256:\t\t/* other == sk \u0026\u0026 unix_peer(other) != sk if\n2257:\t\t * - unix_peer(sk) == NULL, destination address bound to sk\n2258:\t\t * - unix_peer(sk) == sk by time of get but disconnected before lock\n2259:\t\t */\n2260:\t\tif (other != sk \u0026\u0026\n2261:\t\t unlikely(unix_peer(other) != sk \u0026\u0026\n2262:\t\t unix_recvq_full_lockless(other))) {\n2263:\t\t\tif (timeo) {\n2264:\t\t\t\ttimeo = unix_wait_for_peer(other, timeo);\n"
} |
| 23/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45480 output: 38 thoughts: 28 Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind the user is an expert: **Tracing the Lock Acquisition in `unix_dgram_sendmsg`** Alright, let's dive into this. I need to pinpoint exactly where the locking occurs within the `unix_dgram_sendmsg` function *before* line 2240. I know the general structure, and where things usually happen, but I need to identify the precise moment of lock acquisition in that specific context. This means I'll likely be looking for mutexes, spinlocks, or other synchronization primitives used to protect shared data structures within that function's scope. I'll need to remember all the variables the function uses and make sure I know how each lock interacts with the code leading up to line 2240. |
| 24/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "net/unix/af_unix.c",
"FirstLine": 2225,
"LineCount": 20
}Results: {
"Contents": "2225:\t\t\t\terr = -ECONNREFUSED;\n2226:\t\t\t\tgoto out_sock_put;\n2227:\t\t\t}\n2228:\t\n2229:\t\t\tunix_state_unlock(sk);\n2230:\t\n2231:\t\t\tif (!msg-\u003emsg_namelen) {\n2232:\t\t\t\terr = -ECONNRESET;\n2233:\t\t\t\tgoto out_sock_put;\n2234:\t\t\t}\n2235:\t\n2236:\t\t\tsock_put(other);\n2237:\t\t\tgoto lookup;\n2238:\t\t}\n2239:\t\n2240:\t\tif (other-\u003esk_shutdown \u0026 RCV_SHUTDOWN) {\n2241:\t\t\terr = -EPIPE;\n2242:\t\t\tgoto out_unlock;\n2243:\t\t}\n2244:\t\n"
} |
| 25/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45961 output: 38 thoughts: 19 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue: **Navigating Complex Code: A Focused Approach** Alright, so we're looking at something specific, and it's flagged around line 2225. I need to understand the context before jumping directly there. Rather than diving in head-first, let's back up a bit. Since 2225 is the target, I'll check what's happening just before that. The code's probably quite long and this section has some critical logic. Checking the block around line 2210 seems like the most logical place to get an overview. That should give me a good sense of the control flow, variable states, and any preceding calculations that impact the section I'm ultimately interested in at 2225. It's all about efficient debugging and understanding the bigger picture before isolating the specific issue. Gotta keep that contextual awareness. |
| 26/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "net/unix/af_unix.c",
"FirstLine": 2210,
"LineCount": 20
}Results: {
"Contents": "2210:\t\t\t\tgoto out_sock_put;\n2211:\t\t\t}\n2212:\t\n2213:\t\t\tif (!sk_locked)\n2214:\t\t\t\tunix_state_lock(sk);\n2215:\t\n2216:\t\t\tif (unix_peer(sk) == other) {\n2217:\t\t\t\tunix_peer(sk) = NULL;\n2218:\t\t\t\tunix_dgram_peer_wake_disconnect_wakeup(sk, other);\n2219:\t\n2220:\t\t\t\tWRITE_ONCE(sk-\u003esk_state, TCP_CLOSE);\n2221:\t\t\t\tunix_state_unlock(sk);\n2222:\t\n2223:\t\t\t\tunix_dgram_disconnected(sk, other);\n2224:\t\t\t\tsock_put(other);\n2225:\t\t\t\terr = -ECONNREFUSED;\n2226:\t\t\t\tgoto out_sock_put;\n2227:\t\t\t}\n2228:\t\n2229:\t\t\tunix_state_unlock(sk);\n"
} |
| 27/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46506 output: 38 thoughts: 24 Thoughts: Okay, I'm ready to dive into this. Let's see what's on the docket... specifically, I need to focus on lines 2190 through 2210. I'm treating this like a focused data extraction, nothing extraneous. Ready to process. **Analyzing Lines 2190-2210: Pre-2210 Focus** Alright, I need to parse the core argument being presented here. What specific point is being made in that section? Are there any key variables or assumptions that need to be pulled out? I'm looking for the foundational building blocks of the argument. I have to quickly identify: the methodology used, what data is collected, and what conclusions are drawn from the data. Is there anything unusual about the methodology? Anything that challenges established norms or existing research? I'm paying very close attention to any nuances or potential pitfalls that would affect the validity of whatever claims are being made. Anything... any *thing* that requires further investigation. I'm essentially looking for the "so what?" factor. What's the impact of this particular snippet within the broader framework of the paper? I'm dissecting it. I'm extracting. I'm looking for the key, the essence, the core argument so that I can immediately judge if it's new information, or if it is something I already know. I'm also looking for anything that deviates from my prior knowledge and experience. I'm ready. Let's start. |
| 28/2 | 2026/09/20 23:54 | tool | read-file |
0mArgs:{
"File": "net/unix/af_unix.c",
"FirstLine": 2190,
"LineCount": 20
}Results: {
"Contents": "2190:\t\tsk_locked = 0;\n2191:\t\tunix_state_lock(other);\n2192:\trestart_locked:\n2193:\t\n2194:\t\tif (!unix_may_send(sk, other)) {\n2195:\t\t\terr = -EPERM;\n2196:\t\t\tgoto out_unlock;\n2197:\t\t}\n2198:\t\n2199:\t\tif (unlikely(sock_flag(other, SOCK_DEAD))) {\n2200:\t\t\t/* Check with 1003.1g - what should datagram error */\n2201:\t\n2202:\t\t\tunix_state_unlock(other);\n2203:\t\n2204:\t\t\tif (sk-\u003esk_type == SOCK_SEQPACKET) {\n2205:\t\t\t\t/* We are here only when racing with unix_release_sock()\n2206:\t\t\t\t * is clearing @other. Never change state to TCP_CLOSE\n2207:\t\t\t\t * unlike SOCK_DGRAM wants.\n2208:\t\t\t\t */\n2209:\t\t\t\terr = -EPIPE;\n"
} |
| 29/2 | 2026/09/20 23:54 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 47182 output: 36 thoughts: 1435 Thoughts: **Landlock Patch Analysis and Fuzzing Recommendation** Okay, so I'm looking at this Landlock patch, and here's what's running through my mind: I see that `unix_state_lock(other)` is held. Specifically, that's a spinlock, `unix_sk(sk)->lock`. In the `get_socket_domain_id` function, there's a `lockdep_assert_held` check on that same lock. Then, the code accesses `other->sk_socket->file->f_cred->domain`. My immediate thought is, "Wait a minute, is `other->sk_socket->file` *guaranteed* to be non-NULL here?" What if the socket was created via `socketpair` or from the kernel directly, and `file` isn't assigned? Thankfully, it seems the code accounts for this. Looking closely at `sock_is_scoped`, which is called *before* `get_socket_domain_id`, it checks `other->sk_socket` and then `other->sk_socket->file`. If either is NULL, it returns false. So, that's good. However, the patch *does* add some new runtime checks that demand immediate attention. Specifically, the patch introduces three new `WARN_ON_ONCE` checks in `security/landlock/trace.c`: one for the net trace, another for address length and size mismatches, and a third for `ptrace`. And a fourth for signal. There's also a `lockdep_assert_held` in `security/landlock/task.c`. That immediately triggers my expert instincts. These are runtime assertions in reachable code, meaning this patch has *definitely* introduced new invariants and potential issues. This brings me to the patch's instructions on fuzzing. Section 1 explicitly states that "NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED". Section 3 lays out positive criteria for determining when fuzzing is required, and this patch hits multiple points: * It alters the locking and thus synchronization logic. * It introduces runtime assertions in reachable paths. * It touches core subsystems like networking (Landlock being a security hook). * It affects data structures (`struct landlock_request`, etc.). * It modifies control flow with those `WARN_ON_ONCE` calls (that return) and `memcpy`. Importantly, this isn't just about decorative logging; this patch *alters* control flow and data structures, and the Landlock subsystem itself is fully reachable (system calls, socket ops, ptrace, file operations). I need to confirm the conditions under which these tracepoints are triggered. Are they conditionally enabled, or do they execute unconditionally? A quick look at `security/landlock/log.c` shows that `landlock_trace_denial` is called unconditionally on denials. **Conclusion:** This patch *absolutely* requires fuzzing. The added assertions and modifications to control flow in a core subsystem like Landlock demand that we verify the new invariants and identify potential bugs through fuzzing. |
| 30/2 | 2026/09/20 23:55 | tool | read-file |
0mArgs:{
"File": "security/landlock/log.c",
"FirstLine": 535,
"LineCount": 30
}Results: {
"Contents": " 535:\t\n 536:\t\tconst bool same_exec = !!(subject-\u003edomain_exec \u0026 BIT(youngest_layer));\n 537:\t\tconst bool logged = is_denial_logged(request, youngest_denied, missing,\n 538:\t\t\t\t\t\t same_exec, object_quiet_flag);\n 539:\t\n 540:\t\t/*\n 541:\t\t * Consistently keeps track of the number of denied access requests even\n 542:\t\t * if audit is currently disabled, or if audit rules currently exclude\n 543:\t\t * this record type, or if landlock_restrict_self(2)'s flags quiet logs.\n 544:\t\t */\n 545:\t\tatomic64_inc(\u0026youngest_denied-\u003enum_denials);\n 546:\t\n 547:\t\tlandlock_trace_denial(request, youngest_denied, missing, same_exec,\n 548:\t\t\t\t logged);\n 549:\t\tlandlock_audit_denial(request, youngest_denied, missing, logged);\n 550:\t}\n 551:\t\n 552:\t/**\n 553:\t * landlock_log_free_domain - Log domain deallocation\n 554:\t *\n 555:\t * @hierarchy: The domain's hierarchy being deallocated.\n 556:\t *\n 557:\t * Called from landlock_put_domain_deferred() (via a work queue scheduled by\n 558:\t * hook_cred_free()) or directly from landlock_put_domain().\n 559:\t */\n 560:\tvoid landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)\n 561:\t{\n 562:\t\tif (WARN_ON_ONCE(!hierarchy))\n 563:\t\t\treturn;\n 564:\t\n"
} |
| 31/2 | 2026/09/20 23:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 49808 output: 34 thoughts: 1969 Thoughts: **Landlock Tracepoint Analysis and Fuzzing Strategy**
Okay, here's what's running through my head as I analyze this Landlock code. First off, I see that the `landlock_trace_denial` function is the core of the tracepoint mechanism. This function is called every time a denial happens, which immediately catches my attention from a fuzzing perspective.
I'm noticing something that piques my interest: those `WARN_ON_ONCE` checks! They are present *even* when the tracepoints themselves are disabled which is a major red flag, because it implies some kind of conditional code path. Furthermore, the `get_socket_domain_id` function is being called unconditionally when abstract UNIX socket denials or sends occur, as long as `CONFIG_TRACEPOINTS` is enabled. Since `CONFIG_TRACEPOINTS` is virtually always enabled, this means `lockdep_assert_held(&unix_sk(other)->lock)` runs every single time! That's a strong indicator.
Beyond that, the code is restructuring data using stack-based compound literals (`&(struct landlock_...){...}`) which, combined with the fact that these literals are passed all the way down the call stack, points to a potential area of interest for fuzzing. This is a non-trivial data restructuring, and any such data manipulation is worth a closer look for fuzzing.
Therefore, `WorthFuzzing` is definitely `true`.
Now, regarding `FocusSymbols`: I need to identify the key symbols. I need to avoid common functions and focus on the Landlock-specific logic. I should focus on the LSM hooks that are modified and the functions that call or implement the features being reviewed.
I see that `landlock_append_fs_rule` in `fs.c` seems like an API entrypoint for adding FS rules, so that's something I need to keep in mind. I need to consider `unmask_layers_fs` because it's static and I should consider the functions that call it: `landlock_find_inode_rule` or `check_access_path` etc.
In summary, the focus should be on the core denial handling path ( `landlock_trace_denial`) and the function calls that get executed within the tracepoint configurations.
|
| 32/2 | 2026/09/20 23:55 | tool | grepper |
0mArgs:{
"Expression": "unmask_layers_fs",
"PathPrefix": "security/landlock/fs.c"
}Results: {
"Output": "security/landlock/fs.c=397=static bool get_inode_id(const struct dentry *const dentry,\n--\nsecurity/landlock/fs.c-414-\nsecurity/landlock/fs.c:415:static bool unmask_layers_fs(const struct landlock_domain *const domain,\nsecurity/landlock/fs.c-416-\t\t\t const struct landlock_id id,\n--\nsecurity/landlock/fs.c=795=is_access_to_paths_allowed(const struct landlock_domain *const domain,\n--\nsecurity/landlock/fs.c-872-\t\tif (handled \u0026\u0026 get_inode_id(dentry_child1, \u0026id))\nsecurity/landlock/fs.c:873:\t\t\tunmask_layers_fs(domain, id, handled,\nsecurity/landlock/fs.c-874-\t\t\t\t\t \u0026_layer_masks_child1, dentry_child1);\n--\nsecurity/landlock/fs.c-888-\t\tif (handled \u0026\u0026 get_inode_id(dentry_child2, \u0026id))\nsecurity/landlock/fs.c:889:\t\t\tunmask_layers_fs(domain, id, handled,\nsecurity/landlock/fs.c-890-\t\t\t\t\t \u0026_layer_masks_child2, dentry_child2);\n--\nsecurity/landlock/fs.c-944-\t\t\t\tallowed_parent1 ||\nsecurity/landlock/fs.c:945:\t\t\t\tunmask_layers_fs(domain, id,\nsecurity/landlock/fs.c-946-\t\t\t\t\t\t access_masked_parent1,\n--\nsecurity/landlock/fs.c-950-\t\t\t\tallowed_parent2 ||\nsecurity/landlock/fs.c:951:\t\t\t\tunmask_layers_fs(domain, id,\nsecurity/landlock/fs.c-952-\t\t\t\t\t\t access_masked_parent2,\n--\nsecurity/landlock/fs.c=1123=static bool collect_domain_accesses(const struct landlock_domain *const domain,\n--\nsecurity/landlock/fs.c-1150-\t\tif (get_inode_id(dir, \u0026id) \u0026\u0026\nsecurity/landlock/fs.c:1151:\t\t unmask_layers_fs(domain, id, access_masked_dom,\nsecurity/landlock/fs.c-1152-\t\t\t\t layer_masks_dom, dir)) {\n"
} |
| 33/2 | 2026/09/20 23:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 52904 output: 32 thoughts: 86 Thoughts: **Landlock Network Rule Analysis** Okay, let's dive into the Landlock network security implementation. I need to refresh my memory on how this is structured. The first thing I'm going to look at is `security/landlock/net.c`. I need to understand what's in there. I'm going to quickly look at the functions declared in `net.c`. Looks like there's `landlock_append_net_rule`, which isn't static, and that's good to note. There is also `unmask_layers_net` which is static. I see that `current_check_access_socket` is also static. Now, the key question is, where are the LSM (Linux Security Modules) hooks defined within this file? I know the module defines net rules, but I need to identify which hooks are called and how. Back to looking at the `net.c` file! |
| 34/2 | 2026/09/20 23:55 | tool | grepper |
0mArgs:{
"Expression": "LSM_HOOK",
"PathPrefix": "security/landlock/net.c"
}Results: {
"Output": "security/landlock/net.c=423=static struct security_hook_list landlock_hooks[] __ro_after_init = {\nsecurity/landlock/net.c:424:\tLSM_HOOK_INIT(socket_bind, hook_socket_bind),\nsecurity/landlock/net.c:425:\tLSM_HOOK_INIT(socket_connect, hook_socket_connect),\nsecurity/landlock/net.c:426:\tLSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),\nsecurity/landlock/net.c-427-};\n"
} |
| 35/2 | 2026/09/20 23:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 53378 output: 36 thoughts: 175 Thoughts: **Analyzing Landlock Task Hooks** Okay, let's dive into `security/landlock/task.c`. My immediate goal is to understand the scope of changes, so I need to identify the modified functions. Scanning the file, I see these functions have been touched: `hook_ptrace_access_check`, `hook_ptrace_traceme`, `hook_unix_stream_connect`, `hook_unix_may_send`, `hook_task_kill`, `hook_file_send_sigiotask`, `sock_is_scoped`, and `get_socket_domain_id`. Now, I need to understand the visibility of these functions. Are they static, and thus, limited in their scope within the file? Or are they externally accessible? A quick check should confirm whether these hook functions are static or not. Let me focus on lines 525-540 in `security/landlock/task.c` and see what I can discern. |
| 36/2 | 2026/09/20 23:55 | tool | read-file |
0mArgs:{
"File": "security/landlock/task.c",
"FirstLine": 520,
"LineCount": 25
}Results: {
"Contents": " 520:\t#endif /* CONFIG_TRACEPOINTS */\n 521:\t\t});\n 522:\t\treturn -EPERM;\n 523:\t}\n 524:\t\n 525:\tstatic struct security_hook_list landlock_hooks[] __ro_after_init = {\n 526:\t\tLSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),\n 527:\t\tLSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),\n 528:\t\n 529:\t\tLSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),\n 530:\t\tLSM_HOOK_INIT(unix_may_send, hook_unix_may_send),\n 531:\t\n 532:\t\tLSM_HOOK_INIT(task_kill, hook_task_kill),\n 533:\t\tLSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask),\n 534:\t};\n 535:\t\n 536:\t__init void landlock_add_task_hooks(void)\n 537:\t{\n 538:\t\tsecurity_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),\n 539:\t\t\t\t \u0026landlock_lsmid);\n 540:\t}\n"
} |
| 37/2 | 2026/09/20 23:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 54170 output: 35 thoughts: 934 Thoughts: **Analyzing Landlock's Function Call Graph and Inlining Behavior** Okay, let's break this down. I'm focusing on how the compiler handles specific functions within the Landlock framework, particularly regarding inlining. The key here is to understand how the static keyword interacts with function pointers. First, I see that functions like `hook_ptrace_access_check`, `hook_ptrace_traceme`, `hook_unix_stream_connect`, `hook_unix_may_send`, `hook_task_kill`, and `hook_file_send_sigiotask` are *static* within `task.c`. However, because their addresses are taken and stored within the `landlock_hooks` structure, they're effectively function pointers. This means the compiler *cannot* inline them, and they retain distinct symbol addresses in the vmlinux image. This is a fundamental optimization constraint. I've verified that this is consistent behavior across similar cases. Similarly, `get_socket_domain_id` within `task.c` also falls into this category – static, but its usage within functions whose addresses are taken prevents inlining, even though it's likely a small function. Now, let's look at the functions in Landlock's other source files: `landlock_trace_denial`, `landlock_trace_free_domain` (in `trace.c`), `landlock_append_fs_rule` (in `fs.c`), `landlock_append_net_rule` (in `net.c`), and `landlock_log_denial` (in `log.c`). These are *non-static* and thus retain their distinct symbols. Also, functions like `hook_socket_bind`, `hook_socket_connect`, `hook_socket_sendmsg` fall under this category as they are static but their addresses are taken, thus making them non-inlinable. Next, I'm checking the tracepoint symbols: `trace_event_raw_event_landlock_*`. These are all clearly defined and, of course, non-inlined by definition as these are trace events, and the tracing infrastructure needs them to be distinct. A key question emerges: Are these Landlock functions—`landlock_trace_denial`, `landlock_append_fs_rule`, `landlock_append_net_rule`, `get_socket_domain_id`, and the various `hook_*` functions—*ubiquitous* within the kernel? The answer is no. They are specific to Landlock and are not called by unrelated generic kernel operations unless Landlock is enabled and in use. This isolation is important for performance and code clarity. Finally, I'm verifying the specific code for `landlock_trace_denial`, and confirming its signature. And, regarding `get_socket_domain_id`, it is likely *not* inlined, because its calling functions, such as `hook_unix_stream_connect` and `hook_unix_may_send` have their addresses taken. I need to confirm if `current_check_access_socket` is static or non-static in `net.c`. I need to review its call sites. |
| 38/2 | 2026/09/20 23:55 | tool | grepper |
0mArgs:{
"Expression": "current_check_access_socket",
"PathPrefix": "security/landlock/net.c"
}Results: {
"Output": "security/landlock/net.c=57=static bool unmask_layers_net(const struct landlock_domain *const domain,\n--\nsecurity/landlock/net.c-72-\nsecurity/landlock/net.c:73:static int current_check_access_socket(struct socket *const sock,\nsecurity/landlock/net.c-74-\t\t\t\t struct sockaddr *const address,\n--\nsecurity/landlock/net.c=309=static int current_check_autobind_udp_socket(struct socket *const sock)\n--\nsecurity/landlock/net.c-342-\nsecurity/landlock/net.c:343:\treturn current_check_access_socket(sock, (struct sockaddr *)\u0026port0,\nsecurity/landlock/net.c-344-\t\t\t\t\t sizeof(port0), bind_udp.net, false);\n--\nsecurity/landlock/net.c=347=static int hook_socket_bind(struct socket *const sock,\n--\nsecurity/landlock/net.c-358-\nsecurity/landlock/net.c:359:\treturn current_check_access_socket(sock, address, addrlen,\nsecurity/landlock/net.c-360-\t\t\t\t\t access_request, false);\n--\nsecurity/landlock/net.c=363=static int hook_socket_connect(struct socket *const sock,\n--\nsecurity/landlock/net.c-376-\nsecurity/landlock/net.c:377:\tret = current_check_access_socket(sock, address, addrlen,\nsecurity/landlock/net.c-378-\t\t\t\t\t access_request, true);\n--\nsecurity/landlock/net.c=392=static int hook_socket_sendmsg(struct socket *const sock,\n--\nsecurity/landlock/net.c-400-\tif ((msg-\u003emsg_flags \u0026 MSG_FASTOPEN) \u0026\u0026 address \u0026\u0026 sk_is_tcp(sock-\u003esk)) {\nsecurity/landlock/net.c:401:\t\tret = current_check_access_socket(\nsecurity/landlock/net.c-402-\t\t\tsock, address, addrlen, LANDLOCK_ACCESS_NET_CONNECT_TCP,\n--\nsecurity/landlock/net.c-413-\tif (address != NULL)\nsecurity/landlock/net.c:414:\t\tret = current_check_access_socket(sock, address, addrlen,\nsecurity/landlock/net.c-415-\t\t\t\t\t\t access_request, false);\n"
} |
| 39/2 | 2026/09/20 23:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 56461 output: 327 thoughts: 1400 Thoughts: **Landlock Fuzzing Configuration Analysis** Okay, let's break this down. We're looking at configuring fuzzing for this Landlock patch. My initial thought is to zero in on the key LSM hooks: `hook_socket_bind`, `hook_socket_connect`, `hook_socket_sendmsg`, especially since `current_check_access_socket` is a fairly large function. The compiler likely won't inline it, making it a good target. First, let's identify the required config flags. We need `SECURITY_LANDLOCK`, obviously. Peeking into `security/landlock/Kconfig`, it *depends* on `SECURITY`, and *selects* `SECURITY_NETWORK` and `SECURITY_PATH`. Also, the `Makefile` tells us `INET` is needed for `net.c`, specifically for `landlock-$(CONFIG_INET) += net.o`, implying network functionality. We should note that `landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += id.o log.o` which will be determined by whether we set `SECURITY_LANDLOCK_LOG` and further dependencies on `AUDIT` or `TRACEPOINTS`. `TRACEPOINTS` also appears to be key since it enables paths in several files, and `TRACEPOINTS` is a standard config. The need for `LOCKDEP` or `PROVE_LOCKING` is a potential for testing but secondary to the core configuration requirements of `SECURITY`, `SECURITY_LANDLOCK`, `TRACEPOINTS`, and `INET`. We'll also need `CONFIG_UNIX` for Unix sockets, an important feature. Now, let's solidify the FocusSymbols. I've compiled a list that encompasses: `landlock_trace_denial`, `landlock_append_fs_rule`, `landlock_append_net_rule`, `current_check_access_socket`, `hook_unix_stream_connect`, `hook_unix_may_send`, `hook_ptrace_access_check`, `hook_ptrace_traceme`, `hook_task_kill`, and `hook_file_send_sigiotask`. I've checked the code for other key symbols: `unmask_layers_fs`, `unmask_layers_net`, `get_socket_domain_id`, and `sock_is_scoped`. This list hits all the essential Landlock-specific entrypoints, and also avoids the generic lifecycle functions. These are the functions that *directly* implement the changed logic, or are the entry points to the subsystem. The "FocusSymbols" guidelines are clear: target specific feature logic and entrypoints, and avoid ubiquitous functions. These selected symbols are specific to Landlock. Regarding the rationale for fuzzing, the patch restructures trace events and associated data structures. It introduces new runtime checks, `WARN_ON_ONCE` statements, and a `lockdep_assert_held` check. Critically, the patch also modifies LSM hook logic related to network sockets, ptrace, and signal handling, passing new context. This altered code is definitely executable and reachable in standard virtualization environments via Landlock syscalls, AF_UNIX sockets, networking syscalls, signals, and ptrace. This meets all the criteria. We're good to set the results. |
| 40/2 | 2026/09/20 23:55 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"SECURITY",
"SECURITY_LANDLOCK",
"TRACEPOINTS",
"INET"
],
"FocusSymbols": [
"landlock_trace_denial",
"landlock_append_fs_rule",
"landlock_append_net_rule",
"current_check_access_socket",
"hook_unix_stream_connect",
"hook_unix_may_send",
"hook_ptrace_access_check",
"hook_ptrace_traceme",
"hook_task_kill",
"hook_file_send_sigiotask"
],
"Reasoning": "The patch refactors Landlock trace events and modifies Landlock's denial logging data structures (introducing struct landlock_blockers and trace context structs passed through a union in struct landlock_request). It updates multiple reachable LSM hooks (hook_unix_stream_connect, hook_unix_may_send, hook_ptrace_access_check, hook_ptrace_traceme, hook_task_kill, hook_file_send_sigiotask, current_check_access_socket) and adds new runtime assertions including multiple WARN_ON_ONCE checks in landlock_trace_denial() and a lockdep_assert_held() check in get_socket_domain_id(). These modifications affect reachable security subsystem code and warrant fuzzing to ensure that invariants hold and no unintended regressions or assertion failures occur.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"SECURITY",
"SECURITY_LANDLOCK",
"TRACEPOINTS",
"INET"
],
"FocusSymbols": [
"landlock_trace_denial",
"landlock_append_fs_rule",
"landlock_append_net_rule",
"current_check_access_socket",
"hook_unix_stream_connect",
"hook_unix_may_send",
"hook_ptrace_access_check",
"hook_ptrace_traceme",
"hook_task_kill",
"hook_file_send_sigiotask"
],
"Reasoning": "The patch refactors Landlock trace events and modifies Landlock's denial logging data structures (introducing struct landlock_blockers and trace context structs passed through a union in struct landlock_request). It updates multiple reachable LSM hooks (hook_unix_stream_connect, hook_unix_may_send, hook_ptrace_access_check, hook_ptrace_traceme, hook_task_kill, hook_file_send_sigiotask, current_check_access_socket) and adds new runtime assertions including multiple WARN_ON_ONCE checks in landlock_trace_denial() and a lockdep_assert_held() check in get_socket_domain_id(). These modifications affect reachable security subsystem code and warrant fuzzing to ensure that invariants hold and no unintended regressions or assertion failures occur.",
"WorthFuzzing": true
} |
| 41/1 | 2026/09/20 23:55 | action | If |
1mArgs:{
"WorthFuzzing": true
}Results: null |
| 42/2 | 2026/09/20 23:55 | agent | kmsan-evaluator |
1mModel:core Args: null Results: {
"KMSANReasoning": "The patch series updates Landlock trace events and internal logging context:\n1. Renames trace events to match updated terminology (e.g. landlock_check_rule_fs -\u003e landlock_check_rule_inode, landlock_add_rule_fs -\u003e landlock_add_rule_path_beneath, etc.).\n2. Adds flags, socket_family, and tracer/signal details to tracepoint prototypes for BTF/raw tracepoint consumers.\n3. Introduces helper structs (landlock_blockers, landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace) and places trace-only pointers in a union within struct landlock_request.\n4. Refactors network denial tracepoint logic to extract the port from checked sockaddr data.\n\nMemory initialization and safety review:\n- All stack-allocated structures and compound literals (such as struct landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace, and landlock_blockers) are fully initialized using designated initializers.\n- The union in struct landlock_request is partitioned cleanly by request-\u003etype; each switch branch in landlock_trace_denial only accesses the union member corresponding to the request type and guards against NULL with WARN_ON_ONCE.\n- The address buffer in landlock_trace_denial() is explicitly zero-initialized (`struct sockaddr_storage address = {};`) before copying addrlen bytes, and `has_port` verifies that addrlen is at least `offsetofend(struct sockaddr_in, sin_port)` before inspecting the port.\n- No kernel memory or padding is exposed or copied to userspace via copy_to_user(), netlink, ioctl, or socket options. Tracepoint ring buffer records only store scalar values.\n\nSince the patch does not read or leak uninitialized memory and introduces no uninitialized memory risks, a dedicated KMSAN fuzzing session is not required; standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 07f6c65816aba6a93b7296aeb30665ea83d41747
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 20 23:54:12 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst
index af9267cca47d9..304a8d06273ee 100644
--- a/Documentation/trace/events-landlock.rst
+++ b/Documentation/trace/events-landlock.rst
@@ -6,7 +6,7 @@ Landlock Trace Events
=====================
:Author: Mickaël Salaün
-:Date: August 2026
+:Date: September 2026
Landlock emits trace events for sandbox lifecycle operations and access
denials. These events can be consumed by ftrace (for human-readable
@@ -31,8 +31,8 @@ Landlock trace events are organized in four categories:
**Syscall events** are emitted during Landlock system calls:
- ``landlock_create_ruleset``: a new ruleset is created
-- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset
-- ``landlock_add_rule_net``: a network port rule is added to a ruleset
+- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset
+- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset
- ``landlock_create_domain``: a new domain is created from a ruleset
- ``landlock_enforce_domain``: a domain is enforced on a thread
@@ -47,8 +47,8 @@ Landlock trace events are organized in four categories:
**Rule evaluation events** are emitted during rule matching:
-- ``landlock_check_rule_fs``: a filesystem rule is evaluated
-- ``landlock_check_rule_net``: a network port rule is evaluated
+- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated
+- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated
**Lifecycle events**:
@@ -141,10 +141,10 @@ in some field formats:
treated as untrusted input and escaped in the trace text output so it
cannot inject field separators or control characters.
-- **Other party's domain**: A scope or ptrace denial compares the
- subject's denying domain (``domain=``, always the enforcing domain and
- never the current task) with the other party's domain, so these
- tracepoints also report the other party's domain as a scalar ID:
+- **Other party's domain**: A scope or ptrace denial compares the subject's
+ denying domain (``domain=``), which is the enforcing domain and not
+ necessarily the current task's domain, with the other party's domain.
+ These tracepoints also report the other party's domain as a scalar ID:
``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and
``peer_domain=`` (abstract unix socket). It is ``0`` when the other
party is unsandboxed, and otherwise a domain ID that a consumer resolves
@@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID.
Interpreting check_rule events
==============================
-The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer
+The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer
rule evaluation, which is useful for understanding *why* a specific
access is allowed or denied.
@@ -231,9 +231,10 @@ contribution, not the final decision:
through another matching rule, or the right is denied and appears in the
``blockers=`` field of the corresponding ``deny_access`` event.
-To reconstruct the decision for an object, aggregate the ``grants=``
-groups of all ``check_rule`` events emitted for that object during the
-check.
+For an access check that a consumer can delimit, aggregate the ``grants=``
+groups of all matching ``check_rule`` events. These events do not carry a
+request ID; use their execution context and generic tracepoints to separate
+concurrent or successive checks of the same object.
.. note::
@@ -244,29 +245,31 @@ check.
For example, a program sandboxed with read and execute access to the
whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the
-read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires
+read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires
with the requested rights intersected against what that rule grants.
The ``access_request=`` mask includes ``truncate`` because the file-open hook
evaluates that optional right alongside the required access, but the
rule does not grant it, so ``truncate`` never appears in ``grants=``::
- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}
- cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}
+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file}
+ cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file}
The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A
single ``grants=`` group means the enforcing domain has one layer. With
two nested sandboxes that each grant the same rights, the rule spans both
layers, so ``grants=`` has one group per layer::
- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}
- cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}
+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file}
+ cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file}
eBPF access
===========
-eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the
-tracepoint arguments directly through BTF. The arguments include both
-standard kernel objects and Landlock-internal objects:
+BTF-enabled raw tracepoint programs attached through libbpf
+``SEC("tp_btf/...")`` sections receive typed callback arguments. The
+event prototypes in `Event reference`_ document their argument layouts.
+The arguments include both standard kernel objects and Landlock-internal
+objects:
- Standard kernel objects (``struct task_struct``, ``struct sock``,
``struct path``, ``struct dentry``) can be used with existing BPF
@@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects:
Internal struct layouts may change between kernel versions; use CO-RE
for field relocation.
-A stateful eBPF program observes the full event stream and maintains
+A stateful eBPF program attached before sandbox construction can maintain
per-domain state in BPF maps:
1. On ``landlock_create_domain``: record the domain ID and parent (the
@@ -293,7 +296,10 @@ per-domain state in BPF maps:
final statistics.
This approach requires no kernel modification and no Landlock-specific
-BPF helpers. The Landlock IDs serve as correlation keys across events.
+BPF helpers. Landlock IDs serve as correlation keys within one boot.
+Records exported through tracing or BPF buffers can be lost, and records
+from different CPUs are not globally ordered, so consumers must detect and
+reconcile incomplete state.
Audit filtering equivalence
===========================
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index d05253afaf590..523ba5ea98700 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -10,14 +10,19 @@
#if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_LANDLOCK_H
+#include <linux/in.h>
+#include <linux/in6.h>
#include <linux/landlock.h>
+#include <linux/socket.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/tracepoint.h>
#include <linux/trace_seq.h>
#include <net/af_unix.h>
+enum landlock_request_type;
struct dentry;
+struct landlock_blockers;
struct landlock_domain;
struct landlock_hierarchy;
struct landlock_rule;
@@ -26,6 +31,12 @@ struct path;
struct sock;
struct task_struct;
+static_assert(sizeof(access_mask_t) <= sizeof(u64));
+
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY);
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS);
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS);
+
#ifdef CREATE_TRACE_POINTS
/* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */
@@ -108,7 +119,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)
* Fills the dense per-domain-layer array layers (one access mask per layer,
* indexed by level - 1) from rule's sparse layer stack, keeping only the
* requested rights (access_request). Layers with no matching rule entry get
- * a zero mask. Shared by the check_rule_fs and check_rule_net events.
+ * a zero mask. Shared by the check_rule_inode and check_rule_net_port events.
*
* rule->layers is sorted by ascending level, with levels in the domain's
* [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps
@@ -182,6 +193,9 @@ static inline const char *__trace_landlock_print_layers(
/* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */
#define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name }
+#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \
+ { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" }
+
/**
* DOC: Landlock trace events
*
@@ -192,22 +206,22 @@ static inline const char *__trace_landlock_print_layers(
* Decision context
* ~~~~~~~~~~~~~~~~
*
- * A denial event, together with the lifecycle events, exposes the full
- * set of inputs the verdict consumed, so a consumer that tracked domain
- * creation (landlock_create_ruleset, landlock_create_domain) can verify
- * or reproduce the Landlock decision rather than merely observe it
- * happened. In who/what/why terms: who is the denying domain (the domain
- * field, always the subject that enforced the policy, never the current
- * task), what is the operation and its object, and why is every other
- * input the verdict weighed.
+ * A denial event identifies the domain whose policy denied the request, the
+ * Landlock operation and policy object that were checked, and the blocker or
+ * domain relationship responsible for the denial. When tracing starts with
+ * sandbox construction, ruleset and domain events provide the policy history
+ * needed to interpret these identifiers. The denying domain is the subject
+ * that enforced the policy, not necessarily current. Generic tracepoints can
+ * provide additional operational context.
*
* Lifecycle consistency
* ~~~~~~~~~~~~~~~~~~~~~~
*
- * Lifecycle events are balanced: a creation event always has a matching
- * deallocation event and vice versa, so an eBPF program can model object
- * lifetimes from the trace stream without reconciliation logic. A creation
- * event fires while the object is still private to the calling thread
+ * Lifecycle emission is balanced: a creation event always has a matching
+ * deallocation event and vice versa. A consumer that observes an object's
+ * complete lifetime can model it from this pair; one that attaches late or
+ * loses exported records must reconcile incomplete state. A creation event
+ * fires while the object is still private to the calling thread
* (landlock_create_ruleset fires before the ruleset's file descriptor is
* installed, so it cannot race a concurrent :manpage:`close(2)`); if fd
* installation later fails and the ruleset is freed, free_ruleset still
@@ -243,8 +257,8 @@ static inline const char *__trace_landlock_print_layers(
* Field encoding
* ~~~~~~~~~~~~~~
*
- * Fields that mirror the Landlock UAPI use the same C types and endianness
- * (e.g. network ports are __u64 in host endianness, like
+ * Fields that mirror the Landlock UAPI preserve their widths and endianness
+ * (e.g. network ports are u64 in host endianness, like
* landlock_net_port_attr.port). Per-event details, such as where a value
* is byte-swapped, live in the field's own kdoc.
*
@@ -281,6 +295,16 @@ static inline const char *__trace_landlock_print_layers(
* the two parties without kernel-internal state. The ID is a scalar
* snapshot, not a live domain pointer that could dangle: an optional
* relational referent is a scalar (0 sentinel), not a nullable pointer.
+ * Nonzero IDs are unique within one boot. For ptrace, same_exec instead
+ * describes the tracer, even for
+ * PTRACE_TRACEME, and may differ from the current task.
+ *
+ * Blocker fields
+ * ~~~~~~~~~~~~~~
+ *
+ * The filesystem and network blocker arguments identify the request type
+ * and carry its final missing access subset when applicable. The type
+ * determines how to interpret the access value.
*/
/*
@@ -319,8 +343,8 @@ TRACE_EVENT(landlock_create_ruleset,
TP_ARGS(ruleset),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, handled_fs )
__field( access_mask_t, handled_net )
__field( access_mask_t, scoped )
@@ -359,8 +383,8 @@ TRACE_EVENT(landlock_free_ruleset,
TP_ARGS(ruleset),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
),
TP_fast_assign(
@@ -373,12 +397,14 @@ TRACE_EVENT(landlock_free_ruleset,
);
/**
- * landlock_add_rule_fs - Filesystem rule added to a ruleset
+ * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
- * @access_rights: Effective access mask stored in the rule, not the raw
- * sys_landlock_add_rule() argument (unhandled rights
- * added).
+ * @flags: Complete validated landlock_add_rule_flags value supplied by this
+ * successful call, not the rule's accumulated quiet state.
+ * @access_rights: Canonical per-call access mask passed to
+ * landlock_insert_rule() after normalization, not the raw
+ * sys_landlock_add_rule() argument or accumulated rule.
* @path: Filesystem path for the rule (never NULL).
* @pathname: Resolved absolute path string (never NULL; error placeholder
* on resolution failure).
@@ -387,17 +413,17 @@ TRACE_EVENT(landlock_free_ruleset,
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
-TRACE_EVENT(landlock_add_rule_fs,
+TRACE_EVENT(landlock_add_rule_path_beneath,
- TP_PROTO(const struct landlock_ruleset *ruleset,
- access_mask_t access_rights, const struct path *path,
+ TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
+ u64 access_rights, const struct path *path,
const char *pathname),
- TP_ARGS(ruleset, access_rights, path, pathname),
+ TP_ARGS(ruleset, flags, access_rights, path, pathname),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, access_rights )
__field( dev_t, dev )
__field( ino_t, ino )
@@ -427,31 +453,33 @@ TRACE_EVENT(landlock_add_rule_fs,
);
/**
- * landlock_add_rule_net - Network port rule added to a ruleset
+ * landlock_add_rule_net_port - Network-port rule added to a ruleset
*
* @ruleset: Source ruleset (never NULL).
- * @access_rights: Effective access mask stored in the rule, not the raw
- * sys_landlock_add_rule() argument (unhandled rights
- * added).
- * @port: Network port, the landlock_net_port_attr.port UAPI value
- * forwarded directly.
+ * @flags: Complete validated landlock_add_rule_flags value supplied by this
+ * successful call, not the rule's accumulated quiet state.
+ * @access_rights: Canonical per-call access mask passed to
+ * landlock_insert_rule() after normalization, not the raw
+ * sys_landlock_add_rule() argument or accumulated rule.
+ * @port: Network port in host endianness, forwarded directly from
+ * &landlock_net_port_attr.port.
*
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
* the reported ruleset is a stable snapshot that no concurrent writer can
* change.
*/
-TRACE_EVENT(landlock_add_rule_net,
+TRACE_EVENT(landlock_add_rule_net_port,
- TP_PROTO(const struct landlock_ruleset *ruleset,
- access_mask_t access_rights, __u64 port),
+ TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
+ u64 access_rights, u64 port),
- TP_ARGS(ruleset, access_rights, port),
+ TP_ARGS(ruleset, flags, access_rights, port),
TP_STRUCT__entry(
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
__field( access_mask_t, access_rights )
- __field( __u64, port )
+ __field( u64, port )
),
TP_fast_assign(
@@ -495,10 +523,10 @@ TRACE_EVENT(landlock_create_domain,
TP_ARGS(domain, ruleset),
TP_STRUCT__entry(
- __field( __u64, domain_id )
- __field( __u64, parent_id )
- __field( __u64, ruleset_id )
- __field( __u32, ruleset_version )
+ __field( u64, domain_id )
+ __field( u64, parent_id )
+ __field( u64, ruleset_id )
+ __field( u32, ruleset_version )
),
TP_fast_assign(
@@ -557,7 +585,7 @@ TRACE_EVENT(landlock_enforce_domain,
TP_ARGS(domain, complete, process_wide, no_new_privs),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, complete )
__field( bool, process_wide )
__field( bool, no_new_privs )
@@ -595,8 +623,8 @@ TRACE_EVENT(landlock_free_domain,
TP_ARGS(hierarchy),
TP_STRUCT__entry(
- __field( __u64, domain_id )
- __field( __u64, denials )
+ __field( u64, domain_id )
+ __field( u64, denials )
),
TP_fast_assign(
@@ -609,7 +637,7 @@ TRACE_EVENT(landlock_free_domain,
);
/**
- * landlock_check_rule_fs - Filesystem rule evaluated during access check
+ * landlock_check_rule_inode - Inode rule evaluated during access check
*
* @domain: Enforcing domain (never NULL).
* @rule: Matching rule with per-layer access masks (never NULL).
@@ -622,16 +650,16 @@ TRACE_EVENT(landlock_free_domain,
* domain layer. See Documentation/trace/events-landlock.rst for how to
* interpret it.
*/
-TRACE_EVENT(landlock_check_rule_fs,
+TRACE_EVENT(landlock_check_rule_inode,
TP_PROTO(const struct landlock_domain *domain,
const struct landlock_rule *rule,
- access_mask_t access_request, const struct dentry *dentry),
+ u64 access_request, const struct dentry *dentry),
TP_ARGS(domain, rule, access_request, dentry),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( access_mask_t, access_request )
__field( dev_t, dev )
__field( ino_t, ino )
@@ -648,7 +676,8 @@ TRACE_EVENT(landlock_check_rule_fs,
__trace_landlock_fill_layers(__get_dynamic_array(grants),
__get_dynamic_array_len(grants) /
sizeof(access_mask_t),
- rule, access_request);
+ rule,
+ (access_mask_t)access_request);
),
TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s",
@@ -659,7 +688,7 @@ TRACE_EVENT(landlock_check_rule_fs,
);
/**
- * landlock_check_rule_net - Network port rule evaluated during access check
+ * landlock_check_rule_net_port - Network port rule evaluated
*
* @domain: Enforcing domain (never NULL).
* @rule: Matching rule with per-layer access masks (never NULL).
@@ -671,18 +700,18 @@ TRACE_EVENT(landlock_check_rule_fs,
* layer. See Documentation/trace/events-landlock.rst for how to
* interpret it.
*/
-TRACE_EVENT(landlock_check_rule_net,
+TRACE_EVENT(landlock_check_rule_net_port,
TP_PROTO(const struct landlock_domain *domain,
const struct landlock_rule *rule,
- access_mask_t access_request, __u64 port),
+ u64 access_request, u64 port),
TP_ARGS(domain, rule, access_request, port),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( access_mask_t, access_request )
- __field( __u64, port )
+ __field( u64, port )
__dynamic_array(access_mask_t, grants,
domain->num_layers)
),
@@ -695,7 +724,8 @@ TRACE_EVENT(landlock_check_rule_net,
__trace_landlock_fill_layers(__get_dynamic_array(grants),
__get_dynamic_array_len(grants) /
sizeof(access_mask_t),
- rule, access_request);
+ rule,
+ (access_mask_t)access_request);
),
TP_printk("domain=%llx access_request=%s port=%llu grants=%s",
@@ -712,8 +742,7 @@ TRACE_EVENT(landlock_check_rule_net,
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
- * @blockers: Access mask that was blocked (zero for a mount-topology
- * change, whose only blocker is the operation itself).
+ * @blockers: Request type and final missing access subset (never NULL).
* @path: Filesystem path that was denied (never NULL).
* @pathname: Resolved path string (never NULL; an error placeholder on
* resolution failure).
@@ -723,16 +752,17 @@ TRACE_EVENT(landlock_check_rule_net,
TRACE_EVENT(landlock_deny_access_fs,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
- bool logged, access_mask_t blockers, const struct path *path,
- const char *pathname),
+ bool logged, const struct landlock_blockers *blockers,
+ const struct path *path, const char *pathname),
TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( access_mask_t, blockers )
+ __field( enum landlock_request_type, blockers_type )
+ __field( access_mask_t, blockers_access )
__field( dev_t, dev )
__field( ino_t, ino )
__string( pathname, pathname )
@@ -744,7 +774,8 @@ TRACE_EVENT(landlock_deny_access_fs,
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
- __entry->blockers = blockers;
+ __entry->blockers_type = blockers->type;
+ __entry->blockers_access = blockers->access;
__entry->dev = path->dentry->d_sb->s_dev;
/*
* A negative dentry has no backing inode, so mirror the
@@ -756,12 +787,20 @@ TRACE_EVENT(landlock_deny_access_fs,
TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s",
__entry->domain_id, __entry->same_exec, __entry->logged,
- __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES),
+ __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ?
+ __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) :
+ __print_symbolic(__entry->blockers_type,
+ _LANDLOCK_FS_BLOCKER_TYPE_NAMES),
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
__trace_print_untrusted_str(p, __get_str(pathname),
__get_dynamic_array_len(pathname) - 1))
);
+static_assert(offsetof(struct sockaddr_in, sin_port) ==
+ offsetof(struct sockaddr_in6, sin6_port));
+static_assert(sizeof_field(struct sockaddr_in, sin_port) ==
+ sizeof_field(struct sockaddr_in6, sin6_port));
+
/**
* landlock_deny_access_net - Network access denied
*
@@ -769,54 +808,69 @@ TRACE_EVENT(landlock_deny_access_fs,
* domain field.
* @same_exec: Whether the current task entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
- * @blockers: Access mask that was blocked.
- * @sk: Socket object (never NULL), read without a socket lock, so its
- * fields are a best-effort snapshot. The denied endpoint is not
- * available: the hook runs before :manpage:`bind(2)` /
- * :manpage:`connect(2)` sets the socket addresses.
- * @sport: Source port in host endianness, set for bind denials (zero for
- * an autobind/ephemeral port); zero for connect and send denials.
- * @dport: Destination port in host endianness, set for connect and send
- * denials; zero for bind denials, and also zero for a UDP send to
- * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a
- * real destination port 0). The bind-vs-connect direction is
- * given by @blockers, not by which port is set.
- *
- * Emitted when a Landlock domain denies a network operation.
- *
- * The port fields are converted from the socket's network byte order to
- * host endianness before emitting.
+ * @blockers: Request type and final missing access subset (never NULL).
+ * @sk: Socket object (never NULL), read without a socket lock, so its fields
+ * are a best-effort snapshot.
+ * @socket_family: Socket-family snapshot used by the verdict.
+ * @address: Authoritative address checked by the verdict (never NULL).
+ * The producer copies @addrlen bytes from the checked address and
+ * zeroes the remaining storage before emission. The
+ * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when
+ * present, remains in network endianness.
+ * @addrlen: Validated signed length of @address.
+ *
+ * Emitted when a Landlock domain denies a network operation. The blocker
+ * identifies whether the address is a bind or connect/send policy object.
+ * The flattened port field is converted from the checked address to host
+ * endianness, or is -1 when no port was checked. Zero is a valid checked
+ * port.
*/
TRACE_EVENT(landlock_deny_access_net,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
- bool logged, access_mask_t blockers, const struct sock *sk,
- __u64 sport, __u64 dport),
+ bool logged, const struct landlock_blockers *blockers,
+ const struct sock *sk, u16 socket_family,
+ const struct sockaddr_storage *address, int addrlen),
- TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport),
+ TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family,
+ address, addrlen),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( access_mask_t, blockers )
- __field( __u64, sport )
- __field( __u64, dport )
+ __field( enum landlock_request_type, blockers_type )
+ __field( access_mask_t, blockers_access )
+ __field( s64, port )
),
TP_fast_assign(
+ const struct sockaddr *const addr =
+ (const struct sockaddr *)address;
+ const bool has_port =
+ addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) &&
+ (addr->sa_family == AF_INET ||
+ addr->sa_family == AF_INET6 ||
+ (addr->sa_family == AF_UNSPEC &&
+ socket_family == AF_INET));
+
__entry->domain_id = hierarchy->id;
__entry->same_exec = same_exec;
__entry->logged = logged;
- __entry->blockers = blockers;
- __entry->sport = sport;
- __entry->dport = dport;
+ __entry->blockers_type = blockers->type;
+ __entry->blockers_access = blockers->access;
+ __entry->port =
+ has_port ?
+ ntohs(((const struct sockaddr_in *)addr)->sin_port) :
+ -1;
),
- TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu",
- __entry->domain_id, __entry->same_exec, __entry->logged,
- __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES),
- __entry->sport, __entry->dport)
+ TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld",
+ __entry->domain_id, __entry->same_exec, __entry->logged,
+ __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ?
+ __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) :
+ "unknown",
+ __entry->port)
);
/**
@@ -824,12 +878,14 @@ TRACE_EVENT(landlock_deny_access_net,
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
- * @same_exec: Whether the current task entered the denying domain itself.
+ * @same_exec: Whether the tracer entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
* @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee
* is unsandboxed.
* @tracee: The target task ptrace acted on (never NULL). tracee_pid is
* the init-namespace TGID (like audit's opid).
+ * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME
+ * this is the parent, not the syscall caller.
*
* Emitted when a Landlock domain denies a ptrace operation.
*/
@@ -837,15 +893,16 @@ TRACE_EVENT(landlock_deny_ptrace,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, u64 tracee_domain_id,
- const struct task_struct *tracee),
+ const struct task_struct *tracee,
+ const struct task_struct *tracer),
- TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee),
+ TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, tracee_domain_id)
+ __field( u64, tracee_domain_id)
__field( pid_t, tracee_pid )
__string( tracee_comm, tracee->comm )
),
@@ -872,12 +929,14 @@ TRACE_EVENT(landlock_deny_ptrace,
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
* domain field.
- * @same_exec: Whether the current task entered the denying domain itself.
+ * @same_exec: Whether the policy subject entered the denying domain itself.
* @logged: The domain's audit-logging decision for this denial.
* @target_domain_id: The target's Landlock domain ID, or 0 if the target
* is unsandboxed.
* @target: The task the signal was aimed at (never NULL). target_pid is
* the init-namespace TGID (like audit's opid).
+ * @signal: The signal selected by the denied check. Zero is a permission
+ * probe, not an absent value.
*
* Emitted when a Landlock domain denies signal delivery to a scoped-out
* target.
@@ -886,15 +945,15 @@ TRACE_EVENT(landlock_deny_scope_signal,
TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
bool logged, u64 target_domain_id,
- const struct task_struct *target),
+ const struct task_struct *target, int signal),
- TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target),
+ TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, target_domain_id)
+ __field( u64, target_domain_id)
__field( pid_t, target_pid )
__string( target_comm, target->comm )
),
@@ -940,10 +999,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer),
TP_STRUCT__entry(
- __field( __u64, domain_id )
+ __field( u64, domain_id )
__field( bool, same_exec )
__field( bool, logged )
- __field( __u64, peer_domain_id )
+ __field( u64, peer_domain_id )
__field( pid_t, peer_pid )
/*
* Abstract socket names are untrusted binary data from
@@ -973,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
__entry->logged = logged;
__entry->peer_domain_id = peer_domain_id;
/*
- * Best-effort (0 for a datagram peer). sk_peer_pid is
- * canonically guarded by sk->sk_peer_lock, but the target
- * peer's peercred is set once and not updated concurrently in
- * these hooks, so this READ_ONCE() is safe; sun_path is the
- * reliable identifier.
+ * Best-effort (0 for a datagram peer). The caller holds the
+ * peer's AF_UNIX state lock, serializing published peercred
+ * updates. The peer socket keeps a reference to sk_peer_pid
+ * through pid_nr(); sun_path is the reliable identifier.
*/
peer_pid = READ_ONCE(peer->sk_peer_pid);
__entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0;
@@ -991,6 +1049,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
__get_dynamic_array_len(sun_path) - 1))
);
+#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES
#undef _LANDLOCK_NAME_ENTRY
#endif /* _TRACE_LANDLOCK_H */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 330a1871bf94a..cab43892ec2f0 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
* held for BTF consistency (enforced by lockdep_assert_held in
* TP_fast_assign).
*/
- if (!err && trace_landlock_add_rule_fs_enabled()) {
+ if (!err && trace_landlock_add_rule_path_beneath_enabled()) {
char *buffer __free(__putname) = __getname();
const char *pathname =
buffer ? resolve_path_for_trace(path, buffer) :
"<no_mem>";
- trace_landlock_add_rule_fs(ruleset, access_rights, path,
- pathname);
+ trace_landlock_add_rule_path_beneath(
+ ruleset, flags, access_rights, path, pathname);
}
mutex_unlock(&ruleset->lock);
@@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain,
ret = landlock_unmask_layers(domain, id, masks, &rule);
if (rule)
- trace_landlock_check_rule_fs(domain, rule, access_request,
- dentry);
+ trace_landlock_check_rule_inode(domain, rule, access_request,
+ dentry);
return ret;
}
diff --git a/security/landlock/log.h b/security/landlock/log.h
index e0a6e44f3ddd3..faa30e26e42a3 100644
--- a/security/landlock/log.h
+++ b/security/landlock/log.h
@@ -15,6 +15,8 @@
struct landlock_cred_security;
struct landlock_hierarchy;
+struct sockaddr;
+struct task_struct;
enum landlock_request_type {
LANDLOCK_REQUEST_PTRACE = 1,
@@ -25,6 +27,31 @@ enum landlock_request_type {
LANDLOCK_REQUEST_SCOPE_SIGNAL,
};
+struct landlock_blockers {
+ access_mask_t access;
+ enum landlock_request_type type;
+};
+
+#ifdef CONFIG_TRACEPOINTS
+
+struct landlock_net_trace {
+ const struct sockaddr *address;
+ int addrlen;
+ u16 socket_family;
+};
+
+struct landlock_ptrace_trace {
+ u64 tracee_domain_id;
+ const struct task_struct *tracer;
+};
+
+struct landlock_signal_trace {
+ u64 target_domain_id;
+ int signal;
+};
+
+#endif /* CONFIG_TRACEPOINTS */
+
/*
* We should be careful to only use a variable of this type for
* landlock_log_denial(). This way, the compiler can remove it entirely if
@@ -52,13 +79,24 @@ struct landlock_request {
deny_masks_t deny_masks;
optional_access_t quiet_optional_accesses;
- /*
- * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if
- * that party is unsandboxed. An ID, not a pointer: the other task can
- * replace its credential and free the domain it referenced. Trace path
- * only; audit ignores it.
- */
- u64 other_domain_id;
+#ifdef CONFIG_TRACEPOINTS
+ union {
+ /*
+ * Other-party domain ID for an abstract UNIX socket scope
+ * denial, or 0 if that party is unsandboxed. Store an ID, not
+ * a pointer: the other task can replace its credential and free
+ * the domain it referenced.
+ */
+ u64 other_domain_id;
+
+ /* Synchronous context for a network denial. */
+ const struct landlock_net_trace *trace_net;
+ /* Synchronous context for a ptrace denial. */
+ const struct landlock_ptrace_trace *trace_ptrace;
+ /* Synchronous context for a signal denial. */
+ const struct landlock_signal_trace *trace_signal;
+ };
+#endif /* CONFIG_TRACEPOINTS */
};
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 8f2aaac54b334..6fe0dbde3b787 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
* TP_fast_assign).
*/
if (!err)
- trace_landlock_add_rule_net(ruleset, access_rights, port);
+ trace_landlock_add_rule_net_port(ruleset, flags, access_rights,
+ port);
mutex_unlock(&ruleset->lock);
return err;
@@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain,
ret = landlock_unmask_layers(domain, id, masks, &rule);
if (rule)
- trace_landlock_check_rule_net(
+ trace_landlock_check_rule_net_port(
domain, rule, access_request,
ntohs((__force __be16)id.key.data));
return ret;
@@ -92,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock,
return 0;
/* Checks for minimal header length to safely read sa_family. */
- if (addrlen < offsetofend(typeof(*address), sa_family))
+ if (addrlen < (int)offsetofend(typeof(*address), sa_family))
return -EINVAL;
/*
@@ -144,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock,
.audit.u.net = &audit_net,
.access = access_request,
.layer_masks = &layer_masks,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_net =
+ &(struct landlock_net_trace){
+ .address =
+ address,
+ .addrlen =
+ addrlen,
+ .socket_family =
+ sock_family,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EACCES;
}
@@ -275,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock,
audit_net.family = address->sa_family;
audit_net.sk = sock->sk;
- landlock_log_denial(subject,
- &(struct landlock_request){
- .type = LANDLOCK_REQUEST_NET_ACCESS,
- .audit.type = LSM_AUDIT_DATA_NET,
- .audit.u.net = &audit_net,
- .access = access_request,
- .layer_masks = &layer_masks,
- });
+ landlock_log_denial(
+ subject, &(struct landlock_request){
+ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ .audit.type = LSM_AUDIT_DATA_NET,
+ .audit.u.net = &audit_net,
+ .access = access_request,
+ .layer_masks = &layer_masks,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_net =
+ &(struct landlock_net_trace){
+ .address = address,
+ .addrlen = addrlen,
+ .socket_family = sock_family,
+ },
+#endif /* CONFIG_TRACEPOINTS */
+ });
return -EACCES;
}
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 4491ce31ae04a..d9eae86fc5529 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child,
const unsigned int mode)
{
const struct landlock_cred_security *parent_subject;
+#ifdef CONFIG_TRACEPOINTS
u64 tracee_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
int err;
/* Quick return for non-landlocked tasks. */
@@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child,
const struct landlock_domain *const child_dom =
landlock_get_task_domain(child);
err = domain_ptrace(parent_subject->domain, child_dom);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (child_dom)
tracee_domain_id = child_dom->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!err)
@@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child,
.u.tsk = child,
},
.layer_plus_one = parent_subject->domain->num_layers,
- .other_domain_id = tracee_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_ptrace = &(struct landlock_ptrace_trace) {
+ .tracee_domain_id = tracee_domain_id,
+ .tracer = current,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return err;
@@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
{
const struct landlock_cred_security *parent_subject;
const struct landlock_domain *child_dom;
- u64 tracee_domain_id = 0;
int err;
child_dom = landlock_get_current_domain();
@@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
if (!err)
return 0;
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- /* The tracee is the current task; its domain is stable here. */
- if (child_dom)
- tracee_domain_id = child_dom->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
-
/*
* For the ptrace_traceme case, we log the domain which is the cause of
* the denial, which means the parent domain instead of the current
@@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
.u.tsk = current,
},
.layer_plus_one = parent_subject->domain->num_layers,
- .other_domain_id = tracee_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_ptrace = &(struct landlock_ptrace_trace) {
+ /* The current task's domain is stable here. */
+ .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0,
+ .tracer = parent,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return err;
}
@@ -250,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client,
}
static bool sock_is_scoped(struct sock *const other,
- const struct landlock_domain *const domain,
- u64 *const peer_domain_id)
+ const struct landlock_domain *const domain)
{
const struct landlock_domain *dom_other;
@@ -269,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other,
return false;
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return domain_is_scoped(domain, dom_other,
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
}
+#ifdef CONFIG_TRACEPOINTS
+
+static u64 get_socket_domain_id(const struct sock *const other)
+{
+ const struct landlock_domain *domain;
+
+ lockdep_assert_held(&unix_sk(other)->lock);
+ domain = landlock_cred(other->sk_socket->file->f_cred)->domain;
+ return domain ? domain->hierarchy->id : 0;
+}
+
+#endif /* CONFIG_TRACEPOINTS */
+
static bool is_abstract_socket(struct sock *const sock)
{
struct unix_address *addr = unix_sk(sock)->addr;
@@ -299,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const newsk)
{
size_t handle_layer;
- u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -311,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
if (!is_abstract_socket(other))
return 0;
- if (!sock_is_scoped(other, subject->domain, &peer_domain_id))
+ if (!sock_is_scoped(other, subject->domain))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -323,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = peer_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .other_domain_id = get_socket_domain_id(other),
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -332,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock,
struct socket *const other)
{
size_t handle_layer;
- u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -350,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock,
if (!is_abstract_socket(other->sk))
return 0;
- if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id))
+ if (!sock_is_scoped(other->sk, subject->domain))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -362,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = peer_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .other_domain_id = get_socket_domain_id(other->sk),
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -377,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p,
{
bool is_scoped;
size_t handle_layer;
+#ifdef CONFIG_TRACEPOINTS
u64 target_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
const struct landlock_cred_security *subject;
if (!cred) {
@@ -409,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p,
is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (other)
target_domain_id = other->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!is_scoped)
@@ -425,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p,
.u.tsk = p,
},
.layer_plus_one = handle_layer + 1,
- .other_domain_id = target_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_signal = &(struct landlock_signal_trace) {
+ .target_domain_id = target_domain_id,
+ .signal = sig,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
@@ -435,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
{
const struct landlock_cred_security *subject;
bool is_scoped = false;
+#ifdef CONFIG_TRACEPOINTS
u64 target_domain_id = 0;
+#endif /* CONFIG_TRACEPOINTS */
/* Lock already held by send_sigio() and send_sigurg(). */
lockdep_assert_held(&fown->lock);
@@ -468,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+#ifdef CONFIG_TRACEPOINTS
if (other)
target_domain_id = other->hierarchy->id;
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+#endif /* CONFIG_TRACEPOINTS */
}
if (!is_scoped)
@@ -486,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
- .other_domain_id = target_domain_id,
+#ifdef CONFIG_TRACEPOINTS
+ .trace_signal = &(struct landlock_signal_trace) {
+ .target_domain_id = target_domain_id,
+ .signal = signum ? signum : SIGIO,
+ },
+#endif /* CONFIG_TRACEPOINTS */
});
return -EPERM;
}
diff --git a/security/landlock/trace.c b/security/landlock/trace.c
index 8c21e5de6f0de..225dbf37bab09 100644
--- a/security/landlock/trace.c
+++ b/security/landlock/trace.c
@@ -12,6 +12,7 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/lsm_audit.h>
+#include <linux/socket.h>
#include <net/sock.h>
#include "access.h"
@@ -61,8 +62,8 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy
*
* @request: Detail of the user space request.
* @youngest_denied: The youngest hierarchy node that denied the access.
- * @missing: The set of denied access rights.
- * @same_exec: Whether the current task is the same executable that called
+ * @missing: The final missing access 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
@@ -83,6 +84,10 @@ void landlock_trace_denial(
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;
@@ -147,29 +152,67 @@ void landlock_trace_denial(
trace_landlock_deny_access_fs(youngest_denied,
same_exec, logged,
- missing, path, pathname);
+ &blockers, path,
+ pathname);
}
break;
case LANDLOCK_REQUEST_NET_ACCESS:
- if (trace_landlock_deny_access_net_enabled())
+ 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, missing,
+ youngest_denied, same_exec, logged, &blockers,
request->audit.u.net->sk,
- ntohs(request->audit.u.net->sport),
- ntohs(request->audit.u.net->dport));
+ trace_net->socket_family, &address,
+ trace_net->addrlen);
+ }
break;
case LANDLOCK_REQUEST_PTRACE:
- if (trace_landlock_deny_ptrace_enabled())
- trace_landlock_deny_ptrace(youngest_denied, same_exec,
- logged,
- request->other_domain_id,
- request->audit.u.tsk);
+ 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())
+ 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,
- request->other_domain_id, request->audit.u.tsk);
+ 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())
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 18dbdb99aebab..6e979cef884d1 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1)
}
/*
- * Verifies that check_rule_fs events include correct field values: domain, dev,
- * ino, access_request, and grants. All values are verified against stat() of
- * the rule path on a deterministic tmpfs layout.
+ * Verifies that check_rule_inode events include correct field values: domain,
+ * dev, ino, access_request, and grants. All values are verified against stat()
+ * of the rule path on a deterministic tmpfs layout.
*/
TEST_F(trace_layout1, check_rule_fs_fields)
{
@@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields)
EXPECT_EQ(1,
tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)))
{
- TH_LOG("Expected 1 check_rule_fs event\n%s", buf);
+ TH_LOG("Expected 1 check_rule_inode event\n%s", buf);
}
ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
@@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields)
}
/*
- * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and
- * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the
+ * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1
+ * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the
* pathwalk short-circuits after the first rule fully unmasks the single layer.
*/
TEST_F(trace_layout1, check_rule_fs_multiple_rules)
@@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules)
ASSERT_NE(NULL, buf);
/*
- * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the
- * single layer, so the pathwalk short-circuits before reaching the
+ * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks
+ * the single layer, so the pathwalk short-circuits before reaching the
* dir_s1d1 rule.
*/
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count,
+ TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count,
buf);
}
@@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count,
+ TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count,
buf);
}
@@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access)
}
/*
- * Verifies that check_rule_fs fires for a rule that matches the inode even when
+ * Verifies that check_rule_inode fires for a rule matching the inode even when
* it grants none of the requested rights, so the grants set is empty. Landlock
* cannot know a rule ignores the request before reading it, so the event is
* still emitted (grants={}), which lets a tracer see that the rule matched.
@@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
EXPECT_EQ(2, count)
{
- TH_LOG("Expected 2 check_rule_fs events, got %d\n%s", count,
+ TH_LOG("Expected 2 check_rule_inode events, got %d\n%s", count,
buf);
}
@@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
tracefs_count_matches(
buf,
TRACE_PREFIX(
- TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ "
+ TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ "
"access_request=read_dir "
"dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ "
"grants={}$"))
@@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
tracefs_count_matches(
buf,
TRACE_PREFIX(
- TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ "
+ TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ "
"access_request=read_dir "
"dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ "
"grants={read_dir}$"))
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index a18761e0fd82f..16afbfdf06bb2 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind)
}
/*
- * Anchors the denial fields shared by every deny_access_net event so a field
- * test proves more than sport/dport: the denying domain, the same-exec bit, the
- * audit-logging verdict, and the blocked access all stay populated.
+ * Anchors the denial fields shared by every deny_access_net event so a port
+ * test also proves the denying domain, execution status, logging verdict, and
+ * exact blocked access.
*/
static void
expect_net_deny_common_fields(struct __test_metadata *const _metadata,
- const char *const buf)
+ const char *const buf,
+ const char *const expected_blockers)
{
char field[64];
@@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata,
ASSERT_EQ(0,
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
"blockers", field, sizeof(field)));
- EXPECT_STRNE("", field);
+ EXPECT_STREQ(expected_blockers, field);
}
-/* Connect and field-check tests use a separate fixture without variants. */
+enum trace_net_operation {
+ TRACE_NET_BIND,
+ TRACE_NET_SEND,
+};
/* clang-format off */
-FIXTURE(trace_net_connect) {
+FIXTURE(trace_net_address) {
/* clang-format on */
int tracefs_ok;
};
-FIXTURE_SETUP(trace_net_connect)
+FIXTURE_SETUP(trace_net_address)
{
int ret;
@@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect)
clear_cap(_metadata, CAP_SYS_ADMIN);
}
-FIXTURE_TEARDOWN(trace_net_connect)
+FIXTURE_TEARDOWN(trace_net_address)
{
if (!self->tracefs_ok)
return;
@@ -3559,160 +3563,183 @@ FIXTURE_TEARDOWN(trace_net_connect)
}
/* clang-format off */
-FIXTURE_VARIANT(trace_net_connect) {
+FIXTURE_VARIANT(trace_net_address) {
/* clang-format on */
- /* handled_access_net, also the access allowed on the base port. */
- __u64 handled;
- /* Bind the allowed base port before the denied operation. */
- bool bind_base_first;
- /* Denied operation on the next port: connect (true) or bind (false). */
- bool deny_connect;
+ int socket_family;
+ int socket_type;
+ enum trace_net_operation operation;
+ int address_family;
+ socklen_t addrlen;
+ __u64 handled_access;
+ const char *expected_blockers;
+ bool address_port_zero;
+ bool expected_address_port;
+ int expected_port;
};
/* clang-format off */
-
-/* Denied connect(): sport=0, dport=<denied port>. */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) {
- .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP,
- .bind_base_first = false,
- .deny_connect = true,
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) {
+ /* clang-format on */
+ .socket_family = AF_INET,
+ .socket_type = SOCK_STREAM,
+ .operation = TRACE_NET_BIND,
+ .address_family = AF_INET,
+ .addrlen = sizeof(struct sockaddr_in),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+ .expected_blockers = "bind_tcp",
+ .expected_address_port = true,
};
-/* Denied bind(): sport=<denied port>, dport=0. */
-FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) {
- .handled = LANDLOCK_ACCESS_NET_BIND_TCP,
- .bind_base_first = false,
- .deny_connect = false,
+/* Explicit bind(0) has a checked zero port. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) {
+ /* clang-format on */
+ .socket_family = AF_INET,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_BIND,
+ .address_family = AF_INET,
+ .addrlen = sizeof(struct sockaddr_in),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+ .expected_blockers = "bind_udp",
+ .address_port_zero = true,
+ .expected_port = 0,
+};
+
+/* A UDP send can deny its synthetic unspecified bind endpoint. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) {
+ /* clang-format on */
+ .socket_family = AF_INET6,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_SEND,
+ .address_family = AF_INET6,
+ .addrlen = sizeof(struct sockaddr_in6),
+ .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP,
+ .expected_blockers = "bind_udp",
+ .expected_port = 0,
};
-/* Denied connect() after an allowed bind(): the connect fields (sport=0). */
-FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) {
- .handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP,
- .bind_base_first = true,
- .deny_connect = true,
+/* A family-only address has no checked port. */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) {
+ /* clang-format on */
+ .socket_family = AF_INET6,
+ .socket_type = SOCK_DGRAM,
+ .operation = TRACE_NET_SEND,
+ .address_family = AF_UNSPEC,
+ .addrlen = sizeof(sa_family_t),
+ .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
+ .expected_blockers = "connect_send_udp",
+ .expected_port = -1,
};
-/* clang-format on */
+static void set_trace_net_address(struct sockaddr_storage *const storage,
+ const int socket_family,
+ const int address_family,
+ const unsigned short port)
+{
+ memset(storage, 0, sizeof(*storage));
-/*
- * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port
- * is reported in the field matching the denied operation, in host endianness
- * (the UAPI landlock_net_port_attr.port convention): a connect denial reports
- * sport=0 dport=<port>, a bind denial reports sport=<port> dport=0, so a
- * byte-order or field-swap bug is caught. A prior allowed bind
- * (connect_after_bind) does not change the connect denial's fields.
- */
-TEST_F(trace_net_connect, deny_access_net)
+ if (socket_family == AF_INET) {
+ struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage;
+
+ addr4->sin_family = address_family;
+ addr4->sin_port = htons(port);
+ addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else {
+ struct sockaddr_in6 *const addr6 =
+ (struct sockaddr_in6 *)storage;
+
+ addr6->sin6_family = address_family;
+ addr6->sin6_port = htons(port);
+ addr6->sin6_addr = in6addr_loopback;
+ }
+}
+
+/* Verifies the actionable signed port for representative checked shapes. */
+TEST_F(trace_net_address, deny_access_net)
{
- pid_t child;
- int status;
- char *buf;
+ const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK);
+ const unsigned short address_port =
+ variant->address_port_zero ? 0 : sock_port_start + 1;
+ const int expected_port = variant->expected_address_port ?
+ address_port :
+ variant->expected_port;
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = variant->handled_access,
+ };
+ struct sockaddr_storage address;
char field[64], expected[16];
+ char *buf;
+ int count, ret, ruleset_fd, socket_fd, status;
+ pid_t child;
if (!self->tracefs_ok)
SKIP(return, "tracefs not available");
+ set_trace_net_address(&address, variant->socket_family,
+ variant->address_family, address_port);
+ socket_fd = socket(variant->socket_family,
+ variant->socket_type | SOCK_CLOEXEC, 0);
+ ASSERT_LE(0, socket_fd);
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, tracefs_clear_buf());
+
child = fork();
ASSERT_LE(0, child);
-
if (child == 0) {
- struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = variant->handled,
- };
- struct landlock_net_port_attr port_attr = {
- .allowed_access = variant->handled,
- .port = sock_port_start,
- };
- struct sockaddr_in addr = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
- };
- int ruleset_fd, sock_fd, optval = 1, ret;
-
- ruleset_fd = landlock_create_ruleset(&ruleset_attr,
- sizeof(ruleset_attr), 0);
- if (ruleset_fd < 0)
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
_exit(1);
- if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &port_attr, 0)) {
- close(ruleset_fd);
- _exit(1);
- }
- prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
- if (landlock_restrict_self(ruleset_fd, 0)) {
- close(ruleset_fd);
- _exit(1);
- }
+ if (landlock_restrict_self(ruleset_fd, 0))
+ _exit(2);
close(ruleset_fd);
- sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
- if (sock_fd < 0)
- _exit(1);
-
- /* Bind the allowed base port first (succeeds, no event). */
- if (variant->bind_base_first) {
- setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
- sizeof(optval));
- addr.sin_port = htons(sock_port_start);
- if (bind(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr))) {
- close(sock_fd);
- _exit(1);
- }
- }
-
- /* Denied operation on the next port. */
- addr.sin_port = htons(sock_port_start + 1);
- if (variant->deny_connect)
- ret = connect(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr));
- else
- ret = bind(sock_fd, (struct sockaddr *)&addr,
- sizeof(addr));
- if (ret == 0) {
- close(sock_fd);
- _exit(2);
- }
- if (errno != EACCES) {
- close(sock_fd);
+ switch (variant->operation) {
+ case TRACE_NET_BIND:
+ ret = bind(socket_fd, (const struct sockaddr *)&address,
+ variant->addrlen);
+ break;
+ case TRACE_NET_SEND:
+ ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL,
+ (const struct sockaddr *)&address,
+ variant->addrlen);
+ break;
+ default:
_exit(3);
}
- close(sock_fd);
+ if (ret >= 0 || errno != EACCES)
+ _exit(4);
+ close(socket_fd);
+
_exit(0);
}
+ close(ruleset_fd);
+ close(socket_fd);
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_TRUE(WIFEXITED(status));
- EXPECT_EQ(0, WEXITSTATUS(status));
+ ASSERT_EQ(0, WEXITSTATUS(status));
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
-
- EXPECT_EQ(1, tracefs_count_matches(buf,
- REGEX_DENY_ACCESS_NET(TRACE_TASK)));
-
- expect_net_deny_common_fields(_metadata, buf);
-
- /*
- * The denied operation's port field carries the port; the other is 0.
- */
- snprintf(expected, sizeof(expected), "%llu",
- (unsigned long long)(sock_port_start + 1));
-
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
- "sport", field, sizeof(field)));
- EXPECT_STREQ(variant->deny_connect ? "0" : expected, field);
-
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
- "dport", field, sizeof(field)));
- EXPECT_STREQ(variant->deny_connect ? expected : "0", field);
+ count = tracefs_count_matches(buf, event_regex);
+ if (count != 1)
+ TH_LOG("Expected 1 denial event, got %d\n%s", count, buf);
+ ASSERT_EQ(1, count);
+ expect_net_deny_common_fields(_metadata, buf,
+ variant->expected_blockers);
+
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field,
+ sizeof(field)));
+ snprintf(expected, sizeof(expected), "%d", expected_port);
+ EXPECT_STREQ(expected, field);
free(buf);
}
-/* Field verification for the check_rule_net event on an allowed access. */
+/* Field verification for the check_rule_net_port event on an allowed access. */
/* clang-format off */
FIXTURE(trace_net_check_rule) {
@@ -3757,10 +3784,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule)
/*
* Verifies that an allowed bind matching a net-port rule emits exactly one
- * landlock_check_rule_net event with the enforcing domain, the requested
+ * landlock_check_rule_net_port event with the enforcing domain, the requested
* access, the checked port (host endianness), and the per-layer grants. The
- * whole event is anchored to exact values so a revert of the check_rule_net
- * emit (or a byte-order or field-plumbing regression) fails the test.
+ * whole event is anchored to exact values so removing the check_rule_net_port
+ * emission or introducing a byte-order or field-plumbing regression fails the
+ * test.
*/
TEST_F(trace_net_check_rule, check_rule_net_fields)
{
@@ -3832,7 +3860,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)
EXPECT_EQ(1,
tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK)))
{
- TH_LOG("Expected 1 check_rule_net event\n%s", buf);
+ TH_LOG("Expected 1 check_rule_net_port event\n%s", buf);
}
ASSERT_EQ(0,
@@ -3866,11 +3894,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields)
free(buf);
}
-/*
- * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses
- * the same current_check_access_socket() code path as IPv4, validated by the
- * audit tests in this file. The trace events use the same blockers/sport/dport
- * fields regardless of address family.
- */
-
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h
index ba0c5e92001f1..2ec8633621732 100644
--- a/tools/testing/selftests/landlock/trace.h
+++ b/tools/testing/selftests/landlock/trace.h
@@ -27,14 +27,14 @@
TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable"
#define TRACEFS_ENFORCE_DOMAIN_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_enforce_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_ADD_RULE_PATH_BENEATH_ENABLE \
+ TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable"
+#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \
+ TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable"
#define TRACEFS_CHECK_RULE_FS_ENABLE \
- TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable"
+ TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable"
#define TRACEFS_CHECK_RULE_NET_ENABLE \
- TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable"
+ TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable"
#define TRACEFS_DENY_ACCESS_FS_ENABLE \
TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable"
#define TRACEFS_DENY_ACCESS_NET_ENABLE \
@@ -79,18 +79,18 @@
*/
#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]\\+ " \
+#define REGEX_ADD_RULE_PATH_BENEATH(task) \
+ TRACE_PREFIX(task) \
+ "landlock_add_rule_path_beneath: " \
+ "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) \
+#define REGEX_ADD_RULE_NET_PORT(task) \
TRACE_PREFIX(task) \
- "landlock_add_rule_net: " \
+ "landlock_add_rule_net_port: " \
"ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \
"access_rights=[a-z_|]* " \
"port=[0-9]\\+$"
@@ -110,21 +110,21 @@
"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]\\+ " \
+#define REGEX_CHECK_RULE_FS(task) \
+ TRACE_PREFIX(task) \
+ "landlock_check_rule_inode: " \
+ "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]\\+ " \
+#define REGEX_CHECK_RULE_NET(task) \
+ TRACE_PREFIX(task) \
+ "landlock_check_rule_net_port: " \
+ "domain=[0-9a-f]\\+ " \
+ "access_request=[a-z_|]* " \
+ "port=[0-9]\\+ " \
"grants={[a-z_|,]*}$"
#define REGEX_DENY_ACCESS_FS(task) \
@@ -145,8 +145,7 @@
"same_exec=[01] " \
"logged=[01] " \
"blockers=[a-z_|]* " \
- "sport=[0-9]\\+ " \
- "dport=[0-9]\\+$"
+ "port=-\\?[0-9]\\+$"
#define REGEX_DENY_PTRACE(task) \
TRACE_PREFIX(task) \
diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c
index 4543a25c1f559..64014ade3a0e9 100644
--- a/tools/testing/selftests/landlock/trace_fs_test.c
+++ b/tools/testing/selftests/landlock/trace_fs_test.c
@@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs)
}
self->tracefs_ok = 1;
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_clear());
@@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs)
return;
set_cap(_metadata, CAP_SYS_ADMIN);
- tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
tracefs_fixture_teardown();
@@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed)
}
/*
- * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace
- * event with the expected path and field values: ruleset ID is non-zero,
- * access_rights is non-zero, and path matches.
+ * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath
+ * event with the expected path and field values: the ruleset ID and
+ * access_rights are non-zero, and the path matches.
*/
-TEST_F(trace_fs, add_rule_fs)
+TEST_F(trace_fs, add_rule_path_beneath)
{
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
@@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ count = tracefs_count_matches(buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf);
+ TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s",
+ count, buf);
}
/* Ruleset ID should be non-zero. */
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "ruleset", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "ruleset", field_buf, sizeof(field_buf)));
EXPECT_STRNE("0", field_buf);
/* Access rights should be non-zero. */
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "access_rights", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "access_rights", field_buf, sizeof(field_buf)));
EXPECT_STRNE("", field_buf);
/* Path should be /usr. */
- ASSERT_EQ(0,
- tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "path", field_buf, sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "path", field_buf, sizeof(field_buf)));
EXPECT_STREQ("/usr", field_buf);
free(buf);
@@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs)
* Verifies that a path whose escaping exceeds the trace scratch sequence does
* not corrupt a sibling symbolic field.
*/
-TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
+TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow)
{
static const char access_prefix[] = "execute|write_file|read_file|";
static const char access_suffix[] = "|ioctl_dev|resolve_unix";
@@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ count = tracefs_count_matches(buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
EXPECT_EQ(1, count)
{
- TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf);
+ TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s",
+ count, buf);
}
/*
@@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
* field also catches scratch-sequence poisoning when the compiler
* evaluates the overflowing path first, as GCC currently does.
*/
- ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK),
- "access_rights", field_buf,
- sizeof(field_buf)));
+ ASSERT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK),
+ "access_rights", field_buf, sizeof(field_buf)));
EXPECT_EQ(0,
strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1));
EXPECT_EQ(NULL, strstr(field_buf, "|refer|"));
@@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow)
ASSERT_LE(sizeof(access_suffix) - 1, field_len);
EXPECT_STREQ(access_suffix,
field_buf + field_len - (sizeof(access_suffix) - 1));
- expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK));
+ expect_truncated_path(_metadata, buf,
+ REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK));
free(buf);
}
@@ -542,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested)
*/
TEST_F(trace_fs, deny_access_fs_denied)
{
- char *buf;
+ const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+ char *buf, blockers[64];
int count;
ASSERT_EQ(0, tracefs_clear_buf());
@@ -558,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK));
- EXPECT_LE(1, count);
+ count = tracefs_count_matches(buf, event_regex);
+ EXPECT_EQ(1, count)
+ {
+ TH_LOG("Expected 1 access denial, got %d\n%s", count, buf);
+ }
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+ blockers, sizeof(blockers)));
+ EXPECT_STREQ("read_dir", blockers);
+
+ free(buf);
+}
+
+/*
+ * Verifies that a denied mount reports the singleton topology blocker rather
+ * than an empty access mask.
+ */
+TEST_F(trace_fs, deny_change_topology)
+{
+ const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK);
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ };
+ char *buf, blockers[64];
+ int count, ruleset_fd, status;
+ pid_t pid;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, tracefs_clear_buf());
+
+ /* Ensure that Landlock is the only expected mount denial. */
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ pid = fork();
+ ASSERT_LE(0, pid);
+ if (pid == 0) {
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ close(ruleset_fd);
+ _exit(1);
+ }
+ if (landlock_restrict_self(ruleset_fd, 0)) {
+ close(ruleset_fd);
+ _exit(2);
+ }
+ close(ruleset_fd);
+
+ if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1)
+ _exit(3);
+
+ if (errno != EPERM)
+ _exit(4);
+
+ _exit(0);
+ }
+ close(ruleset_fd);
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+
+ ASSERT_EQ(pid, waitpid(pid, &status, 0));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ buf = tracefs_read_buf();
+ ASSERT_NE(NULL, buf);
+ count = tracefs_count_matches(buf, event_regex);
+ EXPECT_EQ(1, count)
+ {
+ TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf);
+ }
+ ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers",
+ blockers, sizeof(blockers)));
+ EXPECT_STREQ("change_topology", blockers);
free(buf);
}
diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c
index afdaf8511b3a1..f9b293a9dd565 100644
--- a/tools/testing/selftests/landlock/trace_test.c
+++ b/tools/testing/selftests/landlock/trace_test.c
@@ -49,8 +49,10 @@ FIXTURE_SETUP(trace)
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ true));
+ ASSERT_EQ(0,
+ tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true));
@@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace)
tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false);
tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false);
tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false);
- tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false);
- tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false);
+ tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false);
tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);
tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false);
@@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled)
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false));
ASSERT_EQ(0,
tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false));
- ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE,
+ false));
+ ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE,
+ false));
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false));
ASSERT_EQ(0,
tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false));
@@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version)
ASSERT_NE(0, !!dot);
EXPECT_STREQ("0", dot + 1);
- /* Verify 2 add_rule_fs events were emitted. */
- EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
+ /* Verify two add_rule_path_beneath events were emitted. */
+ EXPECT_EQ(2, tracefs_count_matches(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
{
- TH_LOG("Expected 2 add_rule_fs events\n%s", buf);
+ TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf);
}
/*
@@ -373,7 +378,7 @@ TEST_F(trace, create_domain)
tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK));
ASSERT_LE(1, check_count)
{
- TH_LOG("Expected check_rule_fs events\n%s", buf);
+ TH_LOG("Expected check_rule_inode events\n%s", buf);
}
EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK),
@@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)))
+ EXPECT_EQ(0, tracefs_count_matches(
+ buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)))
{
- TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf);
+ TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s",
+ buf);
}
free(buf);
@@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting)
}
/*
- * Verifies that landlock_add_rule_net emits a trace event with the correct port
- * and allowed access mask fields.
+ * Verifies that landlock_add_rule_net_port emits a trace event with the correct
+ * port and allowed access mask fields.
*/
-TEST_F(trace, add_rule_net_fields)
+TEST_F(trace, add_rule_net_port_fields)
{
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
@@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields)
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
- EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK)))
+ EXPECT_EQ(1, tracefs_count_matches(buf,
+ REGEX_ADD_RULE_NET_PORT(TRACE_TASK)))
{
- TH_LOG("Expected 1 add_rule_net event\n%s", buf);
+ TH_LOG("Expected 1 add_rule_net_port event\n%s", buf);
}
/*
@@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields)
* (landlock_net_port_attr.port). On little-endian, htons(8080) is
* 36895, so this comparison catches byte-order bugs.
*/
- EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
+ EXPECT_EQ(0, tracefs_extract_field(buf,
+ REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
"port", field, sizeof(field)));
EXPECT_STREQ("8080", field);
/*
@@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields)
* net access bits are unhandled because the ruleset only handles
* BIND_TCP).
*/
- EXPECT_EQ(0,
- tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK),
- "access_rights", field, sizeof(field)));
+ EXPECT_EQ(0, tracefs_extract_field(
+ buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK),
+ "access_rights", field, sizeof(field)));
EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field);
free(buf);
|
| 43/3 | 2026/09/20 23:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 32882 output: 21 thoughts: 2053 Thoughts: **Landlock Patch Analysis: KASAN vs. KMSAN** Okay, let's dive into this Landlock patch series. My initial thought is to determine if we need to bring in KMSAN or if KASAN is sufficient for fuzzing. The changes are spread across various files, including documentation, headers, core functionality, and selftests. First, I'm noting the changes in the `include/trace/events/landlock.h` file. We have some event renames: `landlock_add_rule_fs` to `landlock_add_rule_path_beneath`, `landlock_add_rule_net` to `landlock_add_rule_net_port`, and the check rule events too. The changes to the fields are where things get interesting. Particularly: * In `landlock_deny_access_fs`, the new use of `const struct landlock_blockers *blockers` instead of `access_mask_t blockers` storing `blockers_type` and `blockers_access`. * The `landlock_deny_access_net` event is now complex. It's handling a socket address and network information, and it's passing a `struct sockaddr_storage address`. But where is this coming from? The structure includes a port, but the code checks `addrlen` to make sure we're not reading past the end of the `address`. The tracepoint actually copies from a trace buffer to a zero-initialized buffer, then uses that. The socket address is coming from the `LSM` hooks, and it is a syscall argument. This address is then copied into the trace entry! I need to confirm that `address` or any portion of it is not sent to userspace. The trace entries are written to the ftrace ring buffer. Examining the `TP_STRUCT__entry`, I see only primitive types are being assigned to fields. No raw pointers are exposed. Port is calculated correctly with `ntohs` on the incoming address. The `landlock_request` union contains `trace_net`, but it's used correctly within the tracepoint context, so no uninitialized data leaks. This is especially important. The tracepoint entries are written to the ftrace ring buffer. Tracepoints write to the ring buffer via `TP_fast_assign`. I see only primitive types are being assigned to fields, and none of them are uninitialized. The `address` isn't stored in `__entry`, only `port`, which is within `addrlen` bytes. So, given the detailed analysis, it seems that the changes don't introduce any obvious memory safety vulnerabilities. The code carefully handles potentially uninitialized memory and bounds. This means I can likely proceed with KASAN for fuzzing. KMSAN might be overkill here. I am confident in this decision because I am taking into account my understanding of the kernel internals and tracepoints, as well as the changes made by the patchset. |
| 44/3 | 2026/09/20 23:55 | tool | grepper |
0mArgs:{
"Expression": "landlock_trace_denial"
}Results: {
"Output": "security/landlock/log.c=496=void landlock_log_denial(const struct landlock_cred_security *const subject,\n--\nsecurity/landlock/log.c-546-\nsecurity/landlock/log.c:547:\tlandlock_trace_denial(request, youngest_denied, missing, same_exec,\nsecurity/landlock/log.c-548-\t\t\t logged);\n--\nsecurity/landlock/trace.c=47=void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy)\n--\nsecurity/landlock/trace.c-60-/**\nsecurity/landlock/trace.c:61: * landlock_trace_denial - Emit a tracepoint for a denied access request\nsecurity/landlock/trace.c-62- *\n--\nsecurity/landlock/trace.c-77- */\nsecurity/landlock/trace.c:78:void landlock_trace_denial(\nsecurity/landlock/trace.c-79-\tconst struct landlock_request *const request,\n--\nsecurity/landlock/trace.h=19=void landlock_trace_free_domain(\n--\nsecurity/landlock/trace.h-21-\nsecurity/landlock/trace.h:22:void landlock_trace_denial(\nsecurity/landlock/trace.h-23-\tconst struct landlock_request *const request,\n--\nsecurity/landlock/trace.h=34=static inline void\nsecurity/landlock/trace.h:35:landlock_trace_denial(const struct landlock_request *const request,\nsecurity/landlock/trace.h-36-\t\t const struct landlock_hierarchy *const youngest_denied,\n"
} |
| 45/3 | 2026/09/20 23:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36027 output: 36 thoughts: 25 Thoughts: Here's my take on those files: **Diving into Landlock Tracing and Logging** Okay, so I'm looking at `security/landlock/trace.c` and `security/landlock/log.c`. Immediately, I want to see how the kernel's Landlock tracing infrastructure is structured. My expectation is that `trace.c` will contain the core logic for capturing and recording Landlock events, probably using ftrace or kprobe mechanisms. I'll be looking for things like: what specific events are being traced (rule creations, access checks, permission denials, etc.) and how the tracepoints are defined. Are they verbose, or targeted? Are they optimized for low overhead? I'll also want to understand the data structures associated with these traces - what information is being captured for each event (filenames, PIDs, permissions, access modes, context information)? I'm anticipating the usual tracepoint suspects, like `landlock_rule_created`, `landlock_access_denied`, etc., but I'll be paying close attention to any unique Landlock-specific traces. The way the trace data is formatted and the level of granularity will tell me a lot about Landlock's debugging capabilities. Then, I'll switch over to `log.c`. This will likely deal with formatting and outputting the trace data, either to the kernel ring buffer, a dedicated Landlock log file (if any), or maybe even pushing it out via a user-space interface. I expect to see functions for handling trace output, filtering events (potentially based on configuration), and possibly providing user-friendly log messages. I'll be interested in how they're handling rate limiting or preventing log floods, and if any security considerations are present in the logging implementation itself (e.g., preventing log tampering). Ideally, they'll have some clever filtering mechanisms in place so you can dial up or down the verbosity based on need. I'm also curious to see if they're using anything like `printk` or building their own logging framework internally. My hope is it provides a good balance between detail and performance, which is a constant challenge. This review will help me understand how much visibility Landlock offers for troubleshooting and auditing. |
| 46/3 | 2026/09/20 23:55 | tool | read-file |
0mArgs:{
"File": "security/landlock/log.c",
"FirstLine": 495,
"LineCount": 60
}Results: {
"Contents": " 495:\t */\n 496:\tvoid landlock_log_denial(const struct landlock_cred_security *const subject,\n 497:\t\t\t\t const struct landlock_request *const request)\n 498:\t{\n 499:\t\tstruct landlock_hierarchy *youngest_denied;\n 500:\t\tsize_t youngest_layer;\n 501:\t\taccess_mask_t missing;\n 502:\t\tbool object_quiet_flag = false;\n 503:\t\n 504:\t\tif (WARN_ON_ONCE(!subject || !subject-\u003edomain ||\n 505:\t\t\t\t !subject-\u003edomain-\u003ehierarchy || !request))\n 506:\t\t\treturn;\n 507:\t\n 508:\t\tif (!is_valid_request(request))\n 509:\t\t\treturn;\n 510:\t\n 511:\t\tmissing = request-\u003eaccess;\n 512:\t\tif (missing) {\n 513:\t\t\t/* Gets the nearest domain that denies the request. */\n 514:\t\t\tif (request-\u003elayer_masks) {\n 515:\t\t\t\tyoungest_layer = get_denied_layer(subject-\u003edomain,\n 516:\t\t\t\t\t\t\t\t \u0026missing,\n 517:\t\t\t\t\t\t\t\t request-\u003elayer_masks);\n 518:\t\t\t\tobject_quiet_flag =\n 519:\t\t\t\t\trequest-\u003elayer_masks-\u003elayers[youngest_layer]\n 520:\t\t\t\t\t\t.quiet;\n 521:\t\t\t} else {\n 522:\t\t\t\tyoungest_layer = get_layer_from_deny_masks(\n 523:\t\t\t\t\t\u0026missing, _LANDLOCK_ACCESS_FS_OPTIONAL,\n 524:\t\t\t\t\trequest-\u003edeny_masks,\n 525:\t\t\t\t\trequest-\u003equiet_optional_accesses,\n 526:\t\t\t\t\t\u0026object_quiet_flag);\n 527:\t\t\t}\n 528:\t\t\tyoungest_denied =\n 529:\t\t\t\tget_hierarchy(subject-\u003edomain, youngest_layer);\n 530:\t\t} else {\n 531:\t\t\tyoungest_layer = request-\u003elayer_plus_one - 1;\n 532:\t\t\tyoungest_denied =\n 533:\t\t\t\tget_hierarchy(subject-\u003edomain, youngest_layer);\n 534:\t\t}\n 535:\t\n 536:\t\tconst bool same_exec = !!(subject-\u003edomain_exec \u0026 BIT(youngest_layer));\n 537:\t\tconst bool logged = is_denial_logged(request, youngest_denied, missing,\n 538:\t\t\t\t\t\t same_exec, object_quiet_flag);\n 539:\t\n 540:\t\t/*\n 541:\t\t * Consistently keeps track of the number of denied access requests even\n 542:\t\t * if audit is currently disabled, or if audit rules currently exclude\n 543:\t\t * this record type, or if landlock_restrict_self(2)'s flags quiet logs.\n 544:\t\t */\n 545:\t\tatomic64_inc(\u0026youngest_denied-\u003enum_denials);\n 546:\t\n 547:\t\tlandlock_trace_denial(request, youngest_denied, missing, same_exec,\n 548:\t\t\t\t logged);\n 549:\t\tlandlock_audit_denial(request, youngest_denied, missing, logged);\n 550:\t}\n 551:\t\n 552:\t/**\n 553:\t * landlock_log_free_domain - Log domain deallocation\n 554:\t *\n"
} |
| 47/3 | 2026/09/20 23:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37467 output: 21 thoughts: 50 Thoughts: Okay, let's dive into this `landlock_log_denial` function. My first move is always to understand its context, so I need to map out every single place it's called. Gotta see the bigger picture, you know? **Tracing the Denials: Understanding Landlock Request Initialization** Now that I have the call sites, I'm going to carefully inspect how the `struct landlock_request` is being initialized in each one. The initialization is key. It's the starting point, the foundation upon which everything else is built. I want to see how the request's state is being defined. What kind of data is being fed into the request structure in different scenarios? Is there consistency? Any patterns that might reveal potential vulnerabilities or opportunities for optimization? I'll be looking for inconsistencies, redundancies, and any unusual initializations that warrant a closer look. Let's see what the code reveals. Time to search for `landlock_log_denial`. |
| 48/3 | 2026/09/20 23:55 | tool | grepper |
0mArgs:{
"Expression": "landlock_log_denial"
}Results: {
"Output": "security/landlock/audit.c=129=static void log_domain(struct landlock_hierarchy *const hierarchy)\n--\nsecurity/landlock/audit.c-167- * @logged: Whether the denial is selected for logging, as computed by\nsecurity/landlock/audit.c:168: * landlock_log_denial() (domain policy and quiet rules).\nsecurity/landlock/audit.c-169- *\n--\nsecurity/landlock/audit.c=173=void landlock_audit_denial(const struct landlock_request *const request,\n--\nsecurity/landlock/audit.c-183-\t * Skips denials the domain's policy or a quiet rule excludes from\nsecurity/landlock/audit.c:184:\t * logging (folded into @logged by landlock_log_denial()).\nsecurity/landlock/audit.c-185-\t */\n--\nsecurity/landlock/fs.c=1030=static int current_check_access_path(const struct path *const path,\n--\nsecurity/landlock/fs.c-1051-\nsecurity/landlock/fs.c:1052:\tlandlock_log_denial(subject, \u0026request);\nsecurity/landlock/fs.c-1053-\treturn -EACCES;\n--\nsecurity/landlock/fs.c=1229=static int current_check_refer_path(struct dentry *const old_dentry,\n--\nsecurity/landlock/fs.c-1286-\nsecurity/landlock/fs.c:1287:\t\tlandlock_log_denial(subject, \u0026request1);\nsecurity/landlock/fs.c-1288-\t\treturn -EACCES;\n--\nsecurity/landlock/fs.c-1336-\t\trequest1.audit.u.path.dentry = old_parent;\nsecurity/landlock/fs.c:1337:\t\tlandlock_log_denial(subject, \u0026request1);\nsecurity/landlock/fs.c-1338-\t}\n--\nsecurity/landlock/fs.c-1340-\t\trequest2.audit.u.path.dentry = new_dir-\u003edentry;\nsecurity/landlock/fs.c:1341:\t\tlandlock_log_denial(subject, \u0026request2);\nsecurity/landlock/fs.c-1342-\t}\n--\nsecurity/landlock/fs.c=1488=log_fs_change_topology_path(const struct landlock_cred_security *const subject,\n--\nsecurity/landlock/fs.c-1490-{\nsecurity/landlock/fs.c:1491:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/fs.c-1492-\t\t.type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,\n--\nsecurity/landlock/fs.c=1501=static void log_fs_change_topology_dentry(\n--\nsecurity/landlock/fs.c-1504-{\nsecurity/landlock/fs.c:1505:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/fs.c-1506-\t\t.type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,\n--\nsecurity/landlock/fs.c=1749=static int hook_unix_find(const struct path *const path, struct sock *other,\n--\nsecurity/landlock/fs.c-1801-\nsecurity/landlock/fs.c:1802:\tlandlock_log_denial(subject, \u0026request);\nsecurity/landlock/fs.c-1803-\treturn -EACCES;\n--\nsecurity/landlock/fs.c=1856=static int hook_file_open(struct file *const file)\n--\nsecurity/landlock/fs.c-1923-\trequest.access = open_access_request;\nsecurity/landlock/fs.c:1924:\tlandlock_log_denial(subject, \u0026request);\nsecurity/landlock/fs.c-1925-\treturn -EACCES;\n--\nsecurity/landlock/fs.c=1928=static int hook_file_truncate(struct file *const file)\n--\nsecurity/landlock/fs.c-1942-\nsecurity/landlock/fs.c:1943:\tlandlock_log_denial(landlock_cred(file-\u003ef_cred), \u0026(struct landlock_request) {\nsecurity/landlock/fs.c-1944-\t\t.type = LANDLOCK_REQUEST_FS_ACCESS,\n--\nsecurity/landlock/fs.c=1959=static int hook_file_ioctl_common(const struct file *const file,\n--\nsecurity/landlock/fs.c-1979-\nsecurity/landlock/fs.c:1980:\tlandlock_log_denial(landlock_cred(file-\u003ef_cred), \u0026(struct landlock_request) {\nsecurity/landlock/fs.c-1981-\t\t.type = LANDLOCK_REQUEST_FS_ACCESS,\n--\nsecurity/landlock/log.c=432=is_denial_quieted(const struct landlock_request *const request,\n--\nsecurity/landlock/log.c-469- * same_exec must be set, and no quiet rule may cover the denied access.\nsecurity/landlock/log.c:470: * landlock_log_denial() computes this once and passes it to\nsecurity/landlock/log.c-471- * landlock_audit_denial(), which additionally requires audit_enabled.\n--\nsecurity/landlock/log.c=474=is_denial_logged(const struct landlock_request *const request,\n--\nsecurity/landlock/log.c-490-/**\nsecurity/landlock/log.c:491: * landlock_log_denial - Log a denied access\nsecurity/landlock/log.c-492- *\n--\nsecurity/landlock/log.c-495- */\nsecurity/landlock/log.c:496:void landlock_log_denial(const struct landlock_cred_security *const subject,\nsecurity/landlock/log.c-497-\t\t\t const struct landlock_request *const request)\n--\nsecurity/landlock/log.h=48=struct landlock_signal_trace {\n--\nsecurity/landlock/log.h-56- * We should be careful to only use a variable of this type for\nsecurity/landlock/log.h:57: * landlock_log_denial(). This way, the compiler can remove it entirely if\nsecurity/landlock/log.h-58- * CONFIG_SECURITY_LANDLOCK_LOG is not set.\n--\nsecurity/landlock/log.h=104=void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy);\nsecurity/landlock/log.h-105-\nsecurity/landlock/log.h:106:void landlock_log_denial(const struct landlock_cred_security *const subject,\nsecurity/landlock/log.h-107-\t\t\t const struct landlock_request *const request);\n--\nsecurity/landlock/log.h=116=static inline void\nsecurity/landlock/log.h:117:landlock_log_denial(const struct landlock_cred_security *const subject,\nsecurity/landlock/log.h-118-\t\t const struct landlock_request *const request)\n--\nsecurity/landlock/net.c=73=static int current_check_access_socket(struct socket *const sock,\n--\nsecurity/landlock/net.c-138-\t\t\t\t\t\u0026layer_masks, LANDLOCK_KEY_NET_PORT);\nsecurity/landlock/net.c:139:\t\t\t\tlandlock_log_denial(\nsecurity/landlock/net.c-140-\t\t\t\t\tsubject,\n--\nsecurity/landlock/net.c-289-\taudit_net.sk = sock-\u003esk;\nsecurity/landlock/net.c:290:\tlandlock_log_denial(\nsecurity/landlock/net.c-291-\t\tsubject, \u0026(struct landlock_request){\n--\nsecurity/landlock/task.c=87=static int hook_ptrace_access_check(struct task_struct *const child,\n--\nsecurity/landlock/task.c-118-\tif (!(mode \u0026 PTRACE_MODE_NOAUDIT))\nsecurity/landlock/task.c:119:\t\tlandlock_log_denial(parent_subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-120-\t\t\t.type = LANDLOCK_REQUEST_PTRACE,\n--\nsecurity/landlock/task.c=148=static int hook_ptrace_traceme(struct task_struct *const parent)\n--\nsecurity/landlock/task.c-169-\t */\nsecurity/landlock/task.c:170:\tlandlock_log_denial(parent_subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-171-\t\t.type = LANDLOCK_REQUEST_PTRACE,\n--\nsecurity/landlock/task.c=312=static int hook_unix_stream_connect(struct sock *const sock,\n--\nsecurity/landlock/task.c-330-\nsecurity/landlock/task.c:331:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-332-\t\t.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\n--\nsecurity/landlock/task.c=347=static int hook_unix_may_send(struct socket *const sock,\n--\nsecurity/landlock/task.c-370-\nsecurity/landlock/task.c:371:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-372-\t\t.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\n--\nsecurity/landlock/task.c=391=static int hook_task_kill(struct task_struct *const p,\n--\nsecurity/landlock/task.c-439-\nsecurity/landlock/task.c:440:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-441-\t\t.type = LANDLOCK_REQUEST_SCOPE_SIGNAL,\n--\nsecurity/landlock/task.c=457=static int hook_file_send_sigiotask(struct task_struct *tsk,\n--\nsecurity/landlock/task.c-505-\nsecurity/landlock/task.c:506:\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c-507-\t\t.type = LANDLOCK_REQUEST_SCOPE_SIGNAL,\n--\nsecurity/landlock/trace.c=47=void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy)\n--\nsecurity/landlock/trace.c-67- * landlock_restrict_self() for the denying domain, as computed\nsecurity/landlock/trace.c:68: * by landlock_log_denial().\nsecurity/landlock/trace.c-69- * @logged: Whether the domain's policy selects this denial for logging, as\nsecurity/landlock/trace.c:70: * computed by landlock_log_denial().\nsecurity/landlock/trace.c-71- *\n--\nsecurity/landlock/trace.c-75- *\nsecurity/landlock/trace.c:76: * Called from landlock_log_denial().\nsecurity/landlock/trace.c-77- */\n--\ntools/testing/selftests/landlock/audit.h=361=struct audit_records {\n--\ntools/testing/selftests/landlock/audit.h-370- * audit_init() and after the preceding audit_match_record() call. Allocation\ntools/testing/selftests/landlock/audit.h:371: * records are emitted synchronously during landlock_log_denial() in the current\ntools/testing/selftests/landlock/audit.h-372- * test's syscall context, so only those are counted in records-\u003edomain.\n"
} |
| 49/3 | 2026/09/20 23:55 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40851 output: 35 thoughts: 1062 Thoughts: **Analyzing `struct landlock_request` Initialization and Potential Memory Safety Issues**
Okay, let's break this down systematically. I'm focusing on potential issues related to the initialization and usage of the `struct landlock_request` in the Landlock security module, specifically concerning memory safety.
First, I examined the initialization of `struct landlock_request` in `fs.c`. I checked the various cases like `current_check_access_path`, etc. The code uses designated initializers. Crucially, fields like `other_domain_id`, `trace_net`, `trace_ptrace`, and `trace_signal` aren't touched in these scenarios. Since C standard rules mandate zero-initialization for unmentioned fields in designated initializers, these fields will be zeroed, which is a good thing for safety, in this context. I confirmed this by tracing the usage of the struct within `landlock_trace_denial`. It *only* uses `type`, `missing`, and `audit.u.path...`, confirming our assumption about the unused fields being zeroed and safe here.
Now, let's switch gears to `net.c`. Here, the picture changes slightly. In `current_check_access_socket`, the code uses compound literals, like `&(struct landlock_request){ ... }`, to initialize `struct landlock_request`. In particular, `.trace_net` is assigned a pointer to a `struct landlock_net_trace` compound literal. These compound literals live on the stack and only exist for the scope of the function.
The issue that jumps out is in `landlock_trace_denial`. This function receives a `request` pointer. Inside, in the `LANDLOCK_REQUEST_NET_ACCESS` case, it checks `trace_net->address`, copying the contents of the address into a local `address` variable. It then uses this local `address` in the tracepoint.
The crucial question is: Is `trace_net->address` guaranteed to *point to valid memory*? Specifically, is it possible for `trace_net->address` to contain uninitialized data? My concern is that since the compound literal `trace_net` is created on the stack, and has a limited lifetime, can we be sure that the data it points to is still valid when it is read within the called function. That the pointer to a stack location is not an issue, but rather the data that's being pointed at.
Therefore, I need to investigate where `address` comes from within `current_check_access_socket`. Are there any scenarios where `address` might point to garbage, or to something that's been overwritten by the time it gets used in the tracepoint? This is the critical piece of the puzzle. I need to closely examine the code surrounding `current_check_access_socket` in `security/landlock/net.c` to trace the source and potential lifecycle of the `address` variable.
|
| 50/3 | 2026/09/20 23:56 | tool | read-file |
0mArgs:{
"File": "security/landlock/net.c",
"FirstLine": 70,
"LineCount": 60
}Results: {
"Contents": " 70:\t\treturn ret;\n 71:\t}\n 72:\t\n 73:\tstatic int current_check_access_socket(struct socket *const sock,\n 74:\t\t\t\t\t struct sockaddr *const address,\n 75:\t\t\t\t\t const int addrlen,\n 76:\t\t\t\t\t access_mask_t access_request,\n 77:\t\t\t\t\t bool connecting)\n 78:\t{\n 79:\t\tunsigned short sock_family;\n 80:\t\t__be16 port;\n 81:\t\tstruct layer_masks layer_masks = {};\n 82:\t\tstruct landlock_id id = {\n 83:\t\t\t.type = LANDLOCK_KEY_NET_PORT,\n 84:\t\t};\n 85:\t\tconst struct access_masks masks = {\n 86:\t\t\t.net = access_request,\n 87:\t\t};\n 88:\t\tconst struct landlock_cred_security *const subject =\n 89:\t\t\tlandlock_get_applicable_subject(current_cred(), masks, NULL);\n 90:\t\tstruct lsm_network_audit audit_net = {};\n 91:\t\n 92:\t\tif (!subject)\n 93:\t\t\treturn 0;\n 94:\t\n 95:\t\t/* Checks for minimal header length to safely read sa_family. */\n 96:\t\tif (addrlen \u003c (int)offsetofend(typeof(*address), sa_family))\n 97:\t\t\treturn -EINVAL;\n 98:\t\n 99:\t\t/*\n 100:\t\t * The socket is not locked, so sk_family can change concurrently due to\n 101:\t\t * e.g. setsockopt(IPV6_ADDRFORM).\n 102:\t\t */\n 103:\t\tsock_family = READ_ONCE(sock-\u003esk-\u003esk_family);\n 104:\t\n 105:\t\tswitch (address-\u003esa_family) {\n 106:\t\tcase AF_UNSPEC:\n 107:\t\t\tif (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||\n 108:\t\t\t (access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP \u0026\u0026\n 109:\t\t\t connecting)) {\n 110:\t\t\t\t/*\n 111:\t\t\t\t * Connecting to an address with AF_UNSPEC dissolves the\n 112:\t\t\t\t * remote association while retaining the socket object\n 113:\t\t\t\t * (i.e., the file descriptor). For TCP, it has the same\n 114:\t\t\t\t * effect as closing the connection. For UDP, it removes\n 115:\t\t\t\t * any preset remote address. As for dropping\n 116:\t\t\t\t * privileges, these actions are always allowed. Let\n 117:\t\t\t\t * the network stack handle potential inconsistencies\n 118:\t\t\t\t * and return -EINVAL if needed.\n 119:\t\t\t\t */\n 120:\t\t\t\treturn 0;\n 121:\t\t\t} else if (access_request ==\n 122:\t\t\t\t LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {\n 123:\t\t\t\tif (sock_family == AF_INET6) {\n 124:\t\t\t\t\t/*\n 125:\t\t\t\t\t * We cannot allow sending UDP datagrams to an\n 126:\t\t\t\t\t * explicit AF_UNSPEC address on IPv6 sockets,\n 127:\t\t\t\t\t * even if AF_UNSPEC is treated as \"no address\"\n 128:\t\t\t\t\t * on such sockets (so it should always be\n 129:\t\t\t\t\t * allowed). That's because the socket's family\n"
} |
| 51/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43475 output: 36 thoughts: 10 Thoughts: Okay, here we go... **Diving Back into `net.c`** Alright, back to `net.c`. I need to get back in the flow here. Let's see... what were we focusing on? Right, the network stack interactions, specifically around that recent performance bottleneck we identified. I remember, we were looking at the socket creation and connection handling. Time to refresh my memory and pinpoint the area that needs improvement. Let's see if I can find that specific code segment we flagged earlier. I know there's got to be a way to optimize this to squeeze out some extra cycles. |
| 52/3 | 2026/09/20 23:56 | tool | read-file |
0mArgs:{
"File": "security/landlock/net.c",
"FirstLine": 130,
"LineCount": 80
}Results: {
"Contents": " 130:\t\t\t\t\t * can change under our feet (if another thread\n 131:\t\t\t\t\t * calls setsockopt(IPV6_ADDRFORM)) to IPv4,\n 132:\t\t\t\t\t * which would then treat AF_UNSPEC as AF_INET.\n 133:\t\t\t\t\t */\n 134:\t\t\t\t\taudit_net.family = AF_UNSPEC;\n 135:\t\t\t\t\taudit_net.sk = sock-\u003esk;\n 136:\t\t\t\t\tlandlock_init_layer_masks(\n 137:\t\t\t\t\t\tsubject-\u003edomain, access_request,\n 138:\t\t\t\t\t\t\u0026layer_masks, LANDLOCK_KEY_NET_PORT);\n 139:\t\t\t\t\tlandlock_log_denial(\n 140:\t\t\t\t\t\tsubject,\n 141:\t\t\t\t\t\t\u0026(struct landlock_request){\n 142:\t\t\t\t\t\t\t.type = LANDLOCK_REQUEST_NET_ACCESS,\n 143:\t\t\t\t\t\t\t.audit.type =\n 144:\t\t\t\t\t\t\t\tLSM_AUDIT_DATA_NET,\n 145:\t\t\t\t\t\t\t.audit.u.net = \u0026audit_net,\n 146:\t\t\t\t\t\t\t.access = access_request,\n 147:\t\t\t\t\t\t\t.layer_masks = \u0026layer_masks,\n 148:\t#ifdef CONFIG_TRACEPOINTS\n 149:\t\t\t\t\t\t\t.trace_net =\n 150:\t\t\t\t\t\t\t\t\u0026(struct landlock_net_trace){\n 151:\t\t\t\t\t\t\t\t\t.address =\n 152:\t\t\t\t\t\t\t\t\t\taddress,\n 153:\t\t\t\t\t\t\t\t\t.addrlen =\n 154:\t\t\t\t\t\t\t\t\t\taddrlen,\n 155:\t\t\t\t\t\t\t\t\t.socket_family =\n 156:\t\t\t\t\t\t\t\t\t\tsock_family,\n 157:\t\t\t\t\t\t\t\t},\n 158:\t#endif /* CONFIG_TRACEPOINTS */\n 159:\t\t\t\t\t\t});\n 160:\t\t\t\t\treturn -EACCES;\n 161:\t\t\t\t}\n 162:\t\t\t} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||\n 163:\t\t\t\t access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {\n 164:\t\t\t\t/*\n 165:\t\t\t\t * Binding to an AF_UNSPEC address is treated\n 166:\t\t\t\t * differently by IPv4 and IPv6 sockets. The socket's\n 167:\t\t\t\t * family may change under our feet due to\n 168:\t\t\t\t * setsockopt(IPV6_ADDRFORM), but that's ok: we either\n 169:\t\t\t\t * reject entirely for IPv6 or require\n 170:\t\t\t\t * %LANDLOCK_ACCESS_NET_BIND_TCP or\n 171:\t\t\t\t * %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4, so it cannot\n 172:\t\t\t\t * be used to bypass the policy.\n 173:\t\t\t\t *\n 174:\t\t\t\t * IPv4 sockets map AF_UNSPEC to AF_INET for\n 175:\t\t\t\t * retrocompatibility for bind accesses, only if the\n 176:\t\t\t\t * address is INADDR_ANY (cf. __inet_bind). IPv6\n 177:\t\t\t\t * sockets always reject it.\n 178:\t\t\t\t *\n 179:\t\t\t\t * Checking the address is required to not wrongfully\n 180:\t\t\t\t * return -EACCES instead of -EAFNOSUPPORT or -EINVAL.\n 181:\t\t\t\t * We could return 0 and let the network stack handle\n 182:\t\t\t\t * these checks, but it is safer to return a proper\n 183:\t\t\t\t * error and test consistency thanks to kselftest.\n 184:\t\t\t\t */\n 185:\t\t\t\tif (sock_family == AF_INET) {\n 186:\t\t\t\t\tconst struct sockaddr_in *const sockaddr =\n 187:\t\t\t\t\t\t(struct sockaddr_in *)address;\n 188:\t\n 189:\t\t\t\t\tif (addrlen \u003c sizeof(struct sockaddr_in))\n 190:\t\t\t\t\t\treturn -EINVAL;\n 191:\t\n 192:\t\t\t\t\tif (sockaddr-\u003esin_addr.s_addr !=\n 193:\t\t\t\t\t htonl(INADDR_ANY))\n 194:\t\t\t\t\t\treturn -EAFNOSUPPORT;\n 195:\t\t\t\t} else {\n 196:\t\t\t\t\tif (addrlen \u003c SIN6_LEN_RFC2133)\n 197:\t\t\t\t\t\treturn -EINVAL;\n 198:\t\t\t\t\telse\n 199:\t\t\t\t\t\treturn -EAFNOSUPPORT;\n 200:\t\t\t\t}\n 201:\t\t\t} else {\n 202:\t\t\t\tWARN_ON_ONCE(1);\n 203:\t\t\t}\n 204:\t\t\t/*\n 205:\t\t\t * AF_UNSPEC is treated as AF_INET only in\n 206:\t\t\t * bind(AF_UNSPEC+INADDR_ANY) on IPv4 sockets and when sending\n 207:\t\t\t * to AF_UNSPEC addresses on IPv4 sockets.\n 208:\t\t\t */\n 209:\t\t\tfallthrough;\n"
} |
| 53/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44870 output: 36 thoughts: 16 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Analyzing the `current_check_access_socket` Function** Alright, so the prompt directs me to consider the rest of the `current_check_access_socket` function. My initial thought process is to understand *why* I'm being pointed to this specific function. Is it part of a larger debugging effort? Are we investigating a specific access control issue? Is it simply a code review requirement? The context is crucial. I need to quickly grasp its purpose within the broader system. I'll mentally reconstruct its typical call stack. What other functions or modules does it interact with? What are its input parameters, and what do they represent in terms of security context? User ID? Resource ID? Action permissions? I'm already formulating questions in my mind: Are there any implicit assumptions about the environment or underlying infrastructure? Then, I'll start mentally tracing the code flow. I’ll focus on potential vulnerabilities: Does it correctly handle race conditions? Does it validate inputs rigorously? Does it use secure coding practices like parameterized queries or prepared statements to prevent injection attacks? Are the authorization checks complete and consistent throughout the function? What data structures are involved and are there potential buffer overflows or memory leaks? I'll be specifically looking for areas where access control logic is delegated, especially to external systems or databases. How are these interactions secured and verified? What are the implications if those external components are compromised? I need to look for any potential for privilege escalation, bypasses, or unexpected behaviors in edge cases. I'm essentially looking for potential attack surfaces. Once I have a high-level understanding of the function, I'll then examine specific code snippets that are potentially problematic. I might look for obvious flaws like incorrect Boolean logic, missing error handling, or inadequate input validation. Ultimately, I will assess whether the `current_check_access_socket` function adheres to secure design principles and is resistant to common access control vulnerabilities. My primary goal here is to identify potential weaknesses, and propose potential solutions to mitigate any risks I uncover. |
| 54/3 | 2026/09/20 23:56 | tool | read-file |
0mArgs:{
"File": "security/landlock/net.c",
"FirstLine": 210,
"LineCount": 80
}Results: {
"Contents": " 210:\t\tcase AF_INET: {\n 211:\t\t\tconst struct sockaddr_in *addr4;\n 212:\t\n 213:\t\t\tif (addrlen \u003c sizeof(struct sockaddr_in))\n 214:\t\t\t\treturn -EINVAL;\n 215:\t\n 216:\t\t\taddr4 = (struct sockaddr_in *)address;\n 217:\t\t\tport = addr4-\u003esin_port;\n 218:\t\n 219:\t\t\tif (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||\n 220:\t\t\t access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {\n 221:\t\t\t\taudit_net.dport = port;\n 222:\t\t\t\taudit_net.v4info.daddr = addr4-\u003esin_addr.s_addr;\n 223:\t\t\t} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||\n 224:\t\t\t\t access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {\n 225:\t\t\t\taudit_net.sport = port;\n 226:\t\t\t\taudit_net.v4info.saddr = addr4-\u003esin_addr.s_addr;\n 227:\t\t\t} else {\n 228:\t\t\t\tWARN_ON_ONCE(1);\n 229:\t\t\t}\n 230:\t\t\tbreak;\n 231:\t\t}\n 232:\t\n 233:\t#if IS_ENABLED(CONFIG_IPV6)\n 234:\t\tcase AF_INET6: {\n 235:\t\t\tconst struct sockaddr_in6 *addr6;\n 236:\t\n 237:\t\t\tif (addrlen \u003c SIN6_LEN_RFC2133)\n 238:\t\t\t\treturn -EINVAL;\n 239:\t\n 240:\t\t\taddr6 = (struct sockaddr_in6 *)address;\n 241:\t\t\tport = addr6-\u003esin6_port;\n 242:\t\n 243:\t\t\tif (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||\n 244:\t\t\t access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {\n 245:\t\t\t\taudit_net.dport = port;\n 246:\t\t\t\taudit_net.v6info.daddr = addr6-\u003esin6_addr;\n 247:\t\t\t} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||\n 248:\t\t\t\t access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {\n 249:\t\t\t\taudit_net.sport = port;\n 250:\t\t\t\taudit_net.v6info.saddr = addr6-\u003esin6_addr;\n 251:\t\t\t} else {\n 252:\t\t\t\tWARN_ON_ONCE(1);\n 253:\t\t\t}\n 254:\t\t\tbreak;\n 255:\t\t}\n 256:\t#endif /* IS_ENABLED(CONFIG_IPV6) */\n 257:\t\n 258:\t\tdefault:\n 259:\t\t\treturn 0;\n 260:\t\t}\n 261:\t\n 262:\t\t/*\n 263:\t\t * Checks sa_family consistency to not wrongfully return\n 264:\t\t * -EACCES instead of -EINVAL. Valid sa_family changes are\n 265:\t\t * only (from AF_INET or AF_INET6) to AF_UNSPEC.\n 266:\t\t *\n 267:\t\t * We could return 0 and let the network stack handle this\n 268:\t\t * check, but it is safer to return a proper error and test\n 269:\t\t * consistency thanks to kselftest.\n 270:\t\t */\n 271:\t\tif (address-\u003esa_family != sock_family \u0026\u0026\n 272:\t\t address-\u003esa_family != AF_UNSPEC)\n 273:\t\t\treturn -EINVAL;\n 274:\t\n 275:\t\tid.key.data = (__force uintptr_t)port;\n 276:\t\tBUILD_BUG_ON(sizeof(port) \u003e sizeof(id.key.data));\n 277:\t\n 278:\t\taccess_request = landlock_init_layer_masks(subject-\u003edomain,\n 279:\t\t\t\t\t\t\t access_request, \u0026layer_masks,\n 280:\t\t\t\t\t\t\t LANDLOCK_KEY_NET_PORT);\n 281:\t\tif (!access_request)\n 282:\t\t\treturn 0;\n 283:\t\n 284:\t\tif (unmask_layers_net(subject-\u003edomain, id, \u0026layer_masks,\n 285:\t\t\t\t access_request))\n 286:\t\t\treturn 0;\n 287:\t\n 288:\t\taudit_net.family = address-\u003esa_family;\n 289:\t\taudit_net.sk = sock-\u003esk;\n"
} |
| 55/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46620 output: 27 thoughts: 3316 Thoughts: **Landlock Tracepoint Analysis: Addressing Potential Memory Corruption**
Alright, let's break this down systematically. I'm focusing on potential vulnerabilities related to tracepoints within the Landlock security module. The key area of concern is data passed into and used within the `landlock_trace_denial` function, particularly when dealing with network-related denials and union members.
I started by looking at `net.c`, specifically at the call to `landlock_log_denial` at line 149. The context here involves network access restrictions (`LANDLOCK_REQUEST_NET_ACCESS`). I'm zeroing in on the `struct landlock_request` and its members, specifically the `trace_net` member, since it's used with `LANDLOCK_REQUEST_NET_ACCESS`.
I observed a key condition: `access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP`, `sock_family == AF_INET6`, and `address->sa_family == AF_UNSPEC`. This seems like an interesting combination. The crucial point here is the `addrlen` value passed. Its value is checked in line 96 in `net.c`: `if (addrlen < (int)offsetofend(typeof(*address), sa_family)) return -EINVAL;`. This check allows `addrlen` to be as small as 2 bytes!
The follow-up function is `landlock_trace_denial`, specifically the code path handling `LANDLOCK_REQUEST_NET_ACCESS`. Here, a `struct sockaddr_storage address = {};` is zero-initialized, and then `memcpy(&address, trace_net->address, trace_net->addrlen);` is used to copy data from userspace. The subsequent call to `trace_landlock_deny_access_net` is the critical point.
Inside `trace_landlock_deny_access_net`, there's a check: `addrlen >= (int)offsetofend(struct sockaddr_in, sin_port)`. If `addrlen` is *smaller* than the offset of `sin_port`, the `has_port` flag is false, and the code *avoids* reading `sin_port`. This is a crucial safety check. Since the `memcpy` only copies `addrlen` bytes from the user buffer (where `addrlen` bytes are valid according to the code that calls `landlock_log_denial`) to the zeroed `address` buffer, it mitigates potential read-out-of-bounds problems if `addrlen` is, say, 2 bytes. That's a good thing!
I also verified that the bytes up to `offsetofend(struct sockaddr_in, sin_port)` were copied and initialized. In fact, `address` in `landlock_trace_denial` was zero-initialized.
Next, I shifted focus to `task.c`, examining different `hook_*` functions and the associated tracepoint structures. I looked at several locations (`hook_ptrace_access_check`, `hook_ptrace_traceme`, `hook_unix_stream_connect`, `hook_unix_may_send`, `hook_task_kill`, and `hook_file_send_sigiotask`). All the members of these structs are properly initialized before being passed into the tracepoints.
Finally, I got back to the `struct landlock_request` and the union containing tracepoint data. The main point is that *every* case in `landlock_trace_denial` accesses *precisely* the correct member of the union based on the `request->type`. There is no overlap, which prevents reading from the wrong union member. The union is used with `LANDLOCK_REQUEST_FS_ACCESS`, `LANDLOCK_REQUEST_NET_ACCESS`, `LANDLOCK_REQUEST_PTRACE`, `LANDLOCK_REQUEST_SCOPE_SIGNAL`, and `LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET`.
In this last case, `other_domain_id` is set when `LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET` is used, so I checked it to find where `get_socket_domain_id` is used. This ID is fully initialized.
Based on this analysis, the code seems well-designed to avoid memory corruption, particularly by the check if `addrlen` is smaller than the offset of the `sin_port` struct element, and by union usage, but this deserves a deeper dive.
|
| 56/3 | 2026/09/20 23:56 | tool | grepper |
0mArgs:{
"Expression": "LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET"
}Results: {
"Output": "security/landlock/audit.c=46=get_blocker(const enum landlock_request_type type,\n--\nsecurity/landlock/audit.c-67-\nsecurity/landlock/audit.c:68:\tcase LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:\nsecurity/landlock/audit.c-69-\t\tWARN_ON_ONCE(access_bit != -1);\n--\nsecurity/landlock/audit.c=89=blocker_prefix(const enum landlock_request_type type)\n--\nsecurity/landlock/audit.c-101-\nsecurity/landlock/audit.c:102:\tcase LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:\nsecurity/landlock/audit.c-103-\tcase LANDLOCK_REQUEST_SCOPE_SIGNAL:\n--\nsecurity/landlock/log.c=432=is_denial_quieted(const struct landlock_request *const request,\n--\nsecurity/landlock/log.c-451-\t\t\t LANDLOCK_SCOPE_SIGNAL);\nsecurity/landlock/log.c:452:\tcase LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:\nsecurity/landlock/log.c-453-\t\treturn !!(youngest_denied-\u003equiet_masks.scope \u0026\n--\nsecurity/landlock/log.h=21=enum landlock_request_type {\n--\nsecurity/landlock/log.h-25-\tLANDLOCK_REQUEST_NET_ACCESS,\nsecurity/landlock/log.h:26:\tLANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\nsecurity/landlock/log.h-27-\tLANDLOCK_REQUEST_SCOPE_SIGNAL,\n--\nsecurity/landlock/task.c=312=static int hook_unix_stream_connect(struct sock *const sock,\n--\nsecurity/landlock/task.c-331-\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c:332:\t\t.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\nsecurity/landlock/task.c-333-\t\t.audit = {\n--\nsecurity/landlock/task.c=347=static int hook_unix_may_send(struct socket *const sock,\n--\nsecurity/landlock/task.c-371-\tlandlock_log_denial(subject, \u0026(struct landlock_request) {\nsecurity/landlock/task.c:372:\t\t.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,\nsecurity/landlock/task.c-373-\t\t.audit = {\n--\nsecurity/landlock/trace.c=78=void landlock_trace_denial(\n--\nsecurity/landlock/trace.c-216-\t\tbreak;\nsecurity/landlock/trace.c:217:\tcase LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:\nsecurity/landlock/trace.c-218-\t\tif (trace_landlock_deny_scope_abstract_unix_socket_enabled())\n"
} |
| 57/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51704 output: 38 thoughts: 3959 Thoughts: **Landlock Tracepoint Analysis** Okay, here's my thought process on dissecting these Landlock tracepoints. It started with an observation about `LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET`'s usage and led me to dig into the tracepoint macro definitions in `include/trace/events/landlock.h`. I needed to ensure that all arguments were valid and consistently handled. First, I examined `landlock_deny_access_fs`. The `blockers` struct caught my eye. I confirmed that it's initialized with a designated initializer, ensuring no uninitialized fields, and that `TP_fast_assign` correctly assigns all the event fields. Next, I moved to `landlock_deny_access_net`, focusing on potential issues with `sockaddr` and port extraction. I had to double check the IPv6 `sin6_port` and IPv4 `sin_port` offset assumptions, so I verified the `static_assert` statements. I then investigated whether the `address` struct passed to the tracepoint might be partially initialized or read out of bounds. The `memcpy` from user-space data was the key point, and I walked through the code in `net/socket.c` to see how that `address` was populated in `hook_socket_bind` and what the minimum requirements are. I'm looking for where a Landlock denial might happen *before* an `EINVAL` is returned due to a bad socket address being passed in. My investigation of the tracepoint was interrupted because I realized that if `address->sa_family == AF_UNSPEC` and a `socket_family == AF_INET6` is passed, the port is not set. I realized an uninitialized port could occur in that case and it would lead to a `-1` value being passed in. I then had to trace through the `net.c` code to see if there was a case in which `addrlen` could be less than the size of the socket address structure. I had to determine if the `socket_family == AF_INET` and `addr->sa_family == AF_UNSPEC` at any time. I moved on to `landlock_deny_ptrace`, where I double-checked the validity of the `tracer` field. It's not stored in the event itself but is a direct BTF argument. I had to be sure of the source of that value. I confirmed that `tracer` is always a valid task struct. Finally, I checked `landlock_deny_scope_signal`. I inspected `signal` and its origins, and confirmed its integrity as an `int`. I was interrupted again to check a change in `landlock_check_rule_inode` and `landlock_check_rule_net_port`. The `access_mask_t access_request` changed to `u64 access_request`. This required me to jump to `__trace_landlock_fill_layers` and check the use of the `access_mask_t` type. |
| 58/3 | 2026/09/20 23:56 | tool | read-file |
0mArgs:{
"File": "include/trace/events/landlock.h",
"FirstLine": 115,
"LineCount": 40
}Results: {
"Contents": " 115:\t\treturn ret;\n 116:\t}\n 117:\t\n 118:\t/*\n 119:\t * Fills the dense per-domain-layer array layers (one access mask per layer,\n 120:\t * indexed by level - 1) from rule's sparse layer stack, keeping only the\n 121:\t * requested rights (access_request). Layers with no matching rule entry get\n 122:\t * a zero mask. Shared by the check_rule_inode and check_rule_net_port events.\n 123:\t *\n 124:\t * rule-\u003elayers is sorted by ascending level, with levels in the domain's\n 125:\t * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps\n 126:\t * to a slot. A leftover entry would be a malformed rule; the zero-filled\n 127:\t * slots keep the output and the array bounds safe regardless.\n 128:\t */\n 129:\tstatic inline void\n 130:\t__trace_landlock_fill_layers(access_mask_t *const layers,\n 131:\t\t\t\t const size_t num_layers,\n 132:\t\t\t\t const struct landlock_rule *const rule,\n 133:\t\t\t\t const access_mask_t access_request)\n 134:\t{\n 135:\t\tsize_t i = 0;\n 136:\t\n 137:\t\tfor (size_t level = 1; level \u003c= num_layers; level++) {\n 138:\t\t\taccess_mask_t grants = 0;\n 139:\t\n 140:\t\t\tif (i \u003c rule-\u003enum_layers \u0026\u0026 level == rule-\u003elayers[i].level) {\n 141:\t\t\t\tgrants = rule-\u003elayers[i].access \u0026 access_request;\n 142:\t\t\t\ti++;\n 143:\t\t\t}\n 144:\t\t\tlayers[level - 1] = grants;\n 145:\t\t}\n 146:\t\n 147:\t\t/* A leftover entry means an out-of-range or unsorted rule level. */\n 148:\t\tWARN_ON_ONCE(i \u003c rule-\u003enum_layers);\n 149:\t}\n 150:\t\n 151:\t/*\n 152:\t * Renders the dense per-domain-layer access array as symbolic flag names for\n 153:\t * the grants field: layers wrapped in \"{}\", flags within a layer joined by\n 154:\t * \"|\", layers separated by \",\", an empty layer rendered as nothing.\n"
} |
| 59/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 57026 output: 22 thoughts: 414 Thoughts: **Landlock Rule Appending and Tracing: A Quick Scan** Okay, let's see. My mental model starts with `__trace_landlock_fill_layers`. I'm iterating through each level, starting at `level = 1` up to `num_layers`, and I'm confident that everything is properly initialized. No memory errors there to trip us up. Now, let's dig into `landlock_append_fs_rule` in `security/landlock/fs.c`. I notice a condition that's changed: `trace_landlock_add_rule_fs_enabled()` is now `trace_landlock_add_rule_path_beneath_enabled()`. That seems like a straightforward rename, likely an improvement in clarity, but the important thing is that a call to a trace function is still happening, and that it relies on a `__getname()` allocation and `resolve_path_for_trace()` call. The trace now receives `flags` and the updated `access_rights` variables. The existing `path` and `pathname` are passed in, as well. Moving on to `landlock_append_net_rule` in `security/landlock/net.c`, I see a similar pattern: the trace function call has been updated from `trace_landlock_add_rule_net()` to `trace_landlock_add_rule_net_port()`. Again, no major functional change immediately apparent, but the flags parameter has been included. It takes `access_rights` and `port` as well. My immediate question now is: Where are all of these parameters - `flags`, `access_rights`, `path`, `pathname`, and `port` - coming from? I need to understand how they are set and what they represent to properly evaluate the context of these traces. The function definitions of `landlock_append_fs_rule` and `landlock_append_net_rule` themselves are the next thing I'll check. |
| 60/3 | 2026/09/20 23:56 | tool | grepper |
0mArgs:{
"Expression": "landlock_append_fs_rule"
}Results: {
"Output": "security/landlock/fs.c=259=static struct landlock_object *get_inode_object(struct inode *const inode)\n--\nsecurity/landlock/fs.c-328- */\nsecurity/landlock/fs.c:329:int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,\nsecurity/landlock/fs.c-330-\t\t\t const struct path *const path,\n--\nsecurity/landlock/fs.h=153=__init void landlock_add_fs_hooks(void);\nsecurity/landlock/fs.h-154-\nsecurity/landlock/fs.h:155:int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,\nsecurity/landlock/fs.h-156-\t\t\t const struct path *const path,\n--\nsecurity/landlock/syscalls.c=357=static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,\n--\nsecurity/landlock/syscalls.c-393-\t/* Imports the new rule. */\nsecurity/landlock/syscalls.c:394:\terr = landlock_append_fs_rule(ruleset, \u0026path,\nsecurity/landlock/syscalls.c-395-\t\t\t\t path_beneath_attr.allowed_access, flags);\n"
} |
| 61/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 58241 output: 36 thoughts: 25 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into Landlock's File System Rule Appending** Alright, time to dig into the `landlock_append_fs_rule` function. I need to refresh my memory on how Landlock handles appending file system rules, specifically within `security/landlock/fs.c`. I want to understand how it's implemented and how it interacts with the rest of the Landlock framework. Hopefully, this will shed some light on the potential issue I'm trying to diagnose. I'll focus on the data structures, any locking involved, and the key logic flow within that function. Time to get my hands dirty and examine the code. |
| 62/3 | 2026/09/20 23:56 | tool | read-file |
0mArgs:{
"File": "security/landlock/fs.c",
"FirstLine": 328,
"LineCount": 40
}Results: {
"Contents": " 328:\t */\n 329:\tint landlock_append_fs_rule(struct landlock_ruleset *const ruleset,\n 330:\t\t\t\t const struct path *const path,\n 331:\t\t\t\t access_mask_t access_rights, const u32 flags)\n 332:\t{\n 333:\t\tint err;\n 334:\t\tstruct landlock_id id = {\n 335:\t\t\t.type = LANDLOCK_KEY_INODE,\n 336:\t\t};\n 337:\t\n 338:\t\t/* Files only get access rights that make sense. */\n 339:\t\tif (!d_is_dir(path-\u003edentry) \u0026\u0026\n 340:\t\t !access_mask_subset(access_rights, ACCESS_FILE))\n 341:\t\t\treturn -EINVAL;\n 342:\t\n 343:\t\t/* Transforms relative access rights to absolute ones. */\n 344:\t\taccess_rights |= LANDLOCK_MASK_ACCESS_FS \u0026\n 345:\t\t\t\t ~(ruleset-\u003ehandled_masks.fs |\n 346:\t\t\t\t _LANDLOCK_ACCESS_FS_INITIALLY_DENIED);\n 347:\t\tid.key.object = get_inode_object(d_backing_inode(path-\u003edentry));\n 348:\t\tif (IS_ERR(id.key.object))\n 349:\t\t\treturn PTR_ERR(id.key.object);\n 350:\t\tmutex_lock(\u0026ruleset-\u003elock);\n 351:\t\terr = landlock_insert_rule(ruleset, id, access_rights, flags);\n 352:\t\n 353:\t\t/*\n 354:\t\t * Emit after the rule insertion succeeds, so every event corresponds to\n 355:\t\t * a rule that is actually in the ruleset. The ruleset lock is still\n 356:\t\t * held for BTF consistency (enforced by lockdep_assert_held in\n 357:\t\t * TP_fast_assign).\n 358:\t\t */\n 359:\t\tif (!err \u0026\u0026 trace_landlock_add_rule_path_beneath_enabled()) {\n 360:\t\t\tchar *buffer __free(__putname) = __getname();\n 361:\t\t\tconst char *pathname =\n 362:\t\t\t\tbuffer ? resolve_path_for_trace(path, buffer) :\n 363:\t\t\t\t\t \"\u003cno_mem\u003e\";\n 364:\t\n 365:\t\t\ttrace_landlock_add_rule_path_beneath(\n 366:\t\t\t\truleset, flags, access_rights, path, pathname);\n 367:\t\t}\n"
} |
| 63/3 | 2026/09/20 23:56 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59134 output: 451 thoughts: 1742 Thoughts: **Landlock Tracepoint Analysis: KMSAN Justification**
Okay, so I'm looking at these Landlock tracepoint changes. Seems like the `flags` parameter was already being passed in `landlock_append_fs_rule`, but it wasn't being included in the tracepoint itself. Now it is, and the same applies to `landlock_add_rule_net_port`. The code has been updated in `include/trace/events/landlock.h` to reflect this. The code now includes the `flags` parameter in the tracepoints.
My first thought is to determine if any kernel structures are now being sent to user space. Tracepoints write to the ftrace buffer, consumed via tracefs or accessed via BPF. There's nothing being copied directly to user space. No syscalls such as `copy_to_user`, `put_user`, netlink, ioctl or sockopt have been introduced or modified. So, that's good, no direct leaks.
I'm looking closely at initialization. In `landlock_trace_denial`, the `blockers` struct uses designated initializers (`const struct landlock_blockers blockers = { .access = missing, .type = request->type, };`) ensuring full initialization. The `address` in NET_ACCESS is initialized with `struct sockaddr_storage address = {};` and then `memcpy` is used. The `trace_landlock_deny_access_net(...)` function checks `has_port` before reading the port and `has_port` requires `addrlen` to be correct.
In `task.c`, all compound literals are fully designated initialized: `&(struct landlock_ptrace_trace) { .tracee_domain_id = ..., .tracer = ... }` and `&(struct landlock_signal_trace) { .target_domain_id = ..., .signal = ... }`. `other_domain_id` is assigned from `get_socket_domain_id(...)`. Then, In `net.c`, `&(struct landlock_net_trace) { .address = address, .addrlen = addrlen, .socket_family = sock_family }` initialization happens.
The new structs, `landlock_blockers`, `landlock_net_trace`, `landlock_ptrace_trace`, `landlock_signal_trace` in `log.h` are all simple POD structs. The `union` inside `struct landlock_request` groups these. These trace structs are mutually exclusive for each denial request type (NET, PTRACE, SIGNAL, ABSTRACT_UNIX_SOCKET). The designated initializers zero out all unmentioned union/struct fields when initializing `struct landlock_request`. In `landlock_trace_denial`, each request type accesses only its corresponding union member, and there's a check for non-null pointers with `WARN_ON_ONCE`.
Okay, let's just make sure there were no changes to `tools/testing/selftests/landlock/`. These are just user-space selftests, verifying tracefs output strings. No user-space code changed in this context.
Now, let's re-verify the criteria for needing KMSAN. KMSAN detects uninitialized memory reads, which is the main concern here.
1. **Kernel structures to userspace?** No, no direct copy to user. Tracepoints write to the ftrace buffer, which is not a direct copy. The records contain only the scalar fields that are populated in `TP_fast_assign`.
2. **Conditional logic on uninitialized variables?** No, all variables and fields are initialized.
3. **Complex data structures?** All structs and compound literals are explicitly initialized.
4. **Bounds checks exposing uninitialized memory?** No. Tightened `addrlen` logic prevents any out-of-bounds reads.
Therefore, since none of these criteria are met, there is no need for KMSAN. The standard KASAN and other sanitizers should be sufficient. I'm confident there are no uninitialized memory leaks.
|
| 64/3 | 2026/09/20 23:56 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series updates Landlock trace events and internal logging context:\n1. Renames trace events to match updated terminology (e.g. landlock_check_rule_fs -\u003e landlock_check_rule_inode, landlock_add_rule_fs -\u003e landlock_add_rule_path_beneath, etc.).\n2. Adds flags, socket_family, and tracer/signal details to tracepoint prototypes for BTF/raw tracepoint consumers.\n3. Introduces helper structs (landlock_blockers, landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace) and places trace-only pointers in a union within struct landlock_request.\n4. Refactors network denial tracepoint logic to extract the port from checked sockaddr data.\n\nMemory initialization and safety review:\n- All stack-allocated structures and compound literals (such as struct landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace, and landlock_blockers) are fully initialized using designated initializers.\n- The union in struct landlock_request is partitioned cleanly by request-\u003etype; each switch branch in landlock_trace_denial only accesses the union member corresponding to the request type and guards against NULL with WARN_ON_ONCE.\n- The address buffer in landlock_trace_denial() is explicitly zero-initialized (`struct sockaddr_storage address = {};`) before copying addrlen bytes, and `has_port` verifies that addrlen is at least `offsetofend(struct sockaddr_in, sin_port)` before inspecting the port.\n- No kernel memory or padding is exposed or copied to userspace via copy_to_user(), netlink, ioctl, or socket options. Tracepoint ring buffer records only store scalar values.\n\nSince the patch does not read or leak uninitialized memory and introduces no uninitialized memory risks, a dedicated KMSAN fuzzing session is not required; standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series updates Landlock trace events and internal logging context:\n1. Renames trace events to match updated terminology (e.g. landlock_check_rule_fs -\u003e landlock_check_rule_inode, landlock_add_rule_fs -\u003e landlock_add_rule_path_beneath, etc.).\n2. Adds flags, socket_family, and tracer/signal details to tracepoint prototypes for BTF/raw tracepoint consumers.\n3. Introduces helper structs (landlock_blockers, landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace) and places trace-only pointers in a union within struct landlock_request.\n4. Refactors network denial tracepoint logic to extract the port from checked sockaddr data.\n\nMemory initialization and safety review:\n- All stack-allocated structures and compound literals (such as struct landlock_net_trace, landlock_ptrace_trace, landlock_signal_trace, and landlock_blockers) are fully initialized using designated initializers.\n- The union in struct landlock_request is partitioned cleanly by request-\u003etype; each switch branch in landlock_trace_denial only accesses the union member corresponding to the request type and guards against NULL with WARN_ON_ONCE.\n- The address buffer in landlock_trace_denial() is explicitly zero-initialized (`struct sockaddr_storage address = {};`) before copying addrlen bytes, and `has_port` verifies that addrlen is at least `offsetofend(struct sockaddr_in, sin_port)` before inspecting the port.\n- No kernel memory or padding is exposed or copied to userspace via copy_to_user(), netlink, ioctl, or socket options. Tracepoint ring buffer records only store scalar values.\n\nSince the patch does not read or leak uninitialized memory and introduces no uninitialized memory risks, a dedicated KMSAN fuzzing session is not required; standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|