summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorMickaël Salaün <mic@digikod.net>2026-10-14 10:27:10 +0200
committerMickaël Salaün <mic@digikod.net>2026-10-20 16:47:20 +0200
commit97c8386f1d744e9689cd3e72347f980d9af4f59f (patch)
treeecedf373e18002e048031c18211b52b3e0b765f9 /security
parent15dd86021b57a9747b44321dda5384e19ce1a0d8 (diff)
landlock: Enforce capability restrictions
Add Landlock enforcement for capability use via the LSM capable hook. This lets a sandboxed process restrict which Linux capabilities it can exercise, using LANDLOCK_PERMISSION_CAPABILITY_USE and per-capability rules. The check is a flat per-layer allowed-capabilities test, with no domain-ancestry bypass, no cross-namespace discriminant, and no dependency on the target user namespace. These categorical denials return -EPERM, like the namespace permission introduced by the previous commit, and they mirror its per-capability allowed and quiet masks, so LANDLOCK_ADD_RULE_QUIET stays rejected for this rule type. Successful capability rules and denials are traced as the previous commit describes. Enforce only at capability exercise time rather than modifying the credential's capability sets. Modifying them would give capget(2) an accurate view of usable capabilities, but no LSM other than commoncap does so; Landlock follows that convention. A sandboxed process inside a user namespace therefore sees all capabilities via capget(2) but receives -EPERM when attempting to use one that is denied. An earlier design bypassed this hook for namespace management through a domain-ancestry comparison with the namespace creator's stored Landlock domain. That made a Landlock decision depend on kernel namespace state, and the ns != cred->user_ns heuristic did not accurately identify namespace-management operations. The flat, namespace-agnostic check instead enforces capability and namespace restrictions independently; creating a non-user namespace requires an allowed CAP_SYS_ADMIN even when combined with CLONE_NEWUSER in one unshare(). Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Paul Moore <paul@paul-moore.com> Cc: Serge E. Hallyn <serge@hallyn.com> Link: https://patch.msgid.link/20261014082718.1435842-5-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
Diffstat (limited to 'security')
-rw-r--r--security/landlock/Makefile3
-rw-r--r--security/landlock/access.h10
-rw-r--r--security/landlock/audit.c4
-rw-r--r--security/landlock/cap.c159
-rw-r--r--security/landlock/cap.h48
-rw-r--r--security/landlock/domain.h3
-rw-r--r--security/landlock/limits.h4
-rw-r--r--security/landlock/log.c10
-rw-r--r--security/landlock/log.h1
-rw-r--r--security/landlock/setup.c2
-rw-r--r--security/landlock/syscalls.c95
-rw-r--r--security/landlock/trace.c12
12 files changed, 345 insertions, 6 deletions
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 <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 */
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 <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/capability.h>
+#include <linux/types.h>
+
+/**
+ * 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 <linux/bitops.h>
+#include <linux/capability.h>
#include <linux/limits.h>
#include <linux/ns/ns_common_types.h>
#include <uapi/linux/landlock.h>
@@ -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 <linux/lsm_hooks.h>
#include <uapi/linux/lsm.h>
+#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 <linux/uaccess.h>
#include <uapi/linux/landlock.h>
+#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()) {