blob: b4f491b09baa7990141acad123b0b303454fd65f [file]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Landlock - Capability hooks
*
* Copyright © 2026 Cloudflare, Inc.
*/
#include <linux/capability.h>
#include <linux/cred.h>
#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <uapi/linux/landlock.h>
#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 <kunit/test.h>
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 */