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 @@ -552,6 +553,53 @@ TRACE_EVENT(landlock_add_rule_namespace, __entry->quiet_namespace_types) ); +/** + * 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 * @@ -980,6 +1028,54 @@ TRACE_EVENT(landlock_deny_permission_namespace, __entry->namespace_type, __entry->namespace_id) ); +/** + * 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 * 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, }; /** @@ -325,6 +330,42 @@ struct landlock_namespace_attr { __u64 quiet_namespace_types; }; +/** + * 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 * @@ -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 */ diff --git a/security/landlock/Makefile b/security/landlock/Makefile index e88ca842c782..97cf668db165 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -10,7 +10,8 @@ landlock-y := \ fs.o \ tsync.o \ domain.o \ - ns.o + ns.o \ + cap.o landlock-$(CONFIG_INET) += net.o diff --git a/security/landlock/access.h b/security/landlock/access.h index e3dbaa29a29f..f9eaa2f53686 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -73,16 +73,24 @@ struct permission_masks { * order. */ u64 ns_types : LANDLOCK_NUM_NAMESPACE_TYPE; + /** + * @caps: Capability member mask, indexed by CAP_* values. + */ + u64 caps : LANDLOCK_NUM_CAPABILITY; } __packed __aligned(sizeof(u64)); static_assert(sizeof(struct permission_masks) == sizeof(u64)); +/* All permission_masks bitfields must fit in a single u64. */ +static_assert(LANDLOCK_NUM_CAPABILITY + LANDLOCK_NUM_NAMESPACE_TYPE <= + BITS_PER_TYPE(u64)); /** * struct layer_config - Per-layer access configuration * * A ruleset stores one mutable layer and a domain stores a flexible array of * immutable layers. Unlike filesystem and network access rights, namespace - * types use a flat bitmask because their keyspace is small and bounded. + * types and capabilities use flat bitmasks because their keyspaces are small + * and bounded. */ struct layer_config { /** diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 5386f1411ba6..be49ef47b4ea 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -81,6 +81,7 @@ get_blocker(const enum landlock_request_type type, return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)]; case LANDLOCK_REQUEST_NAMESPACE: + case LANDLOCK_REQUEST_CAPABILITY: if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(permission_strings))) return "unknown"; return permission_strings[access_bit]; @@ -116,6 +117,9 @@ blocker_prefix(const enum landlock_request_type type) case LANDLOCK_REQUEST_NAMESPACE: return _LANDLOCK_PERMISSION_NAMESPACE_NAME "."; + + case LANDLOCK_REQUEST_CAPABILITY: + return _LANDLOCK_PERMISSION_CAPABILITY_NAME "."; } WARN_ON_ONCE(1); diff --git a/security/landlock/cap.c b/security/landlock/cap.c new file mode 100644 index 000000000000..b4f491b09baa --- /dev/null +++ b/security/landlock/cap.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Capability hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#include +#include +#include +#include +#include + +#include "cap.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "log.h" +#include "ruleset.h" +#include "setup.h" + +static const struct access_masks cap_permission = { + .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, +}; + +/** + * hook_capable - Deny capability use for Landlock-sandboxed processes + * + * @cred: Credentials being checked. + * @ns: Target user namespace (intentionally ignored; policy depends on the + * acting credentials and @cap, not namespace ownership). + * @cap: Capability number (CAP_*). + * @opts: Capability check options. CAP_OPT_NOAUDIT skips the denial record: + * no audit record, no trace event, and no denial count. + * + * Pure bitmask check: denies the capability if it is not in the layer's allowed + * set. This hook is purely restrictive: commoncap is registered with + * LSM_ORDER_FIRST so cap_capable() always runs first, which means Landlock can + * deny capabilities that commoncap would allow, but never grant capabilities + * that commoncap denied. + * + * Return: 0 if allowed, -EPERM if capability use is denied. + */ +static int hook_capable(const struct cred *cred, struct user_namespace *ns, + int cap, unsigned int opts) +{ + const struct landlock_cred_security *subject; + size_t denied_layer; + + subject = landlock_get_applicable_subject(cred, cap_permission, NULL); + if (!subject) + return 0; + + denied_layer = landlock_permission_is_denied( + subject->domain, LANDLOCK_PERMISSION_CAPABILITY_USE, + landlock_cap_to_bit(cap)); + if (!denied_layer) + return 0; + + if (!(opts & CAP_OPT_NOAUDIT)) + landlock_log_denial( + subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_CAPABILITY, + .audit.type = LSM_AUDIT_DATA_CAP, + .audit.u.cap = cap, + .permission = + LANDLOCK_PERMISSION_CAPABILITY_USE, + .layer_plus_one = denied_layer, + }); + + return -EPERM; +} + +static struct security_hook_list landlock_hooks[] __ro_after_init = { + LSM_HOOK_INIT(capable, hook_capable), +}; + +__init void landlock_add_cap_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + &landlock_lsmid); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +#include + +static void test_cap_to_bit(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, BIT_ULL(0), landlock_cap_to_bit(0)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW), + landlock_cap_to_bit(CAP_NET_RAW)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_SYS_ADMIN), + landlock_cap_to_bit(CAP_SYS_ADMIN)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_LAST_CAP), + landlock_cap_to_bit(CAP_LAST_CAP)); +} + +static void test_cap_to_bit_invalid(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(-1)); + KUNIT_EXPECT_EQ(test, 0ULL, + landlock_cap_to_bit(CAP_LAST_CAP + 1)); + } +} + +static void test_caps_to_bits_valid(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, (u64)CAP_VALID_MASK, + landlock_caps_to_bits(CAP_VALID_MASK)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW), + landlock_caps_to_bits(BIT_ULL(CAP_NET_RAW))); +} + +static void test_caps_to_bits_unknown(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ( + test, 0ULL, + landlock_caps_to_bits(BIT_ULL(CAP_LAST_CAP + 1))); + } +} + +static void test_caps_to_bits_zero(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, 0ULL, landlock_caps_to_bits(0)); +} + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_cap_to_bit), + KUNIT_CASE(test_cap_to_bit_invalid), + KUNIT_CASE(test_caps_to_bits_valid), + KUNIT_CASE(test_caps_to_bits_unknown), + KUNIT_CASE(test_caps_to_bits_zero), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_cap", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/cap.h b/security/landlock/cap.h new file mode 100644 index 000000000000..11208cbf15d2 --- /dev/null +++ b/security/landlock/cap.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Capability hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _SECURITY_LANDLOCK_CAP_H +#define _SECURITY_LANDLOCK_CAP_H + +#include +#include +#include +#include + +/** + * landlock_cap_to_bit - Convert a capability number to a compact bitmask + * + * @cap: Capability number (CAP_*). + * + * Return: BIT_ULL(@cap), or 0 if @cap is invalid (with a WARN). + */ +static inline u64 landlock_cap_to_bit(const int cap) +{ + if (WARN_ON_ONCE(!cap_valid(cap))) + return 0; + + return BIT_ULL(cap); +} + +/** + * landlock_caps_to_bits - Validate and mask a capability bitmask + * + * @capabilities: Bitmask of capabilities (e.g. from user space). + * + * Return: @capabilities masked to known capabilities. Warns if unknown bits + * are present (callers must pre-mask for user input). + */ +static inline u64 landlock_caps_to_bits(const u64 capabilities) +{ + /* Callers pre-mask (CAP_VALID_MASK); the WARN guards future callers. */ + WARN_ON_ONCE(capabilities & ~CAP_VALID_MASK); + return capabilities & CAP_VALID_MASK; +} + +__init void landlock_add_cap_hooks(void); + +#endif /* _SECURITY_LANDLOCK_CAP_H */ diff --git a/security/landlock/domain.h b/security/landlock/domain.h index d9e7b96aa639..935e18b12b3c 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -377,6 +377,9 @@ landlock_permission_is_denied(const struct landlock_domain *const domain, case LANDLOCK_PERMISSION_NAMESPACE_USE: allowed = domain->layers[layer].allowed.ns_types; break; + case LANDLOCK_PERMISSION_CAPABILITY_USE: + allowed = domain->layers[layer].allowed.caps; + break; default: WARN_ONCE(1, "Unknown permission %u\n", (unsigned int)permission_bit); diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 767d57799f60..ddd300e10689 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -11,6 +11,7 @@ #define _SECURITY_LANDLOCK_LIMITS_H #include +#include #include #include #include @@ -32,11 +33,12 @@ #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) -#define LANDLOCK_LAST_PERMISSION LANDLOCK_PERMISSION_NAMESPACE_USE +#define LANDLOCK_LAST_PERMISSION LANDLOCK_PERMISSION_CAPABILITY_USE #define LANDLOCK_MASK_PERMISSION ((LANDLOCK_LAST_PERMISSION << 1) - 1) #define LANDLOCK_NUM_PERMISSION __const_hweight64(LANDLOCK_MASK_PERMISSION) #define LANDLOCK_NUM_NAMESPACE_TYPE __const_hweight64((u64)CLONE_NS_ALL) +#define LANDLOCK_NUM_CAPABILITY (CAP_LAST_CAP + 1) #define LANDLOCK_NUM_ACCESS_MAX \ MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE) diff --git a/security/landlock/log.c b/security/landlock/log.c index 8cb1dfd0d4ae..43efbb95725c 100644 --- a/security/landlock/log.c +++ b/security/landlock/log.c @@ -12,6 +12,7 @@ #include "access.h" #include "audit.h" +#include "cap.h" #include "common.h" #include "cred.h" #include "domain.h" @@ -393,6 +394,12 @@ static bool is_valid_request(const struct landlock_request *const request) WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_NS)) return false; break; + case LANDLOCK_REQUEST_CAPABILITY: + if (WARN_ON_ONCE(request->permission != + LANDLOCK_PERMISSION_CAPABILITY_USE) || + WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_CAP)) + return false; + break; case LANDLOCK_REQUEST_PTRACE: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: case LANDLOCK_REQUEST_FS_ACCESS: @@ -481,6 +488,9 @@ is_denial_quieted(const struct landlock_request *const request, case LANDLOCK_REQUEST_NAMESPACE: return !!(youngest_denied->quiet_permission.ns_types & landlock_ns_type_to_bit(request->audit.u.ns.ns_type)); + case LANDLOCK_REQUEST_CAPABILITY: + return !!(youngest_denied->quiet_permission.caps & + landlock_cap_to_bit(request->audit.u.cap)); /* * Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY * unhandled for now - they are never quiet. diff --git a/security/landlock/log.h b/security/landlock/log.h index a12956cac40e..596feeeb7aa6 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -26,6 +26,7 @@ enum landlock_request_type { LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, LANDLOCK_REQUEST_SCOPE_SIGNAL, LANDLOCK_REQUEST_NAMESPACE, + LANDLOCK_REQUEST_CAPABILITY, }; struct landlock_blockers { diff --git a/security/landlock/setup.c b/security/landlock/setup.c index a7ed776b41b4..971419d663bb 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -11,6 +11,7 @@ #include #include +#include "cap.h" #include "common.h" #include "cred.h" #include "errata.h" @@ -70,6 +71,7 @@ static int __init landlock_init(void) landlock_add_fs_hooks(); landlock_add_net_hooks(); landlock_add_ns_hooks(); + landlock_add_cap_hooks(); landlock_init_id(); landlock_initialized = true; pr_info("Up and running.\n"); diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index c37edcc478bc..fb5af318b69b 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -31,6 +31,7 @@ #include #include +#include "cap.h" #include "cred.h" #include "domain.h" #include "fs.h" @@ -101,8 +102,9 @@ static void build_check_abi(void) struct landlock_path_beneath_attr path_beneath_attr; struct landlock_net_port_attr net_port_attr; struct landlock_namespace_attr namespace_attr; + struct landlock_capability_attr capability_attr; size_t ruleset_size, path_beneath_size, net_port_size; - size_t namespace_size; + size_t namespace_size, capability_size; /* * For each user space ABI structures, first checks that there is no @@ -134,6 +136,12 @@ static void build_check_abi(void) namespace_size += sizeof(namespace_attr.quiet_namespace_types); BUILD_BUG_ON(sizeof(namespace_attr) != namespace_size); BUILD_BUG_ON(sizeof(namespace_attr) != 24); + + capability_size = sizeof(capability_attr.permissions); + capability_size += sizeof(capability_attr.allowed_capabilities); + capability_size += sizeof(capability_attr.quiet_capabilities); + BUILD_BUG_ON(sizeof(capability_attr) != capability_size); + BUILD_BUG_ON(sizeof(capability_attr) != 24); } /* Ruleset handling */ @@ -527,14 +535,88 @@ static int add_rule_namespace(struct landlock_ruleset *const ruleset, return 0; } +static int add_rule_capability(struct landlock_ruleset *const ruleset, + const void __user *const rule_attr, + const u32 flags) +{ + struct landlock_capability_attr cap_attr; + access_mask_t mask; + u64 allowed_caps, quiet_caps; + int ret; + + /* + * Capability rules support no add-rule flags. In particular, + * LANDLOCK_ADD_RULE_QUIET is filesystem/network only. + */ + if (flags) + return -EINVAL; + + /* Copies raw user space buffer. */ + ret = copy_from_user(&cap_attr, rule_attr, sizeof(cap_attr)); + if (ret) + return -EFAULT; + + /* Informs about useless rule: empty permissions. */ + if (!cap_attr.permissions) + return -ENOMSG; + + /* + * The permissions selector must match + * LANDLOCK_PERMISSION_CAPABILITY_USE. The valid set is a single bit + * today, so this is an exact match now; the check broadens to a subset + * test once another supported permission is added. + */ + if (cap_attr.permissions != LANDLOCK_PERMISSION_CAPABILITY_USE) + return -EINVAL; + + /* + * Checks that permissions match the ruleset constraints. This also + * makes quieting require the category to be handled. + */ + mask = landlock_get_permission_mask(ruleset); + if (!(mask & LANDLOCK_PERMISSION_CAPABILITY_USE)) + return -EINVAL; + + /* + * Informs about useless rule: neither allows nor quiets anything. A + * quiet-only rule (empty allowed set) is legal. + */ + if (!cap_attr.allowed_capabilities && !cap_attr.quiet_capabilities) + return -ENOMSG; + + /* + * Stores only the capabilities this kernel knows about. Unknown bits + * are silently accepted for forward compatibility: user space compiled + * against newer headers can pass new CAP_* bits without getting EINVAL + * on older kernels. Unknown bits have no effect because no hook checks + * them. The quiet bitmask suppresses logging of denials attributed to + * this layer; see landlock_log_denial(). + */ + allowed_caps = cap_attr.allowed_capabilities & CAP_VALID_MASK; + quiet_caps = cap_attr.quiet_capabilities & CAP_VALID_MASK; + + mutex_lock(&ruleset->lock); + ruleset->layer.allowed.caps |= landlock_caps_to_bits(allowed_caps); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + ruleset->quiet_permission.caps |= landlock_caps_to_bits(quiet_caps); +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#ifdef CONFIG_TRACEPOINTS + ruleset->version++; +#endif /* CONFIG_TRACEPOINTS */ + trace_landlock_add_rule_capability(ruleset, flags, cap_attr.permissions, + allowed_caps, quiet_caps); + mutex_unlock(&ruleset->lock); + return 0; +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * * @ruleset_fd: File descriptor tied to the ruleset that should be extended * with the new rule. * @rule_type: Identify the structure type pointed to by @rule_attr: - * %LANDLOCK_RULE_PATH_BENEATH, %LANDLOCK_RULE_NET_PORT, or - * %LANDLOCK_RULE_NAMESPACE. + * %LANDLOCK_RULE_PATH_BENEATH, %LANDLOCK_RULE_NET_PORT, + * %LANDLOCK_RULE_NAMESPACE, or %LANDLOCK_RULE_CAPABILITY. * @rule_attr: Pointer to a rule (matching the @rule_type). * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET. * @@ -553,6 +635,8 @@ static int add_rule_namespace(struct landlock_ruleset *const ruleset, * handled accesses) * - %EINVAL: A nonzero &landlock_namespace_attr.permissions is not * %LANDLOCK_PERMISSION_NAMESPACE_USE or is not handled by the ruleset; + * - %EINVAL: A nonzero &landlock_capability_attr.permissions is not + * %LANDLOCK_PERMISSION_CAPABILITY_USE or is not handled by the ruleset; * - %EINVAL: &landlock_net_port_attr.port is greater than 65535; * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no * quiet access bits set for the corresponding rule type. @@ -561,6 +645,9 @@ static int add_rule_namespace(struct landlock_ruleset *const ruleset, * - %ENOMSG: &landlock_namespace_attr.permissions is 0, or both * &landlock_namespace_attr.allowed_namespace_types and * &landlock_namespace_attr.quiet_namespace_types are 0; + * - %ENOMSG: &landlock_capability_attr.permissions is 0, or both + * &landlock_capability_attr.allowed_capabilities and + * &landlock_capability_attr.quiet_capabilities are 0; * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of @@ -595,6 +682,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, return add_rule_net_port(ruleset, rule_attr, flags); case LANDLOCK_RULE_NAMESPACE: return add_rule_namespace(ruleset, rule_attr, flags); + case LANDLOCK_RULE_CAPABILITY: + return add_rule_capability(ruleset, rule_attr, flags); default: return -EINVAL; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 300afcc82220..995c6b948e16 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -94,6 +94,18 @@ void landlock_trace_denial( request->audit.u.ns.ns_id); } break; + case LANDLOCK_REQUEST_CAPABILITY: + if (trace_landlock_deny_permission_capability_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + + trace_landlock_deny_permission_capability( + youngest_denied, same_exec, logged, &blockers, + request->audit.u.cap); + } + break; case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) {