* [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