From: "Mickaël Salaün" <mic@digikod.net>
To: James Morris <jmorris@namei.org>,
Paul Moore <paul@paul-moore.com>, Serge Hallyn <serge@hallyn.com>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Ada Smith" <adasmithgt@gmail.com>,
"Christian Brauner" <brauner@kernel.org>,
"Günther Noack" <gnoack@google.com>,
"John Johansen" <john.johansen@canonical.com>,
"KP Singh" <kpsingh@kernel.org>,
"Matt Bobrowski" <matt@bobrowski.net>,
"Stephen Smalley" <stephen.smalley.work@gmail.com>,
apparmor@lists.ubuntu.com, bpf@vger.kernel.org,
kernel-team@cloudflare.com, linux-kernel@vger.kernel.org,
linux-security-module@vger.kernel.org, selinux@vger.kernel.org
Subject: [PATCH 2/2] selftests/landlock: Test PR_SET_SECUREBITS mediation
Date: Tue, 10 Nov 2026 21:28:07 +0100 [thread overview]
Message-ID: <20261110202811.2409395-3-mic@digikod.net> (raw)
In-Reply-To: <20261110202811.2409395-1-mic@digikod.net>
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
prev parent reply other threads:[~2026-11-10 20:28 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110202811.2409395-3-mic@digikod.net \
--to=mic@digikod.net \
--cc=adasmithgt@gmail.com \
--cc=apparmor@lists.ubuntu.com \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=gnoack@google.com \
--cc=jmorris@namei.org \
--cc=john.johansen@canonical.com \
--cc=kernel-team@cloudflare.com \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=matt@bobrowski.net \
--cc=paul@paul-moore.com \
--cc=selinux@vger.kernel.org \
--cc=serge@hallyn.com \
--cc=stephen.smalley.work@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®