mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-09-22 09:34:56 +02:00
landlock: Fix LOG_SUBDOMAINS_OFF inheritance across fork()
commit874c8f8382upstream. hook_cred_transfer() only copies the Landlock security blob when the source credential has a domain. This is inconsistent with landlock_restrict_self() which can set LOG_SUBDOMAINS_OFF on a credential without creating a domain (via the ruleset_fd=-1 path): the field is committed but not preserved across fork() because the child's prepare_creds() calls hook_cred_transfer() which skips the copy when domain is NULL. This breaks the documented use case where a process mutes subdomain logs before forking sandboxed children: the children lose the muting and their domains produce unexpected audit records. Fix this by unconditionally copying the Landlock credential blob. Cc: Günther Noack <gnoack@google.com> Cc: Jann Horn <jannh@google.com> Cc: stable@vger.kernel.org Fixes:ead9079f75("landlock: Add LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF") Reviewed-by: Günther Noack <gnoack3000@gmail.com> Link: https://lore.kernel.org/r/20260407164107.2012589-1-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
df865a3bb9
commit
2fcde49092
@@ -22,10 +22,8 @@ static void hook_cred_transfer(struct cred *const new,
|
||||
const struct landlock_cred_security *const old_llcred =
|
||||
landlock_cred(old);
|
||||
|
||||
if (old_llcred->domain) {
|
||||
landlock_get_ruleset(old_llcred->domain);
|
||||
*landlock_cred(new) = *old_llcred;
|
||||
}
|
||||
landlock_get_ruleset(old_llcred->domain);
|
||||
*landlock_cred(new) = *old_llcred;
|
||||
}
|
||||
|
||||
static int hook_cred_prepare(struct cred *const new,
|
||||
|
||||
@@ -279,6 +279,94 @@ TEST_F(audit, thread)
|
||||
&audit_tv_default, sizeof(audit_tv_default)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that log_subdomains_off set via the ruleset_fd=-1 path (without
|
||||
* creating a domain) is inherited by children across fork(). This exercises
|
||||
* the hook_cred_transfer() fix: the Landlock credential blob must be copied
|
||||
* even when the source credential has no domain.
|
||||
*
|
||||
* Phase 1 (baseline): a child without muting creates a domain and triggers a
|
||||
* denial that IS logged.
|
||||
*
|
||||
* Phase 2 (after muting): the parent mutes subdomain logs, forks another child
|
||||
* who creates a domain and triggers a denial that is NOT logged.
|
||||
*/
|
||||
TEST_F(audit, log_subdomains_off_fork)
|
||||
{
|
||||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.scoped = LANDLOCK_SCOPE_SIGNAL,
|
||||
};
|
||||
struct audit_records records;
|
||||
int ruleset_fd, status;
|
||||
pid_t child;
|
||||
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
|
||||
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
|
||||
|
||||
/*
|
||||
* Phase 1: forks a child that creates a domain and triggers a denial
|
||||
* before any muting. This proves the audit path works.
|
||||
*/
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
if (child == 0) {
|
||||
ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
|
||||
ASSERT_EQ(-1, kill(getppid(), 0));
|
||||
ASSERT_EQ(EPERM, errno);
|
||||
_exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_EQ(true, WIFEXITED(status));
|
||||
ASSERT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
/* The denial must be logged (baseline). */
|
||||
EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, getpid(),
|
||||
NULL));
|
||||
|
||||
/* Drains any remaining records (e.g. domain allocation). */
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
|
||||
/*
|
||||
* Mutes subdomain logs without creating a domain. The parent's
|
||||
* credential has domain=NULL and log_subdomains_off=1.
|
||||
*/
|
||||
ASSERT_EQ(0, landlock_restrict_self(
|
||||
-1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
|
||||
|
||||
/*
|
||||
* Phase 2: forks a child that creates a domain and triggers a denial.
|
||||
* Because log_subdomains_off was inherited via fork(), the child's
|
||||
* domain has log_status=LANDLOCK_LOG_DISABLED.
|
||||
*/
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
if (child == 0) {
|
||||
ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
|
||||
ASSERT_EQ(-1, kill(getppid(), 0));
|
||||
ASSERT_EQ(EPERM, errno);
|
||||
_exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_EQ(true, WIFEXITED(status));
|
||||
ASSERT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
/* No denial record should appear. */
|
||||
EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd,
|
||||
getpid(), NULL));
|
||||
|
||||
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
|
||||
EXPECT_EQ(0, records.access);
|
||||
|
||||
EXPECT_EQ(0, close(ruleset_fd));
|
||||
}
|
||||
|
||||
FIXTURE(audit_flags)
|
||||
{
|
||||
struct audit_filter audit_filter;
|
||||
|
||||
Reference in New Issue
Block a user