* [PATCH 0/2] Fix LSM mediation of PR_SET_SECUREBITS @ 2026-11-10 20:28 Mickaël Salaün 2026-11-10 20:28 ` [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() Mickaël Salaün 2026-11-10 20:28 ` [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation Mickaël Salaün 0 siblings, 2 replies; 6+ messages in thread From: Mickaël Salaün @ 2026-11-10 20:28 UTC (permalink / raw) To: James Morris, Paul Moore, Serge Hallyn Cc: Mickaël Salaün, Ada Smith, Christian Brauner, Günther Noack, John Johansen, KP Singh, Matt Bobrowski, Stephen Smalley, apparmor, bpf, kernel-team, linux-kernel, linux-security-module, selinux Hi, This is series 2 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 1: https://patch.msgid.link/20261110202436.2408244-1-mic@digikod.net Since per-process securebits were introduced, PR_SET_SECUREBITS checks CAP_SETPCAP with cap_capable() instead of ns_capable(), so LSMs restricting capabilities through the capable hook (SELinux, AppArmor, Landlock and BPF LSM programs) cannot deny it. A task with CAP_SETPCAP can therefore change privileged securebits, and thus how its capabilities evolve across UID changes and execve(), without the expected denial or audit record. Patch 1 uses ns_capable(), as PR_CAPBSET_DROP already does, for the changes that require CAP_SETPCAP; changes limited to the unprivileged securebits still do not check a capability. It applies cleanly to the 6.18 and 7.2 stable branches; older ones need a simpler adapted backport. Patch 2 adds a Landlock audit test that fails without patch 1. It shares cap_test.c with series 1, which is the only reason for the ordering. Serge and Paul, a review of the commoncap fix and an Acked-by for taking both patches through the Landlock tree would be welcome. John, Stephen, KP and Matt, please check the AppArmor, SELinux and BPF LSM implications of exposing this CAP_SETPCAP check to the capable hook. Regards, Mickaël Salaün (2): capabilities: Check PR_SET_SECUREBITS with ns_capable() selftests/landlock: Test PR_SET_SECUREBITS mediation security/commoncap.c | 42 ++++++------- tools/testing/selftests/landlock/cap_test.c | 69 +++++++++++++++++++++ 2 files changed, 89 insertions(+), 22 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 -- 2.56.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() 2026-11-10 20:28 [PATCH 0/2] Fix LSM mediation of PR_SET_SECUREBITS Mickaël Salaün @ 2026-11-10 20:28 ` Mickaël Salaün 2026-11-11 1:58 ` sashiko-bot 2026-11-12 13:02 ` Serge Hallyn (AMD) 2026-11-10 20:28 ` [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation Mickaël Salaün 1 sibling, 2 replies; 6+ messages in thread From: Mickaël Salaün @ 2026-11-10 20:28 UTC (permalink / raw) To: James Morris, Paul Moore, Serge Hallyn Cc: Mickaël Salaün, Ada Smith, Christian Brauner, Günther Noack, John Johansen, KP Singh, Matt Bobrowski, Stephen Smalley, apparmor, bpf, kernel-team, linux-kernel, linux-security-module, selinux, stable Per-process securebits were introduced with a direct cap_capable() check for CAP_SETPCAP. The capable LSM hook and SELinux capability policy already existed, and SELinux forwarded task_prctl operations to commoncap. The direct call therefore bypassed SELinux policy from the start. It now also bypasses AppArmor, Landlock, and BPF LSM programs attached to the capable hook. A task with CAP_SETPCAP in its effective set can consequently change privileged securebits despite an LSM denial. This can alter how its capabilities evolve across UID and execution transitions without the expected LSM denial or audit record. Use ns_capable(), as cap_prctl_drop() already does for PR_CAPBSET_DROP. Call it only for a no-op, which requires privilege for legacy reasons, or when a privileged bit changes. Changes limited to SECURE_ALL_UNPRIVILEGED and their lock bits do not need the capability, so they must not invoke an LSM check or set PF_SUPERPRIV. Successful privileged checks now set PF_SUPERPRIV, as other ns_capable() callers do. Without other LSMs, the same requests remain allowed as before. The check now reaches every capable hook before commoncap commits the new credentials. A later task_prctl hook cannot provide equivalent mediation because commoncap runs first and commits the change itself. Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: James Morris <jmorris@namei.org> 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> Cc: stable@vger.kernel.org Reported-by: Ada Smith <adasmithgt@gmail.com> Fixes: 3898b1b4ebff ("capabilities: implement per-process securebits") Signed-off-by: Mickaël Salaün <mic@digikod.net> --- security/commoncap.c | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/security/commoncap.c b/security/commoncap.c index d47ab3022343..1947128396e2 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -1283,6 +1283,23 @@ static int cap_prctl_drop(unsigned long cap) return commit_creds(new); } +/* + * Doing anything with securebits requires privilege (go read about the + * "sendmail capabilities bug"), except for unprivileged bits. Indeed, the + * SECURE_ALL_UNPRIVILEGED bits are not restrictions enforced by the kernel but + * by user space on itself. For legacy reasons, a non-change requires + * privilege too. Only checking CAP_SETPCAP when it is needed keeps LSMs from + * seeing (and logging) a capability check for unprivileged changes. + */ +static bool securebits_need_setpcap(unsigned long old, unsigned long new) +{ + const unsigned long unpriv_and_locks = SECURE_ALL_UNPRIVILEGED | + SECURE_ALL_UNPRIVILEGED << 1; + const unsigned long changed = old ^ new; + + return !changed || (changed & ~unpriv_and_locks); +} + /** * cap_task_prctl - Implement process control functions for this security module * @option: The process control function requested @@ -1346,28 +1363,9 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, /* cannot change a locked bit */ return -EPERM; - /* - * Doing anything requires privilege (go read about the - * "sendmail capabilities bug"), except for unprivileged bits. - * Indeed, the SECURE_ALL_UNPRIVILEGED bits are not - * restrictions enforced by the kernel but by user space on - * itself. - */ - if (cap_capable(current_cred(), current_cred()->user_ns, - CAP_SETPCAP, CAP_OPT_NONE) != 0) { - const unsigned long unpriv_and_locks = - SECURE_ALL_UNPRIVILEGED | - SECURE_ALL_UNPRIVILEGED << 1; - const unsigned long changed = old->securebits ^ arg2; - - /* For legacy reason, denies non-change. */ - if (!changed) - return -EPERM; - - /* Denies privileged changes. */ - if (changed & ~unpriv_and_locks) - return -EPERM; - } + if (securebits_need_setpcap(old->securebits, arg2) && + !ns_capable(current_user_ns(), CAP_SETPCAP)) + return -EPERM; new = prepare_creds(); if (!new) -- 2.56.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() 2026-11-10 20:28 ` [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() Mickaël Salaün @ 2026-11-11 1:58 ` sashiko-bot 2026-11-12 13:02 ` Serge Hallyn (AMD) 1 sibling, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-11 1:58 UTC (permalink / raw) Cc: linux-security-module, selinux > Per-process securebits were introduced with a direct cap_capable() check > for CAP_SETPCAP. The capable LSM hook and SELinux capability policy > already existed, and SELinux forwarded task_prctl operations to > commoncap. The direct call therefore bypassed SELinux policy from the > start. It now also bypasses AppArmor, Landlock, and BPF LSM programs > attached to the capable hook. > > A task with CAP_SETPCAP in its effective set can consequently change > privileged securebits despite an LSM denial. This can alter how its > capabilities evolve across UID and execution transitions without the > expected LSM denial or audit record. > > Use ns_capable(), as cap_prctl_drop() already does for PR_CAPBSET_DROP. > Call it only for a no-op, which requires privilege for legacy reasons, > or when a privileged bit changes. Changes limited to > [ ... ] > Cc: Stephen Smalley <stephen.smalley.work@gmail.com> > Cc: stable@vger.kernel.org > Reported-by: Ada Smith <adasmithgt@gmail.com> > Fixes: 3898b1b4ebff ("capabilities: implement per-process securebits") > Signed-off-by: Mickaël Salaün <mic@digikod.net> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261110202811.2409395-1-mic@digikod.net?part=1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() 2026-11-10 20:28 ` [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() Mickaël Salaün 2026-11-11 1:58 ` sashiko-bot @ 2026-11-12 13:02 ` Serge Hallyn (AMD) 1 sibling, 0 replies; 6+ messages in thread From: Serge Hallyn (AMD) @ 2026-11-12 13:02 UTC (permalink / raw) To: Mickaël Salaün Cc: James Morris, Paul Moore, Serge Hallyn, Ada Smith, Christian Brauner, Günther Noack, John Johansen, KP Singh, Matt Bobrowski, Stephen Smalley, apparmor, bpf, kernel-team, linux-kernel, linux-security-module, selinux, stable On Tue, Nov 10, 2026 at 09:28:06PM +0100, Mickaël Salaün wrote: > Per-process securebits were introduced with a direct cap_capable() check > for CAP_SETPCAP. The capable LSM hook and SELinux capability policy > already existed, and SELinux forwarded task_prctl operations to > commoncap. The direct call therefore bypassed SELinux policy from the > start. It now also bypasses AppArmor, Landlock, and BPF LSM programs > attached to the capable hook. > > A task with CAP_SETPCAP in its effective set can consequently change > privileged securebits despite an LSM denial. This can alter how its > capabilities evolve across UID and execution transitions without the > expected LSM denial or audit record. > > Use ns_capable(), as cap_prctl_drop() already does for PR_CAPBSET_DROP. > Call it only for a no-op, which requires privilege for legacy reasons, > or when a privileged bit changes. Changes limited to > SECURE_ALL_UNPRIVILEGED and their lock bits do not need the capability, > so they must not invoke an LSM check or set PF_SUPERPRIV. Successful > privileged checks now set PF_SUPERPRIV, as other ns_capable() callers > do. Without other LSMs, the same requests remain allowed as before. > > The check now reaches every capable hook before commoncap commits the > new credentials. A later task_prctl hook cannot provide equivalent > mediation because commoncap runs first and commits the change itself. > > Cc: Christian Brauner <brauner@kernel.org> > Cc: Günther Noack <gnoack@google.com> > Cc: James Morris <jmorris@namei.org> > 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> The commit message is a bit wordy. All it really needed to say was that 1. s/cap_capable/ns_capable to query other lsms, and 2. move the check to a concise helper. I really do not relish looking through a git log where every commit message re-litigates the full history of any code that is touched or moved. But it looks good, thanks. Reviewed-by: Serge Hallyn (AMD) <sergeh@kernel.org> > Cc: Stephen Smalley <stephen.smalley.work@gmail.com> > Cc: stable@vger.kernel.org > Reported-by: Ada Smith <adasmithgt@gmail.com> > Fixes: 3898b1b4ebff ("capabilities: implement per-process securebits") > Signed-off-by: Mickaël Salaün <mic@digikod.net> > --- > security/commoncap.c | 42 ++++++++++++++++++++---------------------- > 1 file changed, 20 insertions(+), 22 deletions(-) > > diff --git a/security/commoncap.c b/security/commoncap.c > index d47ab3022343..1947128396e2 100644 > --- a/security/commoncap.c > +++ b/security/commoncap.c > @@ -1283,6 +1283,23 @@ static int cap_prctl_drop(unsigned long cap) > return commit_creds(new); > } > > +/* > + * Doing anything with securebits requires privilege (go read about the > + * "sendmail capabilities bug"), except for unprivileged bits. Indeed, the > + * SECURE_ALL_UNPRIVILEGED bits are not restrictions enforced by the kernel but > + * by user space on itself. For legacy reasons, a non-change requires > + * privilege too. Only checking CAP_SETPCAP when it is needed keeps LSMs from > + * seeing (and logging) a capability check for unprivileged changes. > + */ > +static bool securebits_need_setpcap(unsigned long old, unsigned long new) > +{ > + const unsigned long unpriv_and_locks = SECURE_ALL_UNPRIVILEGED | > + SECURE_ALL_UNPRIVILEGED << 1; > + const unsigned long changed = old ^ new; > + FWIW, I think the code was clearer when the !changed was separate with a one line comment above it, than rolled into this return. > + return !changed || (changed & ~unpriv_and_locks); > +} > + > /** > * cap_task_prctl - Implement process control functions for this security module > * @option: The process control function requested > @@ -1346,28 +1363,9 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, > /* cannot change a locked bit */ > return -EPERM; > > - /* > - * Doing anything requires privilege (go read about the > - * "sendmail capabilities bug"), except for unprivileged bits. > - * Indeed, the SECURE_ALL_UNPRIVILEGED bits are not > - * restrictions enforced by the kernel but by user space on > - * itself. > - */ > - if (cap_capable(current_cred(), current_cred()->user_ns, > - CAP_SETPCAP, CAP_OPT_NONE) != 0) { > - const unsigned long unpriv_and_locks = > - SECURE_ALL_UNPRIVILEGED | > - SECURE_ALL_UNPRIVILEGED << 1; > - const unsigned long changed = old->securebits ^ arg2; > - > - /* For legacy reason, denies non-change. */ > - if (!changed) > - return -EPERM; > - > - /* Denies privileged changes. */ > - if (changed & ~unpriv_and_locks) > - return -EPERM; > - } > + if (securebits_need_setpcap(old->securebits, arg2) && > + !ns_capable(current_user_ns(), CAP_SETPCAP)) > + return -EPERM; > > new = prepare_creds(); > if (!new) > -- > 2.56.0 > ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation 2026-11-10 20:28 [PATCH 0/2] Fix LSM mediation of PR_SET_SECUREBITS Mickaël Salaün 2026-11-10 20:28 ` [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() Mickaël Salaün @ 2026-11-10 20:28 ` Mickaël Salaün 2026-11-11 2:06 ` sashiko-bot 1 sibling, 1 reply; 6+ messages in thread From: Mickaël Salaün @ 2026-11-10 20:28 UTC (permalink / raw) To: James Morris, Paul Moore, Serge Hallyn Cc: Mickaël Salaün, Ada Smith, Christian Brauner, Günther Noack, John Johansen, KP Singh, Matt Bobrowski, Stephen Smalley, apparmor, bpf, kernel-team, linux-kernel, linux-security-module, selinux The preceding commoncap fix routes the CAP_SETPCAP check for privileged PR_SET_SECUREBITS requests through the ordinary capable hooks. Add a focused Landlock regression test for this path. Keep CAP_SETPCAP effective to isolate Landlock mediation and run the audit case in a child domain. A privileged change returns EPERM and emits an exact CAP_SETPCAP record, then an unprivileged-only change succeeds. After the child exits, require the domain deallocation record to report exactly one denial; this proves that the second operation did not create a hidden denial without relying on an asynchronous no-record timeout. The existing check-exec securebits fixture already covers commoncap's privileged, unprivileged, locked, and user-namespace semantics. Without the preceding fix, this Landlock audit test fails. 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> Signed-off-by: Mickaël Salaün <mic@digikod.net> --- tools/testing/selftests/landlock/cap_test.c | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tools/testing/selftests/landlock/cap_test.c b/tools/testing/selftests/landlock/cap_test.c index c79bb42c6282..741567c5ab41 100644 --- a/tools/testing/selftests/landlock/cap_test.c +++ b/tools/testing/selftests/landlock/cap_test.c @@ -1275,6 +1275,75 @@ TEST_F(cap_audit, denied) EXPECT_EQ(0, records.domain); } +/* + * A privileged denial allocates the domain ID. The following unprivileged-only + * change must not create a hidden Landlock denial, so the domain's deallocation + * record must report exactly one denial. + */ +TEST_F(cap_audit, securebits) +{ + const unsigned long unpriv = SECBIT_EXEC_RESTRICT_FILE | + SECBIT_EXEC_RESTRICT_FILE_LOCKED; + __u64 *domain_id, deallocated_dom = 0; + unsigned long bits; + int err, ruleset_fd, status; + pid_t child; + + domain_id = mmap(NULL, sizeof(*domain_id), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, domain_id); + *domain_id = 0; + + ruleset_fd = create_cap_ruleset(); + ASSERT_LE(0, ruleset_fd); + /* Allow the child to replace the audit filter before exiting. */ + ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + set_cap(_metadata, CAP_SETPCAP); + bits = prctl(PR_GET_SECUREBITS); + EXPECT_EQ(-1, prctl(PR_SET_SECUREBITS, + bits | SECBIT_NO_CAP_AMBIENT_RAISE)); + EXPECT_EQ(EPERM, errno); + + EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SETPCAP)); + EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, + getpid(), domain_id)); + + EXPECT_EQ(0, prctl(PR_SET_SECUREBITS, bits | unpriv)); + clear_cap(_metadata, CAP_SETPCAP); + + /* Receive the asynchronous domain deallocation record. */ + set_cap(_metadata, CAP_AUDIT_CONTROL); + EXPECT_EQ(0, audit_filter_drop(self->audit_fd, AUDIT_ADD_RULE)); + EXPECT_EQ(0, + audit_filter_exe(self->audit_fd, &self->audit_filter, + AUDIT_DEL_RULE)); + clear_cap(_metadata, CAP_AUDIT_CONTROL); + + _exit(_metadata->exit_code); + return; + } + + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; + + EXPECT_NE(0, *domain_id); + err = matches_log_domain_deallocated(self->audit_fd, 1, *domain_id, + &deallocated_dom); + EXPECT_EQ(0, err); + EXPECT_EQ(*domain_id, deallocated_dom); + EXPECT_EQ(0, munmap(domain_id, sizeof(*domain_id))); + EXPECT_EQ(0, close(ruleset_fd)); +} + /* * Denials in the capset and mmap_addr paths are audited like capable hook * denials. The vm_enough_memory hook only withholds the admin reserve, with -- 2.56.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation 2026-11-10 20:28 ` [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation Mickaël Salaün @ 2026-11-11 2:06 ` sashiko-bot 0 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-11 2:06 UTC (permalink / raw) Cc: linux-security-module, selinux > The preceding commoncap fix routes the CAP_SETPCAP check for privileged > PR_SET_SECUREBITS requests through the ordinary capable hooks. Add a > focused Landlock regression test for this path. > > Keep CAP_SETPCAP effective to isolate Landlock mediation and run the > audit case in a child domain. A privileged change returns EPERM and > emits an exact CAP_SETPCAP record, then an unprivileged-only change > succeeds. After the child exits, require the domain deallocation record > to report exactly one denial; this proves that the second operation did > not create a hidden denial without relying on an asynchronous no-record > timeout. > > The existing check-exec securebits fixture already covers commoncap's > privileged, unprivileged, locked, and user-namespace semantics. Without > the preceding fix, this Landlock audit test fails. > > 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> > Signed-off-by: Mickaël Salaün <mic@digikod.net> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261110202811.2409395-1-mic@digikod.net?part=2 ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-12 13:03 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-10 20:28 [PATCH 0/2] Fix LSM mediation of PR_SET_SECUREBITS Mickaël Salaün 2026-11-10 20:28 ` [PATCH 1/2] capabilities: Check PR_SET_SECUREBITS with ns_capable() Mickaël Salaün 2026-11-11 1:58 ` sashiko-bot 2026-11-12 13:02 ` Serge Hallyn (AMD) 2026-11-10 20:28 ` [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation Mickaël Salaün 2026-11-11 2:06 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox