// 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 */