summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorMickaël Salaün <mic@digikod.net>2026-10-14 10:27:10 +0200
committerMickaël Salaün <mic@digikod.net>2026-10-20 16:47:20 +0200
commit97c8386f1d744e9689cd3e72347f980d9af4f59f (patch)
treeecedf373e18002e048031c18211b52b3e0b765f9 /include
parent15dd86021b57a9747b44321dda5384e19ce1a0d8 (diff)
landlock: Enforce capability restrictions
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>
Diffstat (limited to 'include')
-rw-r--r--include/linux/landlock.h5
-rw-r--r--include/trace/events/landlock.h96
-rw-r--r--include/uapi/linux/landlock.h49
3 files changed, 149 insertions, 1 deletions
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index d288e3b6756f..0ee4880988ba 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -56,10 +56,13 @@
_LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal")
#define _LANDLOCK_PERMISSION_NAMESPACE_NAME "namespace"
+#define _LANDLOCK_PERMISSION_CAPABILITY_NAME "capability"
#define _LANDLOCK_PERMISSION_LIST(entry) \
entry(LANDLOCK_PERMISSION_NAMESPACE_USE, "use", \
- _LANDLOCK_PERMISSION_NAMESPACE_NAME)
+ _LANDLOCK_PERMISSION_NAMESPACE_NAME), \
+ entry(LANDLOCK_PERMISSION_CAPABILITY_USE, "use", \
+ _LANDLOCK_PERMISSION_CAPABILITY_NAME)
#define _LANDLOCK_PERMISSION_QUALIFIED_ENTRY(mask, action, domain) \
_LANDLOCK_NAME_ENTRY(mask, domain "." action)
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index c5a051ede8d9..744ef55ec1de 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -37,6 +37,7 @@ TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS);
TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NAMESPACE);
+TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_CAPABILITY);
#ifdef CREATE_TRACE_POINTS
@@ -553,6 +554,53 @@ TRACE_EVENT(landlock_add_rule_namespace,
);
/**
+ * landlock_add_rule_capability - Capability rule added to a ruleset
+ *
+ * @ruleset: Source ruleset (never NULL).
+ * @flags: Complete validated landlock_add_rule_flags value supplied by this
+ * successful call, not the rule's accumulated quiet state.
+ * @permissions: Validated permission mask from the rule attribute.
+ * @allowed_capabilities: Effective known capabilities allowed by this call.
+ * @quiet_capabilities: Effective known capabilities quieted by this call.
+ *
+ * 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_capability,
+
+ TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags,
+ u64 permissions, u64 allowed_capabilities,
+ u64 quiet_capabilities),
+
+ TP_ARGS(ruleset, flags, permissions, allowed_capabilities,
+ quiet_capabilities),
+
+ TP_STRUCT__entry(
+ __field( u64, ruleset_id )
+ __field( u64, ruleset_version )
+ __field( access_mask_t, permissions )
+ __field( u64, allowed_capabilities )
+ __field( u64, quiet_capabilities )
+ ),
+
+ TP_fast_assign(
+ lockdep_assert_held(&ruleset->lock);
+ __entry->ruleset_id = ruleset->id;
+ __entry->ruleset_version = ruleset->version;
+ __entry->permissions = permissions;
+ __entry->allowed_capabilities = allowed_capabilities;
+ __entry->quiet_capabilities = quiet_capabilities;
+ ),
+
+ TP_printk("ruleset=%llx.%llu permissions=%s allowed_capabilities=0x%llx quiet_capabilities=0x%llx",
+ __entry->ruleset_id, __entry->ruleset_version,
+ __print_flags(__entry->permissions, "|",
+ _LANDLOCK_PERMISSION_NAMES),
+ __entry->allowed_capabilities, __entry->quiet_capabilities)
+);
+
+/**
* landlock_create_domain - New domain created
*
* @domain: Newly created domain (never NULL, immutable after creation).
@@ -981,6 +1029,54 @@ TRACE_EVENT(landlock_deny_permission_namespace,
);
/**
+ * landlock_deny_permission_capability - Capability use denied
+ *
+ * @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.
+ * @logged: Whether this denial was selected for audit logging.
+ * @blockers: Request type and final missing permission subset (never NULL).
+ * @capability: CAP_* number that was denied.
+ *
+ * Emitted when a Landlock domain denies capability use, except for checks
+ * made with CAP_OPT_NOAUDIT, which are denied without emitting this event.
+ */
+TRACE_EVENT(landlock_deny_permission_capability,
+
+ TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec,
+ bool logged, const struct landlock_blockers *blockers,
+ int capability),
+
+ TP_ARGS(hierarchy, same_exec, logged, blockers, capability),
+
+ TP_STRUCT__entry(
+ __field( u64, domain_id )
+ __field( bool, same_exec )
+ __field( bool, logged )
+ __field( enum landlock_request_type, blockers_type )
+ __field( access_mask_t, blockers_access )
+ __field( int, capability )
+ ),
+
+ TP_fast_assign(
+ __entry->domain_id = hierarchy->id;
+ __entry->same_exec = same_exec;
+ __entry->logged = logged;
+ __entry->blockers_type = blockers->type;
+ __entry->blockers_access = blockers->access;
+ __entry->capability = capability;
+ ),
+
+ TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s capability=%d",
+ __entry->domain_id, __entry->same_exec, __entry->logged,
+ __entry->blockers_type == LANDLOCK_REQUEST_CAPABILITY ?
+ __print_flags(__entry->blockers_access, "|",
+ _LANDLOCK_PERMISSION_BLOCKER_NAMES) :
+ "unknown",
+ __entry->capability)
+);
+
+/**
* landlock_deny_ptrace - Ptrace access denied by a Landlock domain
*
* @hierarchy: Denying domain's hierarchy node (never NULL); its id is the
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index bb8ec589ddac..3128e58d4b78 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -237,6 +237,11 @@ enum landlock_rule_type {
* @LANDLOCK_RULE_NAMESPACE: Type of a &struct landlock_namespace_attr .
*/
LANDLOCK_RULE_NAMESPACE,
+ /**
+ * @LANDLOCK_RULE_CAPABILITY: Type of a &struct
+ * landlock_capability_attr .
+ */
+ LANDLOCK_RULE_CAPABILITY,
};
/**
@@ -326,6 +331,42 @@ struct landlock_namespace_attr {
};
/**
+ * struct landlock_capability_attr - Capability definition
+ *
+ * Argument of sys_landlock_add_rule() with %LANDLOCK_RULE_CAPABILITY.
+ */
+struct landlock_capability_attr {
+ /**
+ * @permissions: Must be set to %LANDLOCK_PERMISSION_CAPABILITY_USE.
+ */
+ __u64 permissions;
+ /**
+ * @allowed_capabilities: Bitmask of capabilities (``1ULL << CAP_*``) to
+ * allow under this rule. Bits above ``CAP_LAST_CAP`` are silently
+ * ignored for forward compatibility.
+ */
+ __u64 allowed_capabilities;
+ /**
+ * @quiet_capabilities: Bitmask of capabilities (``1ULL << CAP_*``)
+ * whose denial by this layer should not be submitted to audit, even if
+ * audit logging would normally take place per landlock_restrict_self()
+ * flags. Only audit records attributed to this layer are suppressed;
+ * denial tracepoints still fire (see `Permission flags`_). Bits also
+ * set in @allowed_capabilities have no effect, since an allowed
+ * capability is never denied. Bits above ``CAP_LAST_CAP`` are silently
+ * ignored.
+ *
+ * At least one of @allowed_capabilities or @quiet_capabilities must be
+ * non-zero, otherwise the call returns ``-ENOMSG``. The non-zero check
+ * runs on the raw input before unknown-bit masking, so a rule that sets
+ * only bits unknown to the running kernel (above ``CAP_LAST_CAP``)
+ * succeeds but has no runtime effect. Programs should omit this rule
+ * when they neither allow nor quiet a capability.
+ */
+ __u64 quiet_capabilities;
+};
+
+/**
* DOC: fs_access
*
* A set of actions on kernel objects may be defined by an attribute (e.g.
@@ -577,7 +618,15 @@ struct landlock_namespace_attr {
* Landlock domain that handles this permission is denied from using namespace
* types that are not explicitly allowed by a %LANDLOCK_RULE_NAMESPACE rule.
* Support added in Landlock ABI version 12.
+ * - %LANDLOCK_PERMISSION_CAPABILITY_USE: Restrict the use of specific Linux
+ * capabilities. A process in a Landlock domain that handles this permission
+ * is denied from exercising capabilities that are not explicitly allowed by a
+ * %LANDLOCK_RULE_CAPABILITY rule. This hook is purely restrictive: it can
+ * deny capabilities that the kernel would otherwise grant, but it can never
+ * grant capabilities that the kernel already denied. Support added in
+ * Landlock ABI version 12.
*/
#define LANDLOCK_PERMISSION_NAMESPACE_USE (1ULL << 0)
+#define LANDLOCK_PERMISSION_CAPABILITY_USE (1ULL << 1)
#endif /* _UAPI_LINUX_LANDLOCK_H */