diff options
| author | Mickaël Salaün <mic@digikod.net> | 2026-10-14 10:27:10 +0200 |
|---|---|---|
| committer | Mickaël Salaün <mic@digikod.net> | 2026-10-20 16:47:20 +0200 |
| commit | 97c8386f1d744e9689cd3e72347f980d9af4f59f (patch) | |
| tree | ecedf373e18002e048031c18211b52b3e0b765f9 /include/trace | |
| parent | 15dd86021b57a9747b44321dda5384e19ce1a0d8 (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/trace')
| -rw-r--r-- | include/trace/events/landlock.h | 96 |
1 files changed, 96 insertions, 0 deletions
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 |
