summaryrefslogtreecommitdiff
path: root/security/landlock/log.c
AgeCommit message (Collapse)Author
3 dayslandlock: Enforce capability restrictionsMickaël Salaün
Add Landlock enforcement for capability use via the LSM capable hook. This lets a sandboxed process restrict which Linux capabilities it can exercise, using LANDLOCK_PERMISSION_CAPABILITY_USE and per-capability rules. The check is a flat per-layer allowed-capabilities test, with no domain-ancestry bypass, no cross-namespace discriminant, and no dependency on the target user namespace. These categorical denials return -EPERM, like the namespace permission introduced by the previous commit, and they mirror its per-capability allowed and quiet masks, so LANDLOCK_ADD_RULE_QUIET stays rejected for this rule type. Successful capability rules and denials are traced as the previous commit describes. Enforce only at capability exercise time rather than modifying the credential's capability sets. Modifying them would give capget(2) an accurate view of usable capabilities, but no LSM other than commoncap does so; Landlock follows that convention. A sandboxed process inside a user namespace therefore sees all capabilities via capget(2) but receives -EPERM when attempting to use one that is denied. An earlier design bypassed this hook for namespace management through a domain-ancestry comparison with the namespace creator's stored Landlock domain. That made a Landlock decision depend on kernel namespace state, and the ns != cred->user_ns heuristic did not accurately identify namespace-management operations. The flat, namespace-agnostic check instead enforces capability and namespace restrictions independently; creating a non-user namespace requires an allowed CAP_SYS_ADMIN even when combined with CLONE_NEWUSER in one unshare(). Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Paul Moore <paul@paul-moore.com> Cc: Serge E. Hallyn <serge@hallyn.com> Link: https://patch.msgid.link/20261014082718.1435842-5-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
3 dayslandlock: Enforce namespace use restrictionsMickaël Salaün
Add Landlock enforcement for namespace use through the LSM namespace_init and namespace_install hooks. This lets a sandboxed process restrict which namespace types it can create, join, or acquire a file descriptor for, using LANDLOCK_PERMISSION_NAMESPACE_USE and per-type rules. Introduce the handled_permissions field in struct landlock_ruleset_attr: each permission gates every use of a kernel-defined category (CLONE_NEW* namespace types, CAP_* capabilities) with complete deny-by-default coverage, so unknown member values need no validation, being denied until a rule allows them. This UAPI extension advances the Landlock ABI from 11 to 12. There is no domain-ancestry bypass and no namespace-creator tracking, only a flat per-layer allowed-types bitmask: hook_namespace_init() covers creation through clone(2), unshare(2), open_tree(2) and fsmount(2), and hook_namespace_install() covers setns(2). Each permission hook maps its member mask explicitly in the shared permission walker, which warns and denies if a future caller omits that mapping. These categorical denials return -EPERM, matching Landlock's scope and mount-topology denials rather than its object-access -EACCES convention. struct permission_masks is forced to eight bytes with __packed and __aligned because it must eventually hold both the eight namespace types and all capabilities, and because m68k GCC otherwise packs its u64 bitfields at byte granularity, making the structure smaller than sizeof(u64). Carrying it grows struct layer_config from 4 to 16 bytes, so the largest 16-entry domain FAM grows from 64 to 256 bytes. The permissions selector keeps the rule attribute self-describing: - User space can mask it against handled_permissions, as it masks allowed_access against handled_access_fs, so a program built against newer headers still adds the rules an older kernel supports. - It tells apart two attributes that share the same three-u64 layout. Unknown member bits are ignored, so without it a mismatched rule type would apply a capability mask as namespace types. - It leaves room for a rule to carry several permissions, or for another rule type to key the same permission differently. The rule also carries a quiet_namespace_types bitmask that suppresses audit submission for denied members without granting them. A sandbox knowingly running a caller that probes a type it will never be granted would otherwise flood the audit log and drown the surprising denials that matter. Quiet is per-member rather than a coarse per-category ruleset bit, so a sandbox can silence CLONE_NEWNET while still auditing CLONE_NEWUTS; making it the complement of the allowed set would be broad, could not audit a member that is neither allowed nor explicitly quieted, and would auto-hide members added by future kernels. It is a per-rule bitmask rather than the LANDLOCK_ADD_RULE_QUIET flag and the ruleset quiet_access_* masks, which suit the unbounded rb-tree objects of filesystem and network rules, so that flag is rejected here. Only the youngest denying layer's quiet mask decides, so a parent cannot silence a denial made by a deeper layer. Trace the handled permission mask, every successful namespace rule, and namespace denials. Successful effective no-ops advance the version, and quiet denials remain trace-visible with logged=0. The denial event takes the blockers argument of the filesystem and network events and records the same blockers_type and blockers_access fields, so one filter expression spans the mask-bearing denial events. Handled and rule permission masks may contain permissions from multiple domains in one field, so their names are domain-qualified, such as namespace.use. A blocker is already interpreted in the domain supplied by the audit prefix or denial event, so it retains the bare action name use, matching existing access blockers such as read_file. User namespace creation does not require capabilities, so Landlock can restrict it directly. Non-user namespace types require CAP_SYS_ADMIN before the Landlock check is reached; when the capability permission added by the next commit is also handled, both must allow the operation. Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Paul Moore <paul@paul-moore.com> Cc: Serge E. Hallyn <serge@hallyn.com> Link: https://patch.msgid.link/20261014082718.1435842-4-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
3 dayslandlock: Rename quiet_masks to quiet_accessMickaël Salaün
The base stores the fs/net/scope quiet bitmasks in a struct access_masks field named quiet_masks. A following commit adds the sibling struct permission_masks field quiet_permission for capabilities and namespaces. quiet_masks drops the access category and keeps the type half ("masks"), while quiet_permission keeps the category and drops the type half, so the two read as an inconsistent pair. Rename the base field to quiet_access, giving the symmetric, category-named pair quiet_access/quiet_permission that also matches the UAPI handled_access_*/quiet_access_* naming. This is a no-functional-change rename. Cc: Günther Noack <gnoack@google.com> Cc: Tingmao Wang <m@maowtm.org> Reviewed-by: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20261014082718.1435842-2-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
2026-08-17landlock: Add landlock_deny_access_fs and landlock_deny_access_netMickaël Salaün
Add per-type tracepoints emitted from landlock_log_denial() when an access is denied: landlock_deny_access_fs for filesystem denials and landlock_deny_access_net for network denials. They use the "deny_" prefix (rather than "check_") to mark that they fire only on a denial, and they complement the check_rule events by making the denial-by-absence case explicit (when no rule matches, no check_rule event fires). Unlike the audit records, these events fire regardless of the audit configuration and the domain's log flags: the user's "disable logging" intent applies to audit records, not to kernel tracing. The logged field records whether the domain's log policy would submit the denial to audit; it is the decision computed once by landlock_log_denial() and passed to both the audit and the tracing emitter, so a stateless ftrace filter can select the audit-visible denials with logged==1. TP_PROTO passes the denying hierarchy node, not the task's current domain, so domain_id reports the specific node that blocked the access, matching audit record semantics. (check_rule instead passes the current domain, which it needs to size its per-layer array.) same_exec is also passed explicitly because it is computed from the credential bitmask and is not derivable from the hierarchy pointer alone. The denial field is named blockers to match the audit record field. The filesystem path comes from the request's audit data. Its type selects which union member holds the object, exactly as dump_common_audit_data() selects it (a path, a file's path, an ioctl op's path, or a bare dentry); reading the wrong member would dereference garbage, so every reachable type has an explicit case and an unexpected one is flagged with WARN_ONCE() instead of misread. Path-backed types resolve via d_absolute_path() (as landlock_add_rule_fs does) and the bare-dentry case via dentry_path_raw(). The inode number is read defensively. A filesystem denial can carry a negative dentry (no backing inode), for example a denied creation, so the event mirrors the guard in dump_common_audit_data() and reports inode 0 rather than dereferencing a NULL inode. The sibling fs tracepoints do not need the guard: a dentry that matches a rule during an access check, or one opened to add a rule, always has a backing inode. Landlock tracepoints are reachable by unprivileged sandboxees, so a denial on a negative dentry with the event enabled must not fault the kernel. Cc: Günther Noack <gnoack@google.com> Cc: Justin Suess <utilityemal77@gmail.com> Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-13-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
2026-08-17landlock: Add create_domain and free_domain tracepointsMickaël Salaün
Add a landlock_create_domain tracepoint emitted from landlock_restrict_self() after the new domain is created, so a consumer can correlate the source ruleset with the resulting domain. The flags-only path (ruleset_fd == -1) creates no domain and emits no event. Move the ruleset lock acquisition from landlock_merge_ruleset() to the caller so the lock is held across both the merge and the tracepoint emission, giving an eBPF program a consistent ruleset snapshot. Release it before the thread-sync: holding ruleset->lock across landlock_restrict_sibling_threads() would deadlock a sibling blocked on the same lock. The event therefore fires before the (rare) thread-sync failure path; when that path aborts the just-created domain, the matching free_domain event fires so the create/free pair stays balanced. Add a landlock_free_domain tracepoint that fires when a domain's hierarchy node is freed. The hierarchy node is the lifecycle boundary because it represents the domain's identity and outlives the domain's access masks, which may still be active in descendant domains. A domain freed without ever being committed to a credential was never visible to user space, so free_domain is suppressed for it. This is tracked by a new landlock_log_status value, LANDLOCK_LOG_UNCOMMITTED, which is also the zero value so a hierarchy whose initialization failed defaults to not observable. A hierarchy is born UNCOMMITTED and is promoted to LANDLOCK_LOG_PENDING (or LANDLOCK_LOG_DISABLED when logging is off) right after its create_domain event fires; a thread-sync failure does not reset it, so an aborted domain that already emitted create_domain still emits the matching free_domain. Promoting right after the event, rather than at commit_creds() time, avoids a race: on a successful thread-sync the sibling threads commit the new domain in lockstep before landlock_restrict_self() returns, so the shared domain may already have moved to LANDLOCK_LOG_RECORDED through a plain store, and a late promotion would race that store and could unbalance the domain allocation and deallocation audit records. Cc: Günther Noack <gnoack@google.com> Cc: Justin Suess <utilityemal77@gmail.com> Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-10-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
2026-08-17landlock: Decouple the per-denial logging decision from CONFIG_AUDITMickaël Salaün
Until now, whether a denial is logged was decided inside landlock_audit_denial(): a per-execution flag check (log_same_exec or log_new_exec, selected by the credential's domain_exec bitmask), preceded by a LANDLOCK_LOG_DISABLED early return in landlock_log_denial() for domains an ancestor fully quieted. Factor that decision into a single is_denial_logged() helper called once by landlock_log_denial(), and pass its result to landlock_audit_denial() as a "logged" boolean. A following commit passes the same boolean to the deny tracepoints, so audit and tracing share one decision that stays correct as new log state is added, and a tracepoints-only build (CONFIG_AUDIT=n) computes it identically. Computing the logged verdict once in the shared helper makes audit and tracing apply identical filtering, so they cannot report different logged= values for the same denial as log controls grow. Move the LANDLOCK_LOG_DISABLED gate out of landlock_log_denial() into the decision so num_denials counts every denial, including those a domain quiets. This was previously masked: the only reader of num_denials is the audit "domain deallocated" record, emitted only for domains that reached LANDLOCK_LOG_RECORDED; a fully quieted domain never records, so its undercount was never observable. A following commit adds a free_domain tracepoint that reports num_denials, which needs the full count. This is not a functional change for audit: the logged decision and the audit_enabled gate are preserved, so the emitted records are identical. Cc: Günther Noack <gnoack@google.com> Link: https://patch.msgid.link/20260811094338.288094-6-mic@digikod.net Reviewed-by: Tingmao Wang <m@maowtm.org> [mic: Update copyright] Signed-off-by: Mickaël Salaün <mic@digikod.net>
2026-08-17landlock: Split denial logging from audit into common frameworkMickaël Salaün
Tracepoint emission requires the denial framework (layer identification, request validation) without depending on CONFIG_AUDIT. Separate the denial logging infrastructure from the audit-specific code by introducing a common log framework. Create CONFIG_SECURITY_LANDLOCK_LOG, enabled by default when CONFIG_AUDIT is set; a following commit extends it to CONFIG_TRACEPOINTS when the first tracepoint consumer is added. Move the common framework (the request types, the layer identification and request validation, and the landlock_log_denial() and landlock_log_free_domain() entry points) into log.c and log.h, and keep the audit-specific record formatting in audit.c. log.o is built for CONFIG_SECURITY_LANDLOCK_LOG and audit.o for CONFIG_AUDIT, so the common framework is available to a tracepoints-only build. The entry points dispatch to no-op static inline audit stubs without CONFIG_AUDIT, so the call sites stay unconditional. Rename the former landlock_log_drop_domain() to landlock_log_free_domain() to match the landlock_free_domain tracepoint added in a following commit. landlock_log_denial() counts denials even without audit, so its declaration and no-op stub are guarded by CONFIG_SECURITY_LANDLOCK_LOG, not CONFIG_AUDIT; a CONFIG_AUDIT guard would expose the stub and clash with log.c's definition in a tracepoints-only build. Widen the ID allocation (id.o and the landlock_init_id() / landlock_get_id_range() declarations) and the log-state representation (the domain_exec and log_subdomains_off credential fields, the landlock_hierarchy log fields, and the code that maintains them) from CONFIG_AUDIT to CONFIG_SECURITY_LANDLOCK_LOG, so each field and its writer share one guard and are available to tracing without audit support. Widen the denial-path state that feeds the per-denial logging decision the same way, so the "logged" verdict is computed identically whether or not CONFIG_AUDIT is set. Widening fown_layer is what keeps the file-owner-signal path valid without audit: otherwise hook_file_send_sigiotask() would leave layer_plus_one at zero, tripping the is_valid_request() canary and dropping the LANDLOCK_SCOPE_SIGNAL denial from tracing. The ruleset-level quiet_masks stays on no CONFIG guard: it is builder state validated and stored from user input, kept available so LANDLOCK_ADD_RULE_QUIET flags are accepted and ignored, not rejected, when CONFIG_SECURITY_LANDLOCK_LOG is disabled. Cc: Günther Noack <gnoack@google.com> Link: https://patch.msgid.link/20260811094338.288094-5-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>