From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-yw1-f170.google.com (mail-yw1-f170.google.com [209.85.128.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ECA7A31B80D for ; Fri, 31 Jul 2026 02:21:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785464485; cv=none; b=bizZjEfp8fDKaoOhDMgq31W8yTuYIHgrf9iHQ9pT61GnaCRkTnY74X79i4huVub6RJUJMzwkFYaWONblcXnIzIAYPVjWPqn2kZVC16RbBBDiLwMa/PDFPLfcDGaFcZeXWF8zGQIW+b51OE1wOk+sv8gCorMxscJiQNwjaNrUd4E= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785464485; c=relaxed/simple; bh=hSZLKL0ZVoziVn/VT3sa2EnQmfS/hzy/t7GIiq3s3qE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OhlDJ/sbve3jmSdIwzJEO90JOOqK8MO9imiiguLSsMUPsLF3F36KnfS0oV2WOspifcc2a56hcyGEKNk/Za3vcaS80AI1UhYfao209rshji2opUm30uemxYhJrIUqs26h1tYRH4lLvKZ45DWRHOAMyW/qVrFMxWtATyCK3Ve114U= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ldghJKV2; arc=none smtp.client-ip=209.85.128.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ldghJKV2" Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-81f3b227a4aso7492667b3.1 for ; Thu, 30 Jul 2026 19:21:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785464480; x=1786069280; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=E2YkARLK1UgWLJ9tWM7nw633P6+MEg0nLyubK9WrMGQ=; b=ldghJKV26wZ0KqFjxbLBm+5PmwfryPlwH/MfBCzS/rCpo24VQ75qn4TC3CZeAZ8O61 TeXUlgdCHwLDUyvhmH7en7lHbCRlwthc/Z6GY8PS0hbJFX6HT2sx+grL2hbfruAw58pu aE+bA5fiH6+PZV7o39kXO0fdHMkAi2p7ejks6pP6wBFZqvJuTPNvdOmow4ZkUpd6Tm9M Ifkzqpxyu84uSHKhUHmeMJbOS+YXFSIqfrl4saeif3T1cCbolaCbhpDX6k7HZWMoEk30 dtxHqiSdsDy9Z1U80sa1/rEEVqifbrU+/n3ryS/NygRYn43sxAi63Sh23qhu1I+vEOut sr1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785464480; x=1786069280; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=E2YkARLK1UgWLJ9tWM7nw633P6+MEg0nLyubK9WrMGQ=; b=CHH2bU+IsZZt3bNs8e5uLS+8Sb9XDmRE5zXH/YWPrSHSHZ/iauL2w62LrcBaBhAFx8 F9JRpFvXsf5NVbPrEdudcgIxYbMnm8uOPFQ70GJ/EWN6TXXzjKy7L/Mp/XAZkiOJHgbX tBqhBstU66i0tH0ch/yiqizd3BJ+RTMSfw2IYx5Y65xFbvK5ZAQVvX4txJe2DZYB25Kw 0Zv1aiT8NIN8y9BBn1YBKf+Jj6g5qHTlIs4lLizSsqyLIpMrHFTeQR68NJC9IectqVie sO3M9UsM5TjoSQ0chRX3ESRjPpcAwqe+N7oM9vWVQU1d4S2EXv6EtzxQu2NuKrcBTsKm X7vQ== X-Forwarded-Encrypted: i=1; AHgh+RoDoMWH5cQu5kznCthACXgFqKz3l9t85UlG4DOdOvel9bC+2bFh4jfouQMg3D8woFRmsQglK+yFQ4fsG9g=@vger.kernel.org X-Gm-Message-State: AOJu0Yzx86YIlXdNV93caISV6o93A1J0cYJ3MvYh9wnnQhCJ7XDa1qbB FiopQOaVc9haWBlm2WvbJi4paJ9xphcKVDjDKDdPl9JHNCRWixbZtq/P X-Gm-Gg: AR+sD137pobJgVYC+gyyOHDrJExQb1zF0CBNeAhYflcGymMYQqCDw75lNKgcsNALL3V sy0RJs+Axib9tYFka9/MVAVSeAvEGBwNKZtX/MrRCUttWnFGyQ3Vi2BmEXqbEOAHM989cp2SfFz fulncrX15KFeO/o4wL+c0ECKtklSWDfqa1oh2NlFZvQ7xW/E8+Hkp2ypqX8vppe+JP1EKN+R1Ut JOGhMGphG+0W8MyPY1rBPk/Cw1sngT0TuOMHrMGI0pMmn6YThBTUj5kLn0RBmIGRLWRSE8rACTg uITqK2ZJ97Hn/JqkSbCRy+JJkClq7brMBBxqxYRbzUO3PKgOZPDt/rRZw55BVBd9HsHyJnyD6nD P5XAB4MuysSgHGdM+eSf5MwVr0SC2mZpKRghrUWwRqCh+EzTm2WUYBRVeN1mRX5vPpeCVQVSU3B bQVTC/+2UhUhVPYd/hAMB6H4hK80VZ5pUoiv8er1sVxCMItm/NgZDm+aoYhXdpYWCUkgsjKpIG0 +06V6mdooDT+8o54/DnVA== X-Received: by 2002:a05:690c:660e:b0:80d:78bd:7a37 with SMTP id 00721157ae682-81fcbaff9ebmr585657b3.49.1785464479517; Thu, 30 Jul 2026 19:21:19 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:e94:8a83:feea:6720]) by smtp.gmail.com with ESMTPSA id 00721157ae682-81fb8b26e8bsm20519047b3.47.2026.07.30.19.21.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Jul 2026 19:21:19 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next 05/13] landlock: Factor the credential restriction out of landlock_restrict_self() Date: Thu, 30 Jul 2026 22:20:38 -0400 Message-ID: <20260731022047.189137-6-utilityemal77@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260731022047.189137-1-utilityemal77@gmail.com> References: <20260731022047.189137-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the core of landlock_restrict_self() into two credential helpers: landlock_prepare_restriction() - translate the landlock_restrict_self(2) flags, merge the ruleset with the credentials' domain, and configure the new domain's audit log state, producing a struct landlock_restriction: the complete new state that the enforcement gives to a credential. landlock_apply_restriction() - enforce a computed restriction on credentials exclusively owned by the caller. This step cannot fail, so a caller may run it past its last point of failure. The syscall behaves exactly as before: prepare and apply run back to back on the prepared credentials. The no_new_privs/CAP_SYS_ADMIN precheck, the flag mask check, and the TSYNC handling are syscall policy and stay in place. The point of the split is that application is decoupled from computation: a following commit restricts an execution from a BPF kfunc by staging a prepared restriction in the binprm credentials and applying it at the exec point of no return. With the flag translation, domain merge, and audit log configuration in one shared place. Cc: Mickaël Salaün Signed-off-by: Justin Suess --- security/landlock/cred.c | 100 +++++++++++++++++++++++++++++++++++ security/landlock/cred.h | 33 ++++++++++++ security/landlock/syscalls.c | 61 +++++---------------- 3 files changed, 147 insertions(+), 47 deletions(-) diff --git a/security/landlock/cred.c b/security/landlock/cred.c index cc419de75cd6..13b3952c31c5 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -8,14 +8,114 @@ */ #include +#include #include +#include +#include #include +#include #include "common.h" #include "cred.h" +#include "domain.h" #include "ruleset.h" #include "setup.h" +/** + * landlock_prepare_restriction - Compute a credential restriction + * + * @llcred: Landlock credentials to restrict: provides the parent domain and + * the previous log configuration. Not modified. + * @ruleset: Ruleset to enforce, or NULL for a log-configuration-only change. + * @flags: landlock_restrict_self(2) flags. The caller is responsible for + * validating them against the set of flags it supports. + * @restriction: Computed restriction. On success, holds a reference on + * @restriction->domain (if any), which + * landlock_apply_restriction() transfers to the restricted + * credentials. + * + * The restriction builds on @llcred's current state: the caller must apply + * it to (or stage it for) these same credentials. + * + * Return: 0 on success, -errno on failure. + */ +int landlock_prepare_restriction( + const struct landlock_cred_security *const llcred, + struct landlock_ruleset *const ruleset, const u32 flags, + struct landlock_restriction *const restriction) +{ +#ifdef CONFIG_AUDIT + /* Translates "off" and "on" flags to booleans. */ + const bool log_same_exec = + !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); + const bool log_new_exec = + !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); + const bool log_subdomains = + !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF); + const bool prev_log_subdomains = !llcred->log_subdomains_off; +#endif /* CONFIG_AUDIT */ + + *restriction = (struct landlock_restriction){}; + +#ifdef CONFIG_AUDIT + restriction->log_subdomains_off = !prev_log_subdomains || + !log_subdomains; +#endif /* CONFIG_AUDIT */ + + if (!ruleset) + return 0; + + restriction->domain = landlock_merge_ruleset(llcred->domain, ruleset); + if (IS_ERR(restriction->domain)) { + const int err = PTR_ERR(restriction->domain); + + restriction->domain = NULL; + return err; + } + +#ifdef CONFIG_AUDIT + restriction->domain->hierarchy->log_same_exec = log_same_exec; + restriction->domain->hierarchy->log_new_exec = log_new_exec; + if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) + restriction->domain->hierarchy->log_status = + LANDLOCK_LOG_DISABLED; +#endif /* CONFIG_AUDIT */ + + return 0; +} + +/** + * landlock_apply_restriction - Enforce a computed restriction on credentials + * + * @llcred: Landlock credentials to restrict, exclusively owned by the caller + * (prepared and not yet committed). + * @restriction: Restriction computed by landlock_prepare_restriction() + * against the same credential state; its domain reference is + * transferred to @llcred. + * + * Cannot fail, so that a caller may apply a restriction past its last point + * of failure, e.g. an exec point of no return. + */ +void landlock_apply_restriction(struct landlock_cred_security *const llcred, + struct landlock_restriction *const restriction) +{ +#ifdef CONFIG_AUDIT + llcred->log_subdomains_off = restriction->log_subdomains_off; +#endif /* CONFIG_AUDIT */ + + if (!restriction->domain) + return; + + /* Replaces the old domain. */ + landlock_put_ruleset(llcred->domain); + llcred->domain = restriction->domain; + restriction->domain = NULL; + +#ifdef CONFIG_AUDIT + llcred->domain_exec |= BIT(llcred->domain->num_layers - 1); +#endif /* CONFIG_AUDIT */ +} + static void hook_cred_transfer(struct cred *const new, const struct cred *const old) { diff --git a/security/landlock/cred.h b/security/landlock/cred.h index f287c56b5fd4..1d5039b46ce7 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -20,6 +20,31 @@ #include "ruleset.h" #include "setup.h" +/** + * struct landlock_restriction - Computed credential restriction + * + * The result of landlock_prepare_restriction(): the new state that + * enforcing a ruleset with a set of landlock_restrict_self(2) flags + * gives to a credential, decoupled from its application. It is + * enforced with landlock_apply_restriction(), either right away + * (landlock_restrict_self(2)) or after a staging period (restriction + * of an execution). + */ +struct landlock_restriction { + /** + * @domain: New domain to enforce, owning a reference. NULL if the + * restriction only carries a log configuration change. + */ + struct landlock_ruleset *domain; +#ifdef CONFIG_AUDIT + /** + * @log_subdomains_off: New value of the credentials' + * @landlock_cred_security.log_subdomains_off. + */ + u8 log_subdomains_off : 1; +#endif /* CONFIG_AUDIT */ +}; + /** * struct landlock_cred_security - Credential security blob * @@ -153,6 +178,14 @@ landlock_get_applicable_subject(const struct cred *const cred, return NULL; } +int landlock_prepare_restriction( + const struct landlock_cred_security *const llcred, + struct landlock_ruleset *const ruleset, const u32 flags, + struct landlock_restriction *const restriction); + +void landlock_apply_restriction(struct landlock_cred_security *const llcred, + struct landlock_restriction *const restriction); + __init void landlock_add_cred_hooks(void); #endif /* _SECURITY_LANDLOCK_CRED_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 9af2407274b2..899601af7c4e 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -528,9 +528,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, { struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL; struct cred *new_cred; - struct landlock_cred_security *new_llcred; - bool __maybe_unused log_same_exec, log_new_exec, log_subdomains, - prev_log_subdomains; + struct landlock_restriction restriction; + int err; if (!is_initialized()) return -EOPNOTSUPP; @@ -547,13 +546,6 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, LANDLOCK_MASK_RESTRICT_SELF) return -EINVAL; - /* Translates "off" flag to boolean. */ - log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); - /* Translates "on" flag to boolean. */ - log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); - /* Translates "off" flag to boolean. */ - log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF); - /* * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with * -1 as ruleset_fd, optionally combined with @@ -575,53 +567,28 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, if (!new_cred) return -ENOMEM; - new_llcred = landlock_cred(new_cred); - -#ifdef CONFIG_AUDIT - prev_log_subdomains = !new_llcred->log_subdomains_off; - new_llcred->log_subdomains_off = !prev_log_subdomains || - !log_subdomains; -#endif /* CONFIG_AUDIT */ - /* * The only case when a ruleset may not be set is if * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could * optimize this case by not calling commit_creds() if this flag was * already set, but it is not worth the complexity. + * + * There is no possible race condition while copying and manipulating + * the current credentials because they are dedicated per thread. */ - if (ruleset) { - /* - * There is no possible race condition while copying and - * manipulating the current credentials because they are - * dedicated per thread. - */ - struct landlock_ruleset *const new_dom = - landlock_merge_ruleset(new_llcred->domain, ruleset); - if (IS_ERR(new_dom)) { - abort_creds(new_cred); - return PTR_ERR(new_dom); - } - -#ifdef CONFIG_AUDIT - new_dom->hierarchy->log_same_exec = log_same_exec; - new_dom->hierarchy->log_new_exec = log_new_exec; - if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) - new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED; -#endif /* CONFIG_AUDIT */ - - /* Replaces the old (prepared) domain. */ - landlock_put_ruleset(new_llcred->domain); - new_llcred->domain = new_dom; - -#ifdef CONFIG_AUDIT - new_llcred->domain_exec |= BIT(new_dom->num_layers - 1); -#endif /* CONFIG_AUDIT */ + err = landlock_prepare_restriction(landlock_cred(new_cred), ruleset, + flags, &restriction); + if (err) { + abort_creds(new_cred); + return err; } + landlock_apply_restriction(landlock_cred(new_cred), &restriction); + if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { - const int err = landlock_restrict_sibling_threads( - current_cred(), new_cred); + err = landlock_restrict_sibling_threads(current_cred(), + new_cred); if (err) { abort_creds(new_cred); return err; -- 2.54.0