// SPDX-License-Identifier: GPL-2.0-only /* * Landlock - Namespace hooks * * Copyright © 2026 Cloudflare, Inc. */ #include #include #include #include #include #include #include #include #include "cred.h" #include "domain.h" #include "limits.h" #include "log.h" #include "ns.h" #include "ruleset.h" #include "setup.h" #define _LANDLOCK_NS_TYPE(type, flag) flag, static const u32 landlock_namespace_types[] = { /* clang-format off */ FOR_EACH_NS_TYPE(_LANDLOCK_NS_TYPE) /* clang-format on */ }; #undef _LANDLOCK_NS_TYPE static_assert(ARRAY_SIZE(landlock_namespace_types) == LANDLOCK_NUM_NAMESPACE_TYPE); /* Ensures the audit ns_id field can hold ns_common.ns_id without truncation. */ static_assert(sizeof(((struct common_audit_data *)NULL)->u.ns.ns_id) >= sizeof(((struct ns_common *)NULL)->ns_id)); /** * landlock_ns_type_to_bit - Convert a namespace type to a compact bitmask * * @ns_type: Namespace type (``CLONE_NEW*``). * * Return: The compact bit for @ns_type, or 0 if @ns_type is invalid (with a * warning). */ u64 landlock_ns_type_to_bit(const u32 ns_type) { size_t i; for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { if (landlock_namespace_types[i] == ns_type) return BIT_ULL(i); } WARN_ONCE(1, "Unknown namespace type 0x%x\n", ns_type); return 0; } /** * landlock_ns_types_to_bits - Convert namespace types to a compact bitmask * * @ns_types: Bitmask of namespace types (``CLONE_NEW*``). * * Return: The compact bits for all known @ns_types. Warns if unknown bits are * present (callers must pre-mask user input). */ u64 landlock_ns_types_to_bits(const u64 ns_types) { u64 bits = 0; size_t i; /* Callers pre-mask (CLONE_NS_ALL); the WARN guards future callers. */ WARN_ON_ONCE(ns_types & ~(u64)CLONE_NS_ALL); for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { if (ns_types & landlock_namespace_types[i]) bits |= BIT_ULL(i); } return bits; } static const struct access_masks ns_permission = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, }; /** * check_ns_type - Check namespace use permission * * @ns: The namespace being allocated or installed. * * Shared check for namespace_init (creation via clone(2), unshare(2), * open_tree(2), or fsmount(2)) and namespace_install (use via setns(2)): denies * when the namespace type is not in the domain's allowed set. At allocation * time @ns->ns_id is still zero and is logged as such. * * Return: 0 if allowed, -EPERM if denied. */ static int check_ns_type(struct ns_common *const ns) { const struct landlock_cred_security *subject; size_t denied_layer; subject = landlock_get_applicable_subject(current_cred(), ns_permission, NULL); if (!subject) return 0; denied_layer = landlock_permission_is_denied( subject->domain, LANDLOCK_PERMISSION_NAMESPACE_USE, landlock_ns_type_to_bit(ns->ns_type)); if (!denied_layer) return 0; landlock_log_denial(subject, &(struct landlock_request){ .type = LANDLOCK_REQUEST_NAMESPACE, .audit.type = LSM_AUDIT_DATA_NS, .permission = LANDLOCK_PERMISSION_NAMESPACE_USE, .audit.u.ns.ns_type = ns->ns_type, .audit.u.ns.ns_id = ns->ns_id, .layer_plus_one = denied_layer, }); return -EPERM; } static int hook_namespace_init(struct ns_common *const ns) { return check_ns_type(ns); } static int hook_namespace_install(const struct nsset *const nsset, struct ns_common *const ns) { return check_ns_type(ns); } static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(namespace_init, hook_namespace_init), LSM_HOOK_INIT(namespace_install, hook_namespace_install), }; __init void landlock_add_ns_hooks(void) { security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), &landlock_lsmid); } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST #include static void test_ns_type_to_bit(struct kunit *const test) { u64 seen = 0; size_t i; for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { const u64 bit = landlock_ns_type_to_bit(landlock_namespace_types[i]); KUNIT_EXPECT_NE(test, 0ULL, bit); KUNIT_EXPECT_EQ(test, 0ULL, seen & bit); seen |= bit; } KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0), seen); } static void test_ns_type_to_bit_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_ns_type_to_bit(CLONE_THREAD)); KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); } } static void test_ns_types_to_bits_all(struct kunit *const test) { KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0), landlock_ns_types_to_bits(CLONE_NS_ALL)); } static void test_ns_types_to_bits_single(struct kunit *const test) { size_t i; for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) KUNIT_EXPECT_EQ( test, landlock_ns_type_to_bit(landlock_namespace_types[i]), landlock_ns_types_to_bits(landlock_namespace_types[i])); } static void test_ns_types_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_ns_types_to_bits(CLONE_THREAD)); KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); } } static void test_ns_types_to_bits_zero(struct kunit *const test) { KUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_types_to_bits(0)); } static struct kunit_case test_cases[] = { KUNIT_CASE(test_ns_type_to_bit), KUNIT_CASE(test_ns_type_to_bit_unknown), KUNIT_CASE(test_ns_types_to_bits_all), KUNIT_CASE(test_ns_types_to_bits_single), KUNIT_CASE(test_ns_types_to_bits_unknown), KUNIT_CASE(test_ns_types_to_bits_zero), {} }; static struct kunit_suite test_suite = { .name = "landlock_ns", .test_cases = test_cases, }; kunit_test_suite(test_suite); #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */