blob: cd44c927f83ae8dc8b609be2dd48377df8d33742 [file]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Landlock - Namespace hooks
*
* Copyright © 2026 Cloudflare, Inc.
*/
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <linux/ns/ns_common_types.h>
#include <linux/ns_common.h>
#include <linux/nsproxy.h>
#include <uapi/linux/landlock.h>
#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 <kunit/test.h>
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 */