* [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies
@ 2026-11-10 20:30 Mickaël Salaün
2026-11-10 20:30 ` [PATCH 1/6] lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy Mickaël Salaün
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
Hi,
This is series 3 of 3:
1. fix Landlock mediation of commoncap's capability checks;
2. fix PR_SET_SECUREBITS mediation and add focused Landlock tests;
* 3. fix Landlock mediation of capabilities guarding LSM policies.
This series applies on top of series 2:
https://patch.msgid.link/20261110202811.2409395-1-mic@digikod.net
AppArmor, Smack and SELinux deliberately check the capabilities that
guard their own policy with cap_capable() instead of capable(), so that
other LSMs do not have to authorize them:
- commit db59000ab760 ("selinux: only invoke capabilities and selinux
for CAP_MAC_ADMIN checks");
- commit f28e783ff668 ("Smack: Use cap_capable in privilege check");
- commit 31ec99e13346 ("apparmor: switch to apparmor to internal capable
check for policy management").
However, LANDLOCK_PERMISSION_CAPABILITY_USE, merged for v7.4, restricts
capability use in general through the capable hook, so a domain that
denies CAP_MAC_ADMIN can still load AppArmor policy, change a file's
Smack label, or set SELinux contexts unknown to the loaded policy.
Patch 1 adds security_capable_lsm(), which these LSMs call instead of
cap_capable(). It checks the capability with commoncap, then calls a
new capable_lsm hook with the LSM_ID_* of the LSM whose policy the
capability guards. LSMs that only implement the capable hook still do
not see these checks. Patch 2 implements the hook in Landlock, patches
3 to 5 switch AppArmor, Smack and SELinux to the new helper, and patch 6
adds tests.
Calling the other LSMs' capable hooks instead, possibly with a new
CAP_OPT_* flag, would change what the capable hook sees for its current
users, including BPF LSM programs, and would require every MAC LSM to
ignore these checks. The new hook keeps the choice of the commits above
by default and follows the hook guidelines. The trade-off is that an LSM
restricting capability use in general needs to implement both hooks, as the
kerneldoc says.
As the capable hook, the new hook can be called in atomic context and
with another task's credentials (Smack's SIGIO check and ptrace rule).
Smack passes CAP_OPT_NONE, so Landlock logs every denial of these
capabilities, including for the override checks that some Smack hooks
make before their label rules.
The new tests are skipped unless Smack or AppArmor is active. With
lsm=landlock,smack and lsm=landlock,apparmor, they fail without these
patches and pass with them. SELinux only checks CAP_MAC_ADMIN this way
with a loaded policy, so it is only build- and boot-tested.
Paul, an Acked-by for the LSM and SELinux patches, and for taking this
series through the Landlock tree, would be welcome.
Stephen, a review of the LSM and SELinux patches would be welcome.
John, a review of the AppArmor patch would be welcome.
Casey, a review of the Smack patch would be welcome.
KP and Matt, a review of the new BPF LSM attach point would be welcome.
Regards,
Mickaël Salaün (6):
lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy
landlock: Restrict capabilities guarding other LSMs' policies
apparmor: Use security_capable_lsm() for policy capabilities
smack: Use security_capable_lsm() for privilege checks
selinux: Use security_capable_lsm() for CAP_MAC_ADMIN checks
selftests/landlock: Test capabilities guarding other LSMs' policies
include/linux/lsm_hook_defs.h | 2 +
include/linux/security.h | 9 +
security/apparmor/domain.c | 9 +-
security/apparmor/policy.c | 7 +-
security/landlock/cap.c | 23 ++
security/security.c | 47 ++++
security/selinux/hooks.c | 3 +-
security/smack/smack_access.c | 3 +-
tools/testing/selftests/landlock/cap_test.c | 230 +++++++++++++++++++-
tools/testing/selftests/landlock/common.h | 2 +
10 files changed, 321 insertions(+), 14 deletions(-)
base-commit: 7475ea8b58616b33c04afb976cf0fbaad9dd63fb
prerequisite-patch-id: 365071039dd2be439f67707944d12edbe1422d60
prerequisite-patch-id: 021705fb5147cdc8edcac9dbe53c06b854cf976a
prerequisite-patch-id: 1dfd782b28feeac47423e702764778031b850d6c
prerequisite-patch-id: 0bf745b08b99168d266461ef5b0b943a2b71eceb
prerequisite-patch-id: 542fe806b611c7cc8e2ded656b62731d25dcba9b
prerequisite-patch-id: 39d6ce2bdfc170b545eb2d275ab781e15c9e887a
prerequisite-patch-id: e5c55cb6181246d964bfa47b0bc494cf7703289c
prerequisite-patch-id: e56fbdef4bd6316e80e50ec8e8493ffb707dc76d
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/6] lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
2026-11-10 20:30 ` [PATCH 2/6] landlock: Restrict capabilities guarding other LSMs' policies Mickaël Salaün
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
AppArmor, Smack and SELinux check the capabilities that guard their own
policy (e.g. CAP_MAC_ADMIN, CAP_MAC_OVERRIDE, or CAP_SYS_PTRACE for
Smack's ptrace rule) with cap_capable(), usually followed by their own
internal check. They use cap_capable() instead of capable() on purpose:
other stacked LSMs should not have to authorize these capabilities,
which may mean something else for their security model. This also hides
these capability uses from LSMs that restrict capability use in general:
a Landlock domain that denies CAP_MAC_ADMIN does not prevent a process
from managing AppArmor policy.
Add security_capable_lsm() for these checks, and a capable_lsm hook that
it calls after cap_capable(). Only LSMs that implement this new hook
see these checks, so LSMs that only implement the capable hook are still
not asked about another LSM's policy, and the calling LSM's own capable
hook is not called. The hook gets the LSM_ID_* of the LSM whose policy
the capability guards, so that BPF LSM programs can tell these checks
apart, and so that an LSM calling the helper can recognize its own
checks. Landlock implements this hook in the next commit.
The new hook changes nothing for current users of the capable hook, and
only adds a new BPF LSM attach point. Calling every capable hook except
the caller's would not keep this property: BPF LSM programs attached to
the capable hook would start seeing, and could deny, these checks after
a kernel update, and the other MAC LSMs would be asked about another
LSM's policy again. A new CAP_OPT_* flag for these checks would have
the same effect on BPF LSM programs, and would require every MAC LSM to
explicitly ignore the flagged checks.
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Günther Noack <gnoack@google.com>
Cc: John Johansen <john.johansen@canonical.com>
Cc: KP Singh <kpsingh@kernel.org>
Cc: Matt Bobrowski <matt@bobrowski.net>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge Hallyn <serge@hallyn.com>
Cc: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
include/linux/lsm_hook_defs.h | 2 ++
include/linux/security.h | 9 +++++++
security/security.c | 47 +++++++++++++++++++++++++++++++++++
3 files changed, 58 insertions(+)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 87d9d06710cb..dc24e19f7eea 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -43,6 +43,8 @@ LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old,
const kernel_cap_t *permitted)
LSM_HOOK(int, 0, capable, const struct cred *cred, struct user_namespace *ns,
int cap, unsigned int opts)
+LSM_HOOK(int, 0, capable_lsm, const struct cred *cred,
+ struct user_namespace *ns, int cap, unsigned int opts, u64 lsm_id)
LSM_HOOK(int, 0, quotactl, int cmds, int type, int id, const struct super_block *sb)
LSM_HOOK(int, 0, quota_on, struct dentry *dentry)
LSM_HOOK(int, 0, syslog, int type)
diff --git a/include/linux/security.h b/include/linux/security.h
index 40831fab5470..1e89bc1fec17 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -352,6 +352,8 @@ int security_capable(const struct cred *cred,
struct user_namespace *ns,
int cap,
unsigned int opts);
+int security_capable_lsm(const struct cred *cred, struct user_namespace *ns,
+ int cap, unsigned int opts, u64 lsm_id);
int security_quotactl(int cmds, int type, int id, const struct super_block *sb);
int security_quota_on(struct dentry *dentry);
int security_syslog(int type);
@@ -710,6 +712,13 @@ static inline int security_capable(const struct cred *cred,
return cap_capable(cred, ns, cap, opts);
}
+static inline int security_capable_lsm(const struct cred *cred,
+ struct user_namespace *ns, int cap,
+ unsigned int opts, u64 lsm_id)
+{
+ return cap_capable(cred, ns, cap, opts);
+}
+
static inline int security_quotactl(int cmds, int type, int id,
const struct super_block *sb)
{
diff --git a/security/security.c b/security/security.c
index 4727b8347fc4..36365fac5801 100644
--- a/security/security.c
+++ b/security/security.c
@@ -664,6 +664,9 @@ int security_capset(struct cred *new, const struct cred *old,
* credentials. @cap contains the capability <include/linux/capability.h>.
* @opts contains options for the capable check <include/linux/security.h>.
*
+ * An LSM checking a capability that guards its own policy uses
+ * security_capable_lsm() instead.
+ *
* Return: Returns 0 if the capability is granted.
*/
int security_capable(const struct cred *cred,
@@ -674,6 +677,50 @@ int security_capable(const struct cred *cred,
return call_int_hook(capable, cred, ns, cap, opts);
}
+/**
+ * security_capable_lsm() - Check a capability that guards an LSM's policy
+ * @cred: credentials to examine
+ * @ns: user namespace
+ * @cap: capability requested
+ * @opts: capability check options
+ * @lsm_id: LSM_ID_* of the LSM whose policy the capability guards
+ *
+ * Check whether @cred has the @cap capability, when @cap guards the policy of
+ * the LSM identified by @lsm_id (e.g. CAP_MAC_ADMIN to manage this policy).
+ * @cap is first checked with commoncap, and then the LSMs implementing the
+ * capable_lsm hook may restrict this use of @cap. LSMs that only implement the
+ * capable hook are not asked, so they do not have to authorize capabilities
+ * that guard another LSM's policy.
+ *
+ * Callers: An LSM uses this function instead of cap_capable() or capable() for
+ * a capability that guards its own policy, with its own LSM_ID_* as @lsm_id,
+ * and then makes its own decision, without calling its own capable hook. This
+ * function must not be used for other capability checks.
+ *
+ * Implementations: A capable_lsm hook can only deny a capability that commoncap
+ * granted. It may be called in atomic context, including with interrupts
+ * disabled or from an interrupt handler (e.g. Smack's SIGIO check), and @cred
+ * might be another task's credentials (e.g. a ptrace tracer or a SIGIO
+ * recipient). An LSM that restricts capability use in general should implement
+ * both the capable and the capable_lsm hooks. This function calls every
+ * capable_lsm hook, so an LSM implementing this hook and calling this function
+ * should ignore checks for its own @lsm_id.
+ *
+ * Return: Returns 0 if the capability is granted, or a negative error code
+ * (e.g. -EPERM) otherwise.
+ */
+int security_capable_lsm(const struct cred *cred, struct user_namespace *ns,
+ int cap, unsigned int opts, u64 lsm_id)
+{
+ int rc;
+
+ rc = cap_capable(cred, ns, cap, opts);
+ if (rc)
+ return rc;
+
+ return call_int_hook(capable_lsm, cred, ns, cap, opts, lsm_id);
+}
+
/**
* security_quotactl() - Check if a quotactl() syscall is allowed for this fs
* @cmds: commands
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/6] landlock: Restrict capabilities guarding other LSMs' policies
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
2026-11-10 20:30 ` [PATCH 1/6] lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
2026-11-10 20:30 ` [PATCH 3/6] apparmor: Use security_capable_lsm() for policy capabilities Mickaël Salaün
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
A process in a domain that denies CAP_MAC_ADMIN or CAP_MAC_OVERRIDE
could still use them for AppArmor, Smack or SELinux policy, and
CAP_SYS_PTRACE for Smack's ptrace rule, because these LSMs check them
with cap_capable() instead of capable().
Implement the capable_lsm hook with the same check as the capable hook,
so that such capability uses are restricted like any other. The
following commits switch these LSMs to security_capable_lsm().
Cc: Christian Brauner <brauner@kernel.org>
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge Hallyn <serge@hallyn.com>
Fixes: 97c8386f1d74 ("landlock: Enforce capability restrictions")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/landlock/cap.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/security/landlock/cap.c b/security/landlock/cap.c
index c9a900888f3b..162edf689f29 100644
--- a/security/landlock/cap.c
+++ b/security/landlock/cap.c
@@ -91,6 +91,28 @@ static int hook_capable(const struct cred *cred, struct user_namespace *ns,
return check_cap(cred, cap, opts);
}
+/**
+ * hook_capable_lsm - Deny capabilities guarding another LSM's policy
+ *
+ * @cred: Credentials being checked.
+ * @ns: Target user namespace (intentionally ignored, see hook_capable()).
+ * @cap: Capability number (CAP_*).
+ * @opts: Capability check options, see check_cap().
+ * @lsm_id: LSM_ID_* of the LSM whose policy @cap guards (unused: Landlock does
+ * not call security_capable_lsm()).
+ *
+ * Other LSMs check the capabilities that guard their own policy (e.g.
+ * CAP_MAC_ADMIN) with security_capable_lsm() instead of capable(). Restrict
+ * them like any other capability use.
+ *
+ * Return: 0 if allowed, -EPERM if capability use is denied.
+ */
+static int hook_capable_lsm(const struct cred *cred, struct user_namespace *ns,
+ int cap, unsigned int opts, u64 lsm_id)
+{
+ return check_cap(cred, cap, opts);
+}
+
/**
* hook_capset - Restrict inheritable capability changes
*
@@ -163,6 +185,7 @@ static int hook_vm_enough_memory(struct mm_struct *mm, long pages)
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(capable, hook_capable),
+ LSM_HOOK_INIT(capable_lsm, hook_capable_lsm),
LSM_HOOK_INIT(capset, hook_capset),
LSM_HOOK_INIT(mmap_addr, hook_mmap_addr),
LSM_HOOK_INIT(vm_enough_memory, hook_vm_enough_memory),
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 3/6] apparmor: Use security_capable_lsm() for policy capabilities
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
2026-11-10 20:30 ` [PATCH 1/6] lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy Mickaël Salaün
2026-11-10 20:30 ` [PATCH 2/6] landlock: Restrict capabilities guarding other LSMs' policies Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
2026-11-10 20:30 ` [PATCH 4/6] smack: Use security_capable_lsm() for privilege checks Mickaël Salaün
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
AppArmor checks CAP_MAC_ADMIN for policy management and CAP_MAC_OVERRIDE
for unconfined stacking with cap_capable() instead of capable(), so that
other stacked LSMs do not have to authorize them. This also lets a
process in a Landlock domain that denies CAP_MAC_ADMIN manage AppArmor
policy, and one that denies CAP_MAC_OVERRIDE avoid the stacking
restriction on unconfined profile changes.
Commit 31ec99e13346 ("apparmor: switch to apparmor to internal capable
check for policy management") made this choice for CAP_MAC_ADMIN.
Switch these checks to security_capable_lsm(), which keeps this choice
for LSMs that only implement the capable hook. policy_ns_capable()
still makes its own aa_capable() check. Update the comments that
described these checks.
Cc: Georgia Garcia <georgia.garcia@canonical.com>
Cc: Günther Noack <gnoack@google.com>
Cc: John Johansen <john.johansen@canonical.com>
Cc: Paul Moore <paul@paul-moore.com>
Fixes: 97c8386f1d74 ("landlock: Enforce capability restrictions")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/apparmor/domain.c | 9 +++++----
security/apparmor/policy.c | 7 ++++---
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index af9e8431e3ea..d2c111783457 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -884,11 +884,12 @@ static bool is_profile_priv_restricted_to_stack(const struct cred *subj_cred,
{
if (profile_unconfined(profile) && profile == profile->ns->unconfined &&
aa_unprivileged_unconfined_restricted &&
- /* cap_capable returns false (0) if true, hence true here means
- * doesn't have capability and the stack will be restricted
+ /* security_capable_lsm() returns 0 if CAP_MAC_OVERRIDE is granted,
+ * otherwise the stack will be restricted
*/
- cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
- CAP_OPT_NOAUDIT))
+ security_capable_lsm(current_cred(), &init_user_ns,
+ CAP_MAC_OVERRIDE, CAP_OPT_NOAUDIT,
+ LSM_ID_APPARMOR))
return true;
return false;
}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 63f7821b4dff..ba162d921805 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -876,8 +876,8 @@ static int audit_policy(struct aa_label *subj_label, const char *op,
return error;
}
-/* don't call out to other LSMs in the stack for apparmor policy admin
- * permissions
+/* don't ask other LSMs' capable hooks about apparmor policy admin
+ * permissions, only LSMs restricting capability use through capable_lsm
*/
static int policy_ns_capable(const struct cred *subj_cred,
struct aa_label *label,
@@ -886,7 +886,8 @@ static int policy_ns_capable(const struct cred *subj_cred,
int err;
/* check for MAC_ADMIN cap in cred */
- err = cap_capable(subj_cred, userns, cap, CAP_OPT_NONE);
+ err = security_capable_lsm(subj_cred, userns, cap, CAP_OPT_NONE,
+ LSM_ID_APPARMOR);
if (!err)
err = aa_capable(subj_cred, label, cap, CAP_OPT_NONE);
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 4/6] smack: Use security_capable_lsm() for privilege checks
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
` (2 preceding siblings ...)
2026-11-10 20:30 ` [PATCH 3/6] apparmor: Use security_capable_lsm() for policy capabilities Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
2026-11-10 20:30 ` [PATCH 5/6] selinux: Use security_capable_lsm() for CAP_MAC_ADMIN checks Mickaël Salaün
2026-11-10 20:30 ` [PATCH 6/6] selftests/landlock: Test capabilities guarding other LSMs' policies Mickaël Salaün
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
smack_privileged_cred() checks the capabilities that guard Smack policy
(CAP_MAC_ADMIN to change it, CAP_MAC_OVERRIDE to bypass it, and
CAP_SYS_PTRACE for the ptrace rule) with cap_capable() instead of
capable(), so that other stacked LSMs are not asked (see commit
f28e783ff668 ("Smack: Use cap_capable in privilege check")). This also
lets a process in a Landlock domain that denies these capabilities
manage or bypass Smack policy.
Switch this check to security_capable_lsm(), as for AppArmor in the
previous commit. The onlycap rule still applies.
smack_privileged_cred() may check another task's credentials (the
tracer's for the ptrace rule, the SIGIO recipient's), and may run in
interrupt context for SIGIO delivery, which the capable_lsm hook allows.
Smack passes CAP_OPT_NONE everywhere, so Landlock logs every denial of
these capabilities like any other capability denial. This includes the
checks that some Smack hooks make before their label rules (e.g.
smack_syslog(), smack_unix_may_send()): the override is denied, and the
label rules then decide.
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Fixes: 97c8386f1d74 ("landlock: Enforce capability restrictions")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/smack/smack_access.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c
index fb85356266e5..7e282e0b716a 100644
--- a/security/smack/smack_access.c
+++ b/security/smack/smack_access.c
@@ -723,7 +723,8 @@ bool smack_privileged_cred(int cap, const struct cred *cred)
struct smack_known_list_elem *sklep;
int rc;
- rc = cap_capable(cred, &init_user_ns, cap, CAP_OPT_NONE);
+ rc = security_capable_lsm(cred, &init_user_ns, cap, CAP_OPT_NONE,
+ LSM_ID_SMACK);
if (rc)
return false;
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 5/6] selinux: Use security_capable_lsm() for CAP_MAC_ADMIN checks
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
` (3 preceding siblings ...)
2026-11-10 20:30 ` [PATCH 4/6] smack: Use security_capable_lsm() for privilege checks Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
2026-11-10 20:30 ` [PATCH 6/6] selftests/landlock: Test capabilities guarding other LSMs' policies Mickaël Salaün
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
has_cap_mac_admin() checks CAP_MAC_ADMIN with cap_capable() and then
with SELinux's own check, so that other stacked LSMs do not have to
authorize it (see commit db59000ab760 ("selinux: only invoke
capabilities and selinux for CAP_MAC_ADMIN checks")). This also lets a
process in a Landlock domain that denies CAP_MAC_ADMIN get or set
security contexts unknown to the loaded policy.
Switch this check to security_capable_lsm(), as for AppArmor and Smack
in the previous commits. The cred_has_capability() check is kept.
Cc: Günther Noack <gnoack@google.com>
Cc: Ondrej Mosnáček <omosnacek@gmail.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Stephen Smalley <stephen.smalley.work@gmail.com>
Fixes: 97c8386f1d74 ("landlock: Enforce capability restrictions")
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
security/selinux/hooks.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 9950f89dfaa2..5d85bc06f4c5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3363,7 +3363,8 @@ static bool has_cap_mac_admin(bool audit)
const struct cred *cred = current_cred();
unsigned int opts = audit ? CAP_OPT_NONE : CAP_OPT_NOAUDIT;
- if (cap_capable(cred, &init_user_ns, CAP_MAC_ADMIN, opts))
+ if (security_capable_lsm(cred, &init_user_ns, CAP_MAC_ADMIN, opts,
+ LSM_ID_SELINUX))
return false;
if (cred_has_capability(cred, CAP_MAC_ADMIN, opts, true))
return false;
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 6/6] selftests/landlock: Test capabilities guarding other LSMs' policies
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
` (4 preceding siblings ...)
2026-11-10 20:30 ` [PATCH 5/6] selinux: Use security_capable_lsm() for CAP_MAC_ADMIN checks Mickaël Salaün
@ 2026-11-10 20:30 ` Mickaël Salaün
5 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2026-11-10 20:30 UTC (permalink / raw)
To: Casey Schaufler, Georgia Garcia, Günther Noack,
James Morris, John Johansen, Ondrej Mosnáček,
Paul Moore, Serge Hallyn, Stephen Smalley
Cc: Mickaël Salaün, Christian Brauner, KP Singh,
Matt Bobrowski, apparmor, bpf, kernel-team, linux-kernel,
linux-security-module, selinux
Check that a domain denying CAP_MAC_ADMIN prevents a process from
changing a file's Smack label and from loading AppArmor policy. Also
check that denying CAP_MAC_OVERRIDE prevents a process from bypassing
Smack rules between sockets with distinct labels. The allowed,
unhandled and unsandboxed variants retain each capability, and an audit
test checks both Smack denials.
Smack and AppArmor are optional and are not added to the test
configuration: each test is skipped if its LSM is not active according
to lsm_list_modules(2), and fails if this syscall fails. The default
test configuration has no other LSM, so these tests skip there and the
capable_lsm hook is not exercised.
The cap_other_hooks fixture now covers more than commoncap's checks, so
update its description, and add CAP_MAC_ADMIN and CAP_MAC_OVERRIDE to
the capabilities that the tests may raise.
Without the previous commits, the denied variants and the audit test
fail with Smack (boot with lsm=landlock,smack), and the denied variants
fail with AppArmor (lsm=landlock,apparmor).
Test coverage for security/landlock is 91.7% of 2844 lines according to
LLVM 18.
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Günther Noack <gnoack@google.com>
Cc: John Johansen <john.johansen@canonical.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
tools/testing/selftests/landlock/cap_test.c | 230 +++++++++++++++++++-
tools/testing/selftests/landlock/common.h | 2 +
2 files changed, 227 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/landlock/cap_test.c b/tools/testing/selftests/landlock/cap_test.c
index 741567c5ab41..7f9b7fb2bade 100644
--- a/tools/testing/selftests/landlock/cap_test.c
+++ b/tools/testing/selftests/landlock/cap_test.c
@@ -10,12 +10,16 @@
#include <fcntl.h>
#include <linux/capability.h>
#include <linux/landlock.h>
+#include <linux/lsm.h>
+#include <linux/xattr.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
@@ -741,11 +745,12 @@ TEST(cap_target_userns_independent)
}
/*
- * Capability checks that commoncap makes from hooks other than the capable
- * hook. Each test first checks that the operation needs the capability, then
- * checks it with the capability effective. Variants cover an unsandboxed
- * baseline, a domain that denies the capability, a domain that allows it, and a
- * domain that does not handle capabilities.
+ * Capability checks that do not go through the capable hook: commoncap's checks
+ * from other hooks, and other LSMs' checks for their own policy. Each test
+ * first checks that the operation needs the capability, then checks it with the
+ * capability effective. Variants cover an unsandboxed baseline, a domain that
+ * denies the capability, a domain that allows it, and a domain that does not
+ * handle capabilities.
*/
/* clang-format off */
FIXTURE(cap_other_hooks) {};
@@ -969,6 +974,180 @@ TEST_F(cap_other_hooks, capset_inheritable)
clear_cap(_metadata, CAP_SETPCAP);
}
+/* Much more than the number of LSMs that can be active at the same time. */
+#define LSM_IDS_MAX 64
+
+static bool is_lsm_active(struct __test_metadata *const _metadata,
+ const __u64 lsm_id)
+{
+ __u64 ids[LSM_IDS_MAX];
+ __u32 size = sizeof(ids);
+ long count, i;
+
+ count = syscall(__NR_lsm_list_modules, ids, &size, 0);
+ ASSERT_LE(0, count)
+ {
+ TH_LOG("Failed to list LSMs: %s", strerror(errno));
+ }
+ for (i = 0; i < count; i++) {
+ if (ids[i] == lsm_id)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * The following tests check capabilities that guard another LSM's policy, which
+ * that LSM checks with security_capable_lsm(). These LSMs are optional, and
+ * are not in the test configuration: each test is skipped if its LSM is not
+ * active (e.g., boot with lsm=landlock,smack). Not tested here: SELinux only
+ * checks CAP_MAC_ADMIN this way when a policy is loaded, and AppArmor only
+ * checks CAP_MAC_OVERRIDE this way for unconfined profile changes when the
+ * kernel.apparmor_restrict_unprivileged_unconfined sysctl is set.
+ */
+
+/* Returns an FD to a new file on a private tmpfs. */
+static int create_private_file(struct __test_metadata *const _metadata)
+{
+ int fd;
+
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ ASSERT_EQ(0, unshare(CLONE_NEWNS));
+ ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+ ASSERT_EQ(0, mount("test", "/tmp", "tmpfs", 0, NULL));
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+ fd = open("/tmp/f", O_CREAT | O_RDWR | O_CLOEXEC, 0600);
+ ASSERT_LE(0, fd);
+ return fd;
+}
+
+static const char smack_out_label[] = "landlock-out";
+static const char smack_in_label[] = "landlock-in";
+
+/* Creates connected sockets whose Smack labels deny writes. */
+static void create_smack_socketpair(struct __test_metadata *const _metadata,
+ int socket_fds[2])
+{
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0,
+ socket_fds));
+ set_cap(_metadata, CAP_MAC_ADMIN);
+ ASSERT_EQ(0,
+ fsetxattr(socket_fds[0], XATTR_NAME_SMACKIPOUT,
+ smack_out_label, sizeof(smack_out_label) - 1, 0));
+ ASSERT_EQ(0, fsetxattr(socket_fds[1], XATTR_NAME_SMACKIPIN,
+ smack_in_label, sizeof(smack_in_label) - 1, 0));
+ clear_cap(_metadata, CAP_MAC_ADMIN);
+}
+
+/* Smack requires CAP_MAC_ADMIN to change a file's Smack label. */
+TEST_F(cap_other_hooks, smack_relabel)
+{
+ int fd;
+
+ if (!is_lsm_active(_metadata, LSM_ID_SMACK))
+ SKIP(return, "Smack is not active");
+
+ fd = create_private_file(_metadata);
+ enforce_cap_variant(_metadata, variant, CAP_MAC_ADMIN);
+
+ EXPECT_EQ(-1, fsetxattr(fd, "security.SMACK64", "_", 1, 0));
+ EXPECT_EQ(EPERM, errno);
+
+ set_cap(_metadata, CAP_MAC_ADMIN);
+ if (variant->is_allowed) {
+ EXPECT_EQ(0, fsetxattr(fd, "security.SMACK64", "_", 1, 0))
+ {
+ TH_LOG("Failed to set the Smack label: %s",
+ strerror(errno));
+ }
+ } else {
+ EXPECT_EQ(-1, fsetxattr(fd, "security.SMACK64", "_", 1, 0));
+ EXPECT_EQ(EPERM, errno);
+ }
+ clear_cap(_metadata, CAP_MAC_ADMIN);
+ EXPECT_EQ(0, close(fd));
+}
+
+/* CAP_MAC_OVERRIDE bypasses Smack label rules. */
+TEST_F(cap_other_hooks, smack_override)
+{
+ int socket_fds[2];
+
+ if (!is_lsm_active(_metadata, LSM_ID_SMACK))
+ SKIP(return, "Smack is not active");
+
+ create_smack_socketpair(_metadata, socket_fds);
+
+ /* The distinct Smack labels deny writes without an override. */
+ EXPECT_EQ(-1, send(socket_fds[0], "x", 1, 0));
+ EXPECT_EQ(EACCES, errno);
+
+ enforce_cap_variant(_metadata, variant, CAP_MAC_OVERRIDE);
+ set_cap(_metadata, CAP_MAC_OVERRIDE);
+ if (variant->is_allowed) {
+ EXPECT_EQ(1, send(socket_fds[0], "x", 1, 0));
+ } else {
+ EXPECT_EQ(-1, send(socket_fds[0], "x", 1, 0));
+ EXPECT_EQ(EACCES, errno);
+ }
+ clear_cap(_metadata, CAP_MAC_OVERRIDE);
+
+ EXPECT_EQ(0, close(socket_fds[0]));
+ EXPECT_EQ(0, close(socket_fds[1]));
+}
+
+#define APPARMOR_LOAD_PATH "/sys/kernel/security/apparmor/.load"
+
+/*
+ * Returns the errno of writing to AppArmor's policy load interface, or 0 on
+ * success. The written data is not a valid policy: a task that may not manage
+ * policy gets EACCES, and any other task gets EPROTONOSUPPORT.
+ */
+static int write_apparmor_load(struct __test_metadata *const _metadata)
+{
+ int fd, err = 0;
+
+ fd = open(APPARMOR_LOAD_PATH, O_WRONLY | O_CLOEXEC);
+ ASSERT_LE(0, fd)
+ {
+ TH_LOG("Failed to open %s: %s", APPARMOR_LOAD_PATH,
+ strerror(errno));
+ }
+ if (write(fd, "x", 1) < 0)
+ err = errno;
+ EXPECT_EQ(0, close(fd));
+ return err;
+}
+
+/* AppArmor requires CAP_MAC_ADMIN to load policy. */
+TEST_F(cap_other_hooks, apparmor_policy_load)
+{
+ if (!is_lsm_active(_metadata, LSM_ID_APPARMOR))
+ SKIP(return, "AppArmor is not active");
+
+ if (access(APPARMOR_LOAD_PATH, F_OK)) {
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ ASSERT_EQ(0, unshare(CLONE_NEWNS));
+ ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+ ASSERT_EQ(0, mount("securityfs", "/sys/kernel/security",
+ "securityfs", 0, NULL));
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+ }
+ ASSERT_EQ(0, access(APPARMOR_LOAD_PATH, F_OK));
+
+ enforce_cap_variant(_metadata, variant, CAP_MAC_ADMIN);
+
+ EXPECT_EQ(EACCES, write_apparmor_load(_metadata));
+
+ set_cap(_metadata, CAP_MAC_ADMIN);
+ if (variant->is_allowed) {
+ EXPECT_EQ(EPROTONOSUPPORT, write_apparmor_load(_metadata));
+ } else {
+ EXPECT_EQ(EACCES, write_apparmor_load(_metadata));
+ }
+ clear_cap(_metadata, CAP_MAC_ADMIN);
+}
+
/*
* With vm.overcommit_memory=2, cap_vm_enough_memory() lets CAP_SYS_ADMIN use
* the vm.admin_reserve_kbytes reserve. The test sets a commit limit that
@@ -1432,6 +1611,47 @@ TEST_F(cap_audit, other_hooks_allowed)
EXPECT_EQ(0, records.access);
}
+/* Denials in the capable_lsm hook are audited like capable hook denials. */
+TEST_F(cap_audit, smack)
+{
+ struct audit_records records = {};
+ int ruleset_fd, fd, socket_fds[2];
+ __u64 domain_id;
+
+ if (!is_lsm_active(_metadata, LSM_ID_SMACK))
+ SKIP(return, "Smack is not active");
+
+ fd = create_private_file(_metadata);
+ create_smack_socketpair(_metadata, socket_fds);
+ ruleset_fd = create_cap_ruleset();
+ ASSERT_LE(0, ruleset_fd);
+ /* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */
+ ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ set_cap(_metadata, CAP_MAC_ADMIN);
+ EXPECT_EQ(-1, fsetxattr(fd, "security.SMACK64", "_", 1, 0));
+ EXPECT_EQ(EPERM, errno);
+ clear_cap(_metadata, CAP_MAC_ADMIN);
+ EXPECT_EQ(0, close(fd));
+
+ set_cap(_metadata, CAP_MAC_OVERRIDE);
+ EXPECT_EQ(-1, send(socket_fds[0], "x", 1, 0));
+ EXPECT_EQ(EACCES, errno);
+ clear_cap(_metadata, CAP_MAC_OVERRIDE);
+ EXPECT_EQ(0, close(socket_fds[0]));
+ EXPECT_EQ(0, close(socket_fds[1]));
+
+ EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_MAC_ADMIN));
+ EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(),
+ &domain_id));
+ EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_MAC_OVERRIDE));
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
+}
+
TEST_F(cap_audit, allowed)
{
struct audit_records records = {};
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index a6fdfb422fad..77a8278fe89a 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -41,6 +41,8 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
/* clang-format off */
CAP_AUDIT_CONTROL,
CAP_DAC_OVERRIDE,
+ CAP_MAC_ADMIN,
+ CAP_MAC_OVERRIDE,
CAP_MKNOD,
CAP_NET_ADMIN,
CAP_NET_BIND_SERVICE,
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-10 20:31 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 20:30 [PATCH 0/6] Fix Landlock mediation of capabilities guarding LSM policies Mickaël Salaün
2026-11-10 20:30 ` [PATCH 1/6] lsm: Add a capable_lsm hook for capabilities guarding an LSM's policy Mickaël Salaün
2026-11-10 20:30 ` [PATCH 2/6] landlock: Restrict capabilities guarding other LSMs' policies Mickaël Salaün
2026-11-10 20:30 ` [PATCH 3/6] apparmor: Use security_capable_lsm() for policy capabilities Mickaël Salaün
2026-11-10 20:30 ` [PATCH 4/6] smack: Use security_capable_lsm() for privilege checks Mickaël Salaün
2026-11-10 20:30 ` [PATCH 5/6] selinux: Use security_capable_lsm() for CAP_MAC_ADMIN checks Mickaël Salaün
2026-11-10 20:30 ` [PATCH 6/6] selftests/landlock: Test capabilities guarding other LSMs' policies Mickaël Salaün
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®