* Re: [PATCH v2] memfd,selinux: call security_inode_init_security_anon
From: Stephen Smalley @ 2025-09-08 16:27 UTC (permalink / raw)
To: Thiébaud Weksteen
Cc: Paul Moore, James Morris, Hugh Dickins, Jeff Vander Stoep,
Nick Kralevich, Jeff Xu, Baolin Wang, Isaac Manjarres,
linux-kernel, linux-security-module, selinux, linux-mm
In-Reply-To: <20250908013419.4186627-1-tweek@google.com>
On Sun, Sep 7, 2025 at 9:34 PM Thiébaud Weksteen <tweek@google.com> wrote:
>
> Prior to this change, no security hooks were called at the creation of a
> memfd file. It means that, for SELinux as an example, it will receive
> the default type of the filesystem that backs the in-memory inode. In
> most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> be hugetlbfs. Both can be considered implementation details of memfd.
>
> It also means that it is not possible to differentiate between a file
> coming from memfd_create and a file coming from a standard tmpfs mount
> point.
>
> Additionally, no permission is validated at creation, which differs from
> the similar memfd_secret syscall.
>
> Call security_inode_init_security_anon during creation. This ensures
> that the file is setup similarly to other anonymous inodes. On SELinux,
> it means that the file will receive the security context of its task.
>
> The ability to limit fexecve on memfd has been of interest to avoid
> potential pitfalls where /proc/self/exe or similar would be executed
> [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> similarly to the file class. These access vectors may not make sense for
> the existing "anon_inode" class. Therefore, define and assign a new
> class "memfd_file" to support such access vectors.
>
> Guard these changes behind a new policy capability named "memfd_class".
>
> [1] https://crbug.com/1305267
> [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
>
> Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
When you revise a patch, you aren't supposed to retain other's tags
since they haven't technically reviewed, agreed to, or tested the
revised change.
That said, I have now done so and thus these tags can remain!
> ---
> Changes since v1:
> - Move test of class earlier in selinux_bprm_creds_for_exec
> - Remove duplicate call to security_transition_sid
>
> Changes since RFC:
> - Remove enum argument, simply compare the anon inode name
> - Introduce a policy capability for compatility
> - Add validation of class in selinux_bprm_creds_for_exec
>
> include/linux/memfd.h | 2 ++
> mm/memfd.c | 14 ++++++++++--
> security/selinux/hooks.c | 26 +++++++++++++++++-----
> security/selinux/include/classmap.h | 2 ++
> security/selinux/include/policycap.h | 1 +
> security/selinux/include/policycap_names.h | 1 +
> security/selinux/include/security.h | 5 +++++
> 7 files changed, 44 insertions(+), 7 deletions(-)
>
> diff --git a/include/linux/memfd.h b/include/linux/memfd.h
> index 6f606d9573c3..cc74de3dbcfe 100644
> --- a/include/linux/memfd.h
> +++ b/include/linux/memfd.h
> @@ -4,6 +4,8 @@
>
> #include <linux/file.h>
>
> +#define MEMFD_ANON_NAME "[memfd]"
> +
> #ifdef CONFIG_MEMFD_CREATE
> extern long memfd_fcntl(struct file *file, unsigned int cmd, unsigned int arg);
> struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx);
> diff --git a/mm/memfd.c b/mm/memfd.c
> index bbe679895ef6..63b439eb402a 100644
> --- a/mm/memfd.c
> +++ b/mm/memfd.c
> @@ -433,6 +433,8 @@ static struct file *alloc_file(const char *name, unsigned int flags)
> {
> unsigned int *file_seals;
> struct file *file;
> + struct inode *inode;
> + int err = 0;
>
> if (flags & MFD_HUGETLB) {
> file = hugetlb_file_setup(name, 0, VM_NORESERVE,
> @@ -444,12 +446,20 @@ static struct file *alloc_file(const char *name, unsigned int flags)
> }
> if (IS_ERR(file))
> return file;
> +
> + inode = file_inode(file);
> + err = security_inode_init_security_anon(inode,
> + &QSTR(MEMFD_ANON_NAME), NULL);
> + if (err) {
> + fput(file);
> + file = ERR_PTR(err);
> + return file;
> + }
> +
> file->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
> file->f_flags |= O_LARGEFILE;
>
> if (flags & MFD_NOEXEC_SEAL) {
> - struct inode *inode = file_inode(file);
> -
> inode->i_mode &= ~0111;
> file_seals = memfd_file_seals_ptr(file);
> if (file_seals) {
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index c95a5874bf7d..6adf2f393ed9 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -93,6 +93,7 @@
> #include <linux/fanotify.h>
> #include <linux/io_uring/cmd.h>
> #include <uapi/linux/lsm.h>
> +#include <linux/memfd.h>
>
> #include "avc.h"
> #include "objsec.h"
> @@ -2315,6 +2316,9 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> new_tsec = selinux_cred(bprm->cred);
> isec = inode_security(inode);
>
> + if (isec->sclass != SECCLASS_FILE && isec->sclass != SECCLASS_MEMFD_FILE)
> + return -EPERM;
> +
> /* Default to the current task SID. */
> new_tsec->sid = old_tsec->sid;
> new_tsec->osid = old_tsec->sid;
> @@ -2366,9 +2370,10 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> ad.type = LSM_AUDIT_DATA_FILE;
> ad.u.file = bprm->file;
>
> +
> if (new_tsec->sid == old_tsec->sid) {
> - rc = avc_has_perm(old_tsec->sid, isec->sid,
> - SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
> + rc = avc_has_perm(old_tsec->sid, isec->sid, isec->sclass,
> + FILE__EXECUTE_NO_TRANS, &ad);
> if (rc)
> return rc;
> } else {
> @@ -2378,8 +2383,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> if (rc)
> return rc;
>
> - rc = avc_has_perm(new_tsec->sid, isec->sid,
> - SECCLASS_FILE, FILE__ENTRYPOINT, &ad);
> + rc = avc_has_perm(new_tsec->sid, isec->sid, isec->sclass,
> + FILE__ENTRYPOINT, &ad);
> if (rc)
> return rc;
>
> @@ -2974,10 +2979,18 @@ static int selinux_inode_init_security_anon(struct inode *inode,
> struct common_audit_data ad;
> struct inode_security_struct *isec;
> int rc;
> + bool is_memfd = false;
>
> if (unlikely(!selinux_initialized()))
> return 0;
>
> + if (name != NULL && name->name != NULL &&
> + !strcmp(name->name, MEMFD_ANON_NAME)) {
> + if (!selinux_policycap_memfd_class())
> + return 0;
> + is_memfd = true;
> + }
> +
> isec = selinux_inode(inode);
>
> /*
> @@ -2997,7 +3010,10 @@ static int selinux_inode_init_security_anon(struct inode *inode,
> isec->sclass = context_isec->sclass;
> isec->sid = context_isec->sid;
> } else {
> - isec->sclass = SECCLASS_ANON_INODE;
> + if (is_memfd)
> + isec->sclass = SECCLASS_MEMFD_FILE;
> + else
> + isec->sclass = SECCLASS_ANON_INODE;
> rc = security_transition_sid(
> sid, sid,
> isec->sclass, name, &isec->sid);
> diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
> index 5665aa5e7853..3ec85142771f 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -179,6 +179,8 @@ const struct security_class_mapping secclass_map[] = {
> { "anon_inode", { COMMON_FILE_PERMS, NULL } },
> { "io_uring", { "override_creds", "sqpoll", "cmd", "allowed", NULL } },
> { "user_namespace", { "create", NULL } },
> + { "memfd_file",
> + { COMMON_FILE_PERMS, "execute_no_trans", "entrypoint", NULL } },
> /* last one */ { NULL, {} }
> };
>
> diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h
> index 7405154e6c42..dabcc9f14dde 100644
> --- a/security/selinux/include/policycap.h
> +++ b/security/selinux/include/policycap.h
> @@ -17,6 +17,7 @@ enum {
> POLICYDB_CAP_NETLINK_XPERM,
> POLICYDB_CAP_NETIF_WILDCARD,
> POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
> + POLICYDB_CAP_MEMFD_CLASS,
> __POLICYDB_CAP_MAX
> };
> #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
> diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h
> index d8962fcf2ff9..8e96f2a816b6 100644
> --- a/security/selinux/include/policycap_names.h
> +++ b/security/selinux/include/policycap_names.h
> @@ -20,6 +20,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
> "netlink_xperm",
> "netif_wildcard",
> "genfs_seclabel_wildcard",
> + "memfd_class",
> };
> /* clang-format on */
>
> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> index 8201e6a3ac0f..72c963f54148 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -209,6 +209,11 @@ static inline bool selinux_policycap_netif_wildcard(void)
> selinux_state.policycap[POLICYDB_CAP_NETIF_WILDCARD]);
> }
>
> +static inline bool selinux_policycap_memfd_class(void)
> +{
> + return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
> +}
> +
> struct selinux_policy_convert_data;
>
> struct selinux_load_state {
> --
> 2.51.0.384.g4c02a37b29-goog
>
^ permalink raw reply
* Re: [PATCH v2] memfd,selinux: call security_inode_init_security_anon
From: Stephen Smalley @ 2025-09-08 16:32 UTC (permalink / raw)
To: Thiébaud Weksteen
Cc: Paul Moore, James Morris, Hugh Dickins, Jeff Vander Stoep,
Nick Kralevich, Jeff Xu, Baolin Wang, Isaac Manjarres,
linux-kernel, linux-security-module, selinux, linux-mm
In-Reply-To: <20250908013419.4186627-1-tweek@google.com>
On Sun, Sep 7, 2025 at 9:34 PM Thiébaud Weksteen <tweek@google.com> wrote:
>
> Prior to this change, no security hooks were called at the creation of a
> memfd file. It means that, for SELinux as an example, it will receive
> the default type of the filesystem that backs the in-memory inode. In
> most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> be hugetlbfs. Both can be considered implementation details of memfd.
>
> It also means that it is not possible to differentiate between a file
> coming from memfd_create and a file coming from a standard tmpfs mount
> point.
>
> Additionally, no permission is validated at creation, which differs from
> the similar memfd_secret syscall.
>
> Call security_inode_init_security_anon during creation. This ensures
> that the file is setup similarly to other anonymous inodes. On SELinux,
> it means that the file will receive the security context of its task.
>
> The ability to limit fexecve on memfd has been of interest to avoid
> potential pitfalls where /proc/self/exe or similar would be executed
> [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> similarly to the file class. These access vectors may not make sense for
> the existing "anon_inode" class. Therefore, define and assign a new
> class "memfd_file" to support such access vectors.
>
> Guard these changes behind a new policy capability named "memfd_class".
>
> [1] https://crbug.com/1305267
> [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
>
> Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
> Changes since v1:
> - Move test of class earlier in selinux_bprm_creds_for_exec
> - Remove duplicate call to security_transition_sid
>
> Changes since RFC:
> - Remove enum argument, simply compare the anon inode name
> - Introduce a policy capability for compatility
> - Add validation of class in selinux_bprm_creds_for_exec
>
> include/linux/memfd.h | 2 ++
> mm/memfd.c | 14 ++++++++++--
> security/selinux/hooks.c | 26 +++++++++++++++++-----
> security/selinux/include/classmap.h | 2 ++
> security/selinux/include/policycap.h | 1 +
> security/selinux/include/policycap_names.h | 1 +
> security/selinux/include/security.h | 5 +++++
> 7 files changed, 44 insertions(+), 7 deletions(-)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index c95a5874bf7d..6adf2f393ed9 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2315,6 +2316,9 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> new_tsec = selinux_cred(bprm->cred);
> isec = inode_security(inode);
>
> + if (isec->sclass != SECCLASS_FILE && isec->sclass != SECCLASS_MEMFD_FILE)
> + return -EPERM;
> +
Sorry, I should have mentioned this earlier, but usually we try to
avoid triggering silent denials from SELinux since it provides no hint
to the user as to what went wrong or how to resolve.
Arguably reaching this code would be suggestive of a kernel bug but I
know that BUG_ON() is frowned upon these days.
Maybe we should WARN_ON_ONCE() here or similar? We also rarely return
-EPERM from SELinux outside of capability checks since usually EPERM
means a failed capability check
(vs -EACCES). Defer to Paul on how/if he wants to handle this and
whether it requires re-spinning this patch or just a follow-on one.
> /* Default to the current task SID. */
> new_tsec->sid = old_tsec->sid;
> new_tsec->osid = old_tsec->sid;
^ permalink raw reply
* Re: [PATCH] ima: don't clear IMA_DIGSIG flag when setting non-IMA xattr
From: Mimi Zohar @ 2025-09-08 20:58 UTC (permalink / raw)
To: Coiby Xu
Cc: linux-integrity, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
Paul Moore, James Morris, Serge E. Hallyn,
open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <13d7fcfecb06423294ae0553c9a561f4cc8faf67.camel@linux.ibm.com>
On Mon, 2025-09-08 at 10:53 -0400, Mimi Zohar wrote:
> Hi Coiby,
>
> On Mon, 2025-09-08 at 19:12 +0800, Coiby Xu wrote:
> > >
> > > Even without an IMA appraise policy, the security xattrs are written out to the
> > > filesystem, but the IMA_DIGSIG flag is not cached.
> >
> > It seems I miss some context for the above sentence. If no IMA policy is
> > configured, no ima_iint_cache will be created. If you mean non-appraisal
> > policy, will not caching IMA_DIGSIG flag cause any problem?
>
> Sorry. What I was trying to say is that your test program illustrates the
> problem both with or without any of the boot command line options as you
> suggested - "ima_appraise=fix evm=fix ima_policy=appraise_tcb". Writing some
> other security xattr is a generic problem, whether the file is in policy or not,
> whether IMA or EVM are in fix mode or not. The rpm-plugin-ima should install
> the IMA signature regardless.
My mistake. An appraise policy indeed needs to be defined for the file
signature to be replaced with a file hash.
>
> SELinux doesn't usually re-write the security.selinux xattr, so the problem is
> hard to reproduce after installing the rpm-plugin-ima with "dnf reinstall
> <package>".
>
> thanks,
>
> Mimi
>
^ permalink raw reply
* Re: [PATCH] ima,evm: move initcalls to the LSM framework
From: Paul Moore @ 2025-09-08 21:04 UTC (permalink / raw)
To: Roberto Sassu, roberto.sassu
Cc: Mimi Zohar, linux-security-module, linux-integrity, selinux,
john.johansen, wufan, mic, kees, mortonm, casey, penguin-kernel,
nicolas.bouchinet, xiujianfeng
In-Reply-To: <0cccd05c0005d3b9e85ab92e35363cc69ea6a3f2.camel@linux.ibm.com>
On Sun, Sep 7, 2025 at 10:46 PM Mimi Zohar <zohar@linux.ibm.com> wrote:
> On Sun, 2025-09-07 at 21:08 -0400, Paul Moore wrote:
> > On Sun, Sep 7, 2025 at 5:18 PM Mimi Zohar <zohar@linux.ibm.com> wrote:
> > > On Tue, 2025-09-02 at 14:54 +0200, Roberto Sassu wrote:
> > > > From: Paul Moore <paul@paul-moore.com>
> > >
> > > Remove above ...
> > >
> > > >
> > > > This patch converts IMA and EVM to use the LSM frameworks's initcall
> > > > mechanism. It moved the integrity_fs_init() call to ima_fs_init() and
> > > > evm_init_secfs(), to work around the fact that there is no "integrity" LSM,
> > > > and introduced integrity_fs_fini() to remove the integrity directory, if
> > > > empty. Both integrity_fs_init() and integrity_fs_fini() support the
> > > > scenario of being called by both the IMA and EVM LSMs.
> > > >
> > > > It is worth mentioning that this patch does not touch any of the
> > > > "platform certs" code that lives in the security/integrity/platform_certs
> > > > directory as the IMA/EVM maintainers have assured me that this code is
> > > > unrelated to IMA/EVM, despite the location, and will be moved to a more
> > >
> > > This wording "unrelated to IMA/EVM" was taken from Paul's patch description, but
> > > needs to be tweaked. Please refer to my comment on Paul's patch.
> >
> > Minim, Roberto, would both of you be okay if I changed the second
> > paragraph to read as follows:
> >
> > "This patch does not touch any of the platform certificate code that
> > lives under the security/integrity/platform_certs directory as the
> > IMA/EVM developers would prefer to address that in a future patchset."
>
> That's fine.
Roberto, is it okay if I update your patch with the text above and use
it to replace my IMA/EVM patch in the LSM init patchset? I'll retain
your From/Sign-off of course.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v3 29/34] apparmor: move initcalls to the LSM framework
From: Paul Moore @ 2025-09-08 21:12 UTC (permalink / raw)
To: John Johansen
Cc: linux-security-module, linux-integrity, selinux, Mimi Zohar,
Roberto Sassu, Fan Wu, Mickaël Salaün,
Günther Noack, Kees Cook, Micah Morton, Casey Schaufler,
Tetsuo Handa, Nicolas Bouchinet, Xiu Jianfeng
In-Reply-To: <e1ed0bc4-136b-4e46-b030-1159411d6240@canonical.com>
On Mon, Sep 8, 2025 at 3:12 AM John Johansen
<john.johansen@canonical.com> wrote:
> On 8/14/25 15:50, Paul Moore wrote:
> > Reviewed-by: Kees Cook <kees@kernel.org>
> > Signed-off-by: Paul Moore <paul@paul-moore.com>
>
> Sorry this took so long.
> So the patch itself looks good, and can have my
> Acked-by: John Johansen <john.johansen@canonical.com>
No worries, thanks.
> with that said I have done 3 different builds with this series. Tweaking the base,
> and the config, and I haven't been able to successfully booted any of them. I am
> not sure what I am missing yet. I working on a bisect, but its just a side project
> atm.
>
> Until I can get a successful boot, and test. I am going to refrain from finishing
> out the review.
It obviously built, booted, and worked for me, but as I mentioned in
the other mail to Mimi, that was some weeks ago so it's possible that
there is an issue with this patchset and one of the latest tagged
-rc's from Linus. Once the IMA/EVM patch is sorted out, which I
expect to happen in a day or two, I'll go ahead and do another post of
the patchset (complete with the usual testing beforehand) and when I
do I'll make a full tree available in the cover letter for people to
review/test/etc. in a known working context.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v3 31/34] ima,evm: move initcalls to the LSM framework
From: Mimi Zohar @ 2025-09-08 22:33 UTC (permalink / raw)
To: Paul Moore
Cc: Roberto Sassu, linux-security-module, linux-integrity, selinux,
John Johansen, Fan Wu, Mickaël Salaün,
Günther Noack, Kees Cook, Micah Morton, Casey Schaufler,
Tetsuo Handa, Nicolas Bouchinet, Xiu Jianfeng
In-Reply-To: <CAHC9VhTJnQ3EggEXwbW5D8xOnb+Z_02yz-Dgb7QiAoArhw1ETg@mail.gmail.com>
On Sun, 2025-09-07 at 21:05 -0400, Paul Moore wrote:
> > The "unrelated to IMA/EVM" wording misses the point. An exception was made to
> > load the pre-boot keys onto the .platform keyring in order for IMA/EVM to verify
> > the kexec kernel image appended signature. This exception was subsequently
> > extended to verifying the pesigned kexec kernel image signature. (Other
> > subsystems are abusing the keys on the .platform keyring to verify other
> > signatures.)
> >
> > Instead of saying "unrelated to IMA/EVM", how about saying something along the
> > lines of "IMA has a dependency on the platform and machine keyrings, but this
> > dependency isn't limited to IMA/EVM."
> >
> > Paul, this patch set doesn't apply to cleanly to Linus's tree. What is the base
> > commit?
>
> It would have been based on the lsm/dev branch since the LSM tree is
> the target, however, given the scope of the patchset and the fact that
> it has been several weeks since it was originally posted, I wouldn't
> be surprised it if needs some fuzzing when applied on top of lsm/dev
> too.
Thanks, Paul. I was able to apply the patches and run some regression tests.
Mimi
^ permalink raw reply
* Re: [PATCH v2] memfd,selinux: call security_inode_init_security_anon
From: Thiébaud Weksteen @ 2025-09-08 22:51 UTC (permalink / raw)
To: Stephen Smalley
Cc: Paul Moore, James Morris, Hugh Dickins, Jeff Vander Stoep,
Nick Kralevich, Jeff Xu, Baolin Wang, Isaac Manjarres,
linux-kernel, linux-security-module, selinux, linux-mm
In-Reply-To: <CAEjxPJ6QfUZijh3PEpHs_Yw6Hmte92-rg8gkvMw9cD=JxA+CMA@mail.gmail.com>
On Tue, Sep 9, 2025 at 2:27 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Sun, Sep 7, 2025 at 9:34 PM Thiébaud Weksteen <tweek@google.com> wrote:
> >
> > Prior to this change, no security hooks were called at the creation of a
> > memfd file. It means that, for SELinux as an example, it will receive
> > the default type of the filesystem that backs the in-memory inode. In
> > most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> > be hugetlbfs. Both can be considered implementation details of memfd.
> >
> > It also means that it is not possible to differentiate between a file
> > coming from memfd_create and a file coming from a standard tmpfs mount
> > point.
> >
> > Additionally, no permission is validated at creation, which differs from
> > the similar memfd_secret syscall.
> >
> > Call security_inode_init_security_anon during creation. This ensures
> > that the file is setup similarly to other anonymous inodes. On SELinux,
> > it means that the file will receive the security context of its task.
> >
> > The ability to limit fexecve on memfd has been of interest to avoid
> > potential pitfalls where /proc/self/exe or similar would be executed
> > [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> > similarly to the file class. These access vectors may not make sense for
> > the existing "anon_inode" class. Therefore, define and assign a new
> > class "memfd_file" to support such access vectors.
> >
> > Guard these changes behind a new policy capability named "memfd_class".
> >
> > [1] https://crbug.com/1305267
> > [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
> >
> > Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> > Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> > Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
>
> When you revise a patch, you aren't supposed to retain other's tags
> since they haven't technically reviewed, agreed to, or tested the
> revised change.
> That said, I have now done so and thus these tags can remain!
>
I'm sorry for that. Thanks for the clarification, I wasn't sure what
the process was. And thanks for the review!
^ permalink raw reply
* Re: [PATCH v3 27/34] tomoyo: move initcalls to the LSM framework
From: Paul Moore @ 2025-09-08 22:55 UTC (permalink / raw)
To: Tetsuo Handa; +Cc: linux-security-module
In-Reply-To: <2c42b87b-d22f-4ac7-9c1c-200e1241833c@I-love.SAKURA.ne.jp>
On Sun, Sep 7, 2025 at 3:39 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2025/09/05 0:02, Paul Moore wrote:
> > On Thu, Sep 4, 2025 at 5:53 AM Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> wrote:
> >> On 2025/09/04 5:32, Paul Moore wrote:
> >>> On Thu, Aug 14, 2025 at 6:54 PM Paul Moore <paul@paul-moore.com> wrote:
> >>>>
> >>>> Reviewed-by: Kees Cook <kees@kernel.org>
> >>>> Signed-off-by: Paul Moore <paul@paul-moore.com>
> >>>> ---
> >>>> security/tomoyo/common.h | 2 ++
> >>>> security/tomoyo/securityfs_if.c | 4 +---
> >>>> security/tomoyo/tomoyo.c | 1 +
> >>>> 3 files changed, 4 insertions(+), 3 deletions(-)
> >>>
> >>> Tetsuo, does this look okay to you?
> >>>
> >>
> >> Yes.
> >
> > Thanks for reviewing, may I add your ACK?
>
> Yes.
Done. Thank you.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v3 11/34] lsm: get rid of the lsm_names list and do some cleanup
From: Paul Moore @ 2025-09-08 23:05 UTC (permalink / raw)
To: Tetsuo Handa
Cc: John Johansen, Roberto Sassu, linux-security-module,
linux-integrity, selinux, Mimi Zohar, Roberto Sassu, Fan Wu,
Mickaël Salaün, Günther Noack, Kees Cook,
Micah Morton, Casey Schaufler, Nicolas Bouchinet, Xiu Jianfeng
In-Reply-To: <91e6cbd4-9811-4890-84e6-4d58c22a02b0@I-love.SAKURA.ne.jp>
On Mon, Sep 8, 2025 at 9:07 AM Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
> On 2025/09/07 16:35, Tetsuo Handa wrote:
> > On 2025/09/05 2:52, Paul Moore wrote:
> >> + if (!str) {
> >> + str = str_tmp;
> >> + len = len_tmp - 1;
> >
> > This needs to be
> >
> > len = len_tmp - 1;
> > mb();
> > str = str_tmp;
> >
> > , or concurrent access might reach simple_read_from_buffer()
> > with str != 0 and len == 0. (If you don't want mb(), you can use
> >
> > - if (unlikely(!str)) {
> > + if (unlikely(!str || !len)) {
Good catch, thanks. I'm going to go with the approach above as it is
rather straightforward.
> Well, memory barrier is more complicated; it will be
>
> len = len_tmp - 1;
> wmb();
> str = str_tmp;
>
> and
>
> }
> rmb();
> return simple_read_from_buffer(buf, count, ppos, str, len);
>
> pair.
>
> Just splitting the whole { } block that follows "if (unlikely(!str))"
> out as an initcall function is much simpler ...
I would very much prefer to get the string generation out of the boot,
and generate it on demand.
--
paul-moore.com
^ permalink raw reply
* [RFC PATCH 0/6] Implement LANDLOCK_ADD_RULE_QUIET
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
Hi Mickaël,
This RFC patch series implements a first pass patch of the "quiet flag"
feature as proposed in [1]. I've evolved the design beyond the original
discussion to come up with what I believe would be most useful. For this
implementation:
- The user can set the quiet flag for a layer on any part of the fs
hierarchy, and the flag inherits down (no support for "cancelling" the
inheritance of the flag in specific subdirectories).
- The youngest layer that denies a request gets to decide whether the
denial is audited or not. This means that a compromised binary, for
example, cannot "turn off" Landlock auditing when it tries to access
files, unless it denies access to the files itself. There is some
debate to be had on whether, if a parent layer sets the quiet flag, but
the request is denied by a deeper layer, whether Landlock should still
audit anyway (since the rule author of the child layer likely did not
expect the denial, so it would be good diagnostic)
This series does not add any tests yet (and also no support for
suppressing optional access denial audit yet due to complexity). If
you're happy with this design I can write some tests (and add the missing
support). Here is a sandboxer demo:
# LL_FS_RO=/ LL_FS_RW= LL_FORCE_LOG=1 LL_FS_QUIET=/tmp linux/samples/landlock/sandboxer /bin/bash
Executing the sandboxed command...
[ 135.126499][ T60] audit: type=1423 audit(1757374868.281:942): domain=1a435130e blockers=fs.write_file path="/dev/tty" dev="devtmpfs" ino=11
[ 135.133298][ T60] audit: type=1424 audit(1757374868.281:942): domain=1a435130e status=allocated mode=enforcing pid=959 uid=0 exe="/linux/samples/landlock/sandboxer" comm="sandboxer"
[ 135.141869][ T60] audit: type=1300 audit(1757374868.281:942): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=557a9cda83d1 a2=802 a3=0 items=0 ppid=958 pid=959 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
[ 135.156620][ T60] audit: type=1327 audit(1757374868.281:942): proctitle="/bin/bash"
bash: cannot set terminal process group (958): Inappropriate ioctl for device
bash: no job control in this shell
# echo quiet > /tmp/aa
bash: /tmp/aa: Permission denied
# echo not quiet > /usr/aa
[ 165.358804][ T60] audit: type=1423 audit(1757374898.513:943): domain=1a435130e blockers=fs.make_reg path="/usr" dev="virtiofs" ino=840
[ 165.363746][ T60] audit: type=1300 audit(1757374898.513:943): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=557a9ce447c0 a2=241 a3=1b6 items=0 ppid=958 pid=959 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
[ 165.375594][ T60] audit: type=1327 audit(1757374898.513:943): proctitle="/bin/bash"
bash: /usr/aa: Permission denied
## (still in sandboxer)
# LL_FS_RO= LL_FS_RW=/ LL_FS_QUIET=/ linux/samples/landlock/sandboxer /bin/bash
Executing the sandboxed command...
[ 203.490417][ T60] audit: type=1423 audit(1757374936.645:944): domain=1a435130e blockers=fs.write_file path="/dev/tty" dev="devtmpfs" ino=11
...
# echo "child can't suppress audit logs" > /usr/a
[ 219.948543][ T60] audit: type=1423 audit(1757374953.101:945): domain=1a435130e blockers=fs.make_reg path="/usr" dev="virtiofs" ino=840
[ 219.953918][ T60] audit: type=1300 audit(1757374953.101:945): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=5651ea7875c0 a2=241 a3=1b6 items=0 ppid=959 pid=960 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
[ 219.969167][ T60] audit: type=1327 audit(1757374953.101:945): proctitle="/bin/bash"
bash: /usr/a: Permission denied
# echo "/tmp is still quiet" > /tmp/a
bash: /tmp/a: Permission denied
# exit
(still in first layer sandboxer)
# LL_FS_RO=/ LL_FS_RW= LL_FS_QUIET= LL_FORCE_LOG=1 linux/samples/landlock/sandboxer /bin/bash
Executing the sandboxed command...
...
root@fced6595bd01:/# echo "not quiet now" > /tmp/a
[ 492.130486][ T60] audit: type=1423 audit(1757375225.285:949): domain=1a435132a blockers=fs.make_reg path="/tmp" dev="tmpfs" ino=1
[ 492.136729][ T60] audit: type=1300 audit(1757375225.285:949): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=55fc4c168450 a2=241 a3=1b6 items=0 ppid=959 pid=964 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
[ 492.151727][ T60] audit: type=1327 audit(1757375225.285:949): proctitle="/bin/bash"
bash: /tmp/a: Permission denied
All existing kselftests pass.
[1]: https://github.com/landlock-lsm/linux/issues/44#issuecomment-2876500918
Kind regards,
Tingmao
Tingmao Wang (6):
landlock: Add a place for flags to layer rules
landlock: Add API support for the quiet flag
landlock/audit: Check for quiet flag in landlock_log_denial
landlock/audit: Fix wrong type usage
landlock/access: Improve explanation on the deny_masks_t
samples/landlock: Add FS quiet flag support to sandboxer
include/uapi/linux/landlock.h | 25 +++++
samples/landlock/sandboxer.c | 20 +++-
security/landlock/access.h | 6 +-
security/landlock/audit.c | 18 +++-
security/landlock/audit.h | 3 +-
security/landlock/fs.c | 99 ++++++++++++--------
security/landlock/fs.h | 2 +-
security/landlock/net.c | 11 ++-
security/landlock/net.h | 3 +-
security/landlock/ruleset.c | 17 +++-
security/landlock/ruleset.h | 29 +++++-
security/landlock/syscalls.c | 28 +++---
security/landlock/task.c | 12 +--
tools/testing/selftests/landlock/base_test.c | 2 +-
14 files changed, 199 insertions(+), 76 deletions(-)
base-commit: b320789d6883cc00ac78ce83bccbfe7ed58afcf0
--
2.51.0
^ permalink raw reply
* [RFC PATCH 1/6] landlock: Add a place for flags to layer rules
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
To avoid unnecessarily increasing the size of struct landlock_layer, we
make the layer level a u8 and use the space to store the flags struct.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
security/landlock/fs.c | 75 ++++++++++++++++++++++++-------------
security/landlock/net.c | 3 +-
security/landlock/ruleset.c | 9 ++++-
security/landlock/ruleset.h | 27 ++++++++++++-
4 files changed, 83 insertions(+), 31 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index c04f8879ad03..e7eaf55093e9 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -756,10 +756,12 @@ static bool is_access_to_paths_allowed(
const struct path *const path,
const access_mask_t access_request_parent1,
layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
+ struct collected_rule_flags *const rule_flags_parent1,
struct landlock_request *const log_request_parent1,
struct dentry *const dentry_child1,
const access_mask_t access_request_parent2,
layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
+ struct collected_rule_flags *const rule_flags_parent2,
struct landlock_request *const log_request_parent2,
struct dentry *const dentry_child2)
{
@@ -810,22 +812,30 @@ static bool is_access_to_paths_allowed(
}
if (unlikely(dentry_child1)) {
+ /*
+ * The rule_flags for child1 should have been included in
+ * rule_flags_masks_parent1 already. We do not bother about it
+ * for domain check.
+ */
landlock_unmask_layers(
find_rule(domain, dentry_child1),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child1, LANDLOCK_KEY_INODE),
- &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
+ &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1),
+ NULL);
layer_masks_child1 = &_layer_masks_child1;
child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
+ /* See above comment for why NULL is passed as rule_flags_masks */
landlock_unmask_layers(
find_rule(domain, dentry_child2),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child2, LANDLOCK_KEY_INODE),
- &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
+ &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2),
+ NULL);
layer_masks_child2 = &_layer_masks_child2;
child2_is_directory = d_is_dir(dentry_child2);
}
@@ -881,16 +891,18 @@ static bool is_access_to_paths_allowed(
}
rule = find_rule(domain, walker_path.dentry);
- allowed_parent1 = allowed_parent1 ||
- landlock_unmask_layers(
- rule, access_masked_parent1,
- layer_masks_parent1,
- ARRAY_SIZE(*layer_masks_parent1));
- allowed_parent2 = allowed_parent2 ||
- landlock_unmask_layers(
- rule, access_masked_parent2,
- layer_masks_parent2,
- ARRAY_SIZE(*layer_masks_parent2));
+ allowed_parent1 =
+ allowed_parent1 ||
+ landlock_unmask_layers(rule, access_masked_parent1,
+ layer_masks_parent1,
+ ARRAY_SIZE(*layer_masks_parent1),
+ rule_flags_parent1);
+ allowed_parent2 =
+ allowed_parent2 ||
+ landlock_unmask_layers(rule, access_masked_parent2,
+ layer_masks_parent2,
+ ARRAY_SIZE(*layer_masks_parent2),
+ rule_flags_parent2);
/* Stops when a rule from each layer grants access. */
if (allowed_parent1 && allowed_parent2)
@@ -958,6 +970,7 @@ static int current_check_access_path(const struct path *const path,
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), masks, NULL);
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct collected_rule_flags rule_flags = {};
struct landlock_request request = {};
if (!subject)
@@ -967,8 +980,8 @@ static int current_check_access_path(const struct path *const path,
access_request, &layer_masks,
LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(subject->domain, path, access_request,
- &layer_masks, &request, NULL, 0, NULL,
- NULL, NULL))
+ &layer_masks, &rule_flags, &request,
+ NULL, 0, NULL, NULL, NULL, NULL))
return 0;
landlock_log_denial(subject, &request);
@@ -1032,7 +1045,8 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
static bool collect_domain_accesses(
const struct landlock_ruleset *const domain,
const struct dentry *const mnt_root, struct dentry *dir,
- layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS])
+ layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS],
+ struct collected_rule_flags *const rule_flags)
{
unsigned long access_dom;
bool ret = false;
@@ -1051,9 +1065,9 @@ static bool collect_domain_accesses(
struct dentry *parent_dentry;
/* Gets all layers allowing all domain accesses. */
- if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
- layer_masks_dom,
- ARRAY_SIZE(*layer_masks_dom))) {
+ if (landlock_unmask_layers(
+ find_rule(domain, dir), access_dom, layer_masks_dom,
+ ARRAY_SIZE(*layer_masks_dom), rule_flags)) {
/*
* Stops when all handled accesses are allowed by at
* least one rule in each layer.
@@ -1140,6 +1154,8 @@ static int current_check_refer_path(struct dentry *const old_dentry,
struct dentry *old_parent;
layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {},
layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct collected_rule_flags rule_flags_parent1 = {},
+ rule_flags_parent2 = {};
struct landlock_request request1 = {}, request2 = {};
if (!subject)
@@ -1172,10 +1188,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
subject->domain,
access_request_parent1 | access_request_parent2,
&layer_masks_parent1, LANDLOCK_KEY_INODE);
- if (is_access_to_paths_allowed(subject->domain, new_dir,
- access_request_parent1,
- &layer_masks_parent1, &request1,
- NULL, 0, NULL, NULL, NULL))
+ if (is_access_to_paths_allowed(
+ subject->domain, new_dir, access_request_parent1,
+ &layer_masks_parent1, &rule_flags_parent1,
+ &request1, NULL, 0, NULL, NULL, NULL, NULL))
return 0;
landlock_log_denial(subject, &request1);
@@ -1201,10 +1217,12 @@ static int current_check_refer_path(struct dentry *const old_dentry,
/* new_dir->dentry is equal to new_dentry->d_parent */
allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
old_parent,
- &layer_masks_parent1);
+ &layer_masks_parent1,
+ &rule_flags_parent1);
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
new_dir->dentry,
- &layer_masks_parent2);
+ &layer_masks_parent2,
+ &rule_flags_parent2);
if (allow_parent1 && allow_parent2)
return 0;
@@ -1217,8 +1235,9 @@ static int current_check_refer_path(struct dentry *const old_dentry,
*/
if (is_access_to_paths_allowed(
subject->domain, &mnt_dir, access_request_parent1,
- &layer_masks_parent1, &request1, old_dentry,
- access_request_parent2, &layer_masks_parent2, &request2,
+ &layer_masks_parent1, &rule_flags_parent1, &request1,
+ old_dentry, access_request_parent2, &layer_masks_parent2,
+ &rule_flags_parent2, &request2,
exchange ? new_dentry : NULL))
return 0;
@@ -1616,6 +1635,7 @@ static bool is_device(const struct file *const file)
static int hook_file_open(struct file *const file)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
+ struct collected_rule_flags rule_flags = {};
access_mask_t open_access_request, full_access_request, allowed_access,
optional_access;
const struct landlock_cred_security *const subject =
@@ -1647,7 +1667,8 @@ static int hook_file_open(struct file *const file)
landlock_init_layer_masks(subject->domain,
full_access_request, &layer_masks,
LANDLOCK_KEY_INODE),
- &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
+ &layer_masks, &rule_flags, &request, NULL, 0, NULL, NULL,
+ NULL, NULL)) {
allowed_access = full_access_request;
} else {
unsigned long access_bit;
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 1f3915a90a80..fc6369dffa51 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -48,6 +48,7 @@ static int current_check_access_socket(struct socket *const sock,
{
__be16 port;
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+ struct collected_rule_flags rule_flags = {};
const struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
@@ -179,7 +180,7 @@ static int current_check_access_socket(struct socket *const sock,
access_request, &layer_masks,
LANDLOCK_KEY_NET_PORT);
if (landlock_unmask_layers(rule, access_request, &layer_masks,
- ARRAY_SIZE(layer_masks)))
+ ARRAY_SIZE(layer_masks), &rule_flags))
return 0;
audit_net.family = address->sa_family;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index ce7940efea51..3aa4e33ac95b 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -616,7 +616,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
- const size_t masks_array_size)
+ const size_t masks_array_size,
+ struct collected_rule_flags *const rule_flags)
{
size_t layer_level;
@@ -643,6 +644,12 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
unsigned long access_bit;
bool is_empty;
+ if (rule_flags) {
+ /* Collect rule flags for each layer */
+ if (layer->flags.quiet)
+ rule_flags->quiet_masks |= layer_bit;
+ }
+
/*
* Records in @layer_masks which layer grants access to each
* requested access.
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 5da9a64f5af7..d4b70b6af137 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -29,7 +29,18 @@ struct landlock_layer {
/**
* @level: Position of this layer in the layer stack.
*/
- u16 level;
+ u8 level;
+ /**
+ * @flags: Bitfield for special flags attached to this rule.
+ */
+ struct {
+ /**
+ * @quiet: Suppresses denial audit logs for the object covered by
+ * this rule in this domain. For fs rules, this inherits down the
+ * file hierarchy.
+ */
+ bool quiet:1;
+ } flags;
/**
* @access: Bitfield of allowed actions on the kernel object. They are
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
@@ -37,6 +48,17 @@ struct landlock_layer {
access_mask_t access;
};
+
+/**
+ * struct collected_rule_flags - Hold accumulated flags for each layer
+ */
+struct collected_rule_flags {
+ /**
+ * @quiet_masks: Layers for which the quiet flag is effective.
+ */
+ layer_mask_t quiet_masks;
+};
+
/**
* union landlock_key - Key of a ruleset's red-black tree
*/
@@ -304,7 +326,8 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
- const size_t masks_array_size);
+ const size_t masks_array_size,
+ struct collected_rule_flags *const rule_flags);
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
--
2.51.0
^ permalink raw reply related
* [RFC PATCH 2/6] landlock: Add API support for the quiet flag
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
Also added documentation.
As for kselftests, for now we just change add_rule_checks_ordering to use
a different invalid flag. I will add tests for the quiet flag in a later
version.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
include/uapi/linux/landlock.h | 25 +++++++++++++++++
security/landlock/fs.c | 4 +--
security/landlock/fs.h | 2 +-
security/landlock/net.c | 5 ++--
security/landlock/net.h | 3 ++-
security/landlock/ruleset.c | 8 +++++-
security/landlock/ruleset.h | 2 +-
security/landlock/syscalls.c | 28 ++++++++++++--------
tools/testing/selftests/landlock/base_test.c | 2 +-
9 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f030adc462ee..3e5b2ce0b18b 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -69,6 +69,31 @@ struct landlock_ruleset_attr {
#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1)
/* clang-format on */
+/**
+ * DOC: landlock_add_rule_flags
+ *
+ * **Flags**
+ *
+ * %LANDLOCK_ADD_RULE_QUIET
+ * This flag controls whether Landlock will log audit messages when
+ * access to the objects covered by this rule is denied by this layer.
+ *
+ * When Landlock denies an access, if audit logging is enabled,
+ * Landlock will check if the youngest layer that denied the access
+ * has marked the object in question as "quiet". If so, no audit log
+ * will be generated. Note that logging is only suppressed if the
+ * layer that denied the access is this layer. This means that a
+ * sandboxed program cannot use this flag to "hide" access denials,
+ * unless it denies itself the access.
+ *
+ * When this flag is present, the caller is allowed to pass in a rule
+ * with empty allowed_access.
+ */
+
+/* clang-format off */
+#define LANDLOCK_ADD_RULE_QUIET (1U << 0)
+/* clang-format on */
+
/**
* DOC: landlock_restrict_self_flags
*
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index e7eaf55093e9..b566ae498df5 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -322,7 +322,7 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
*/
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
- access_mask_t access_rights)
+ access_mask_t access_rights, const int flags)
{
int err;
struct landlock_id id = {
@@ -343,7 +343,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights);
+ err = landlock_insert_rule(ruleset, id, access_rights, flags);
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index bf9948941f2f..cb7e654933ac 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -126,6 +126,6 @@ __init void landlock_add_fs_hooks(void);
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
- access_mask_t access_hierarchy);
+ access_mask_t access_hierarchy, const int flags);
#endif /* _SECURITY_LANDLOCK_FS_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index fc6369dffa51..bddbe93d69fd 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -20,7 +20,8 @@
#include "ruleset.h"
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
- const u16 port, access_mask_t access_rights)
+ const u16 port, access_mask_t access_rights,
+ const int flags)
{
int err;
const struct landlock_id id = {
@@ -35,7 +36,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
~landlock_get_net_access_mask(ruleset, 0);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights);
+ err = landlock_insert_rule(ruleset, id, access_rights, flags);
mutex_unlock(&ruleset->lock);
return err;
diff --git a/security/landlock/net.h b/security/landlock/net.h
index 09960c237a13..799cedd5d0b7 100644
--- a/security/landlock/net.h
+++ b/security/landlock/net.h
@@ -16,7 +16,8 @@
__init void landlock_add_net_hooks(void);
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
- const u16 port, access_mask_t access_rights);
+ const u16 port, access_mask_t access_rights,
+ const int flags);
#else /* IS_ENABLED(CONFIG_INET) */
static inline void landlock_add_net_hooks(void)
{
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 3aa4e33ac95b..990aa1a2c120 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -21,6 +21,7 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
#include "access.h"
#include "audit.h"
@@ -251,6 +252,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
if (WARN_ON_ONCE(this->layers[0].level != 0))
return -EINVAL;
this->layers[0].access |= (*layers)[0].access;
+ this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
return 0;
}
@@ -297,12 +299,15 @@ static void build_check_layer(void)
/* @ruleset must be locked by the caller. */
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
- const access_mask_t access)
+ const access_mask_t access, const int flags)
{
struct landlock_layer layers[] = { {
.access = access,
/* When @level is zero, insert_rule() extends @ruleset. */
.level = 0,
+ .flags = {
+ .quiet = flags & LANDLOCK_ADD_RULE_QUIET ? 1 : 0,
+ }
} };
build_check_layer();
@@ -343,6 +348,7 @@ static int merge_tree(struct landlock_ruleset *const dst,
return -EINVAL;
layers[0].access = walker_rule->layers[0].access;
+ layers[0].flags = walker_rule->layers[0].flags;
err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
if (err)
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index d4b70b6af137..4f184d2da382 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -224,7 +224,7 @@ DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
- const access_mask_t access);
+ const access_mask_t access, const int flags);
struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 0116e9f93ffe..e46164246fdb 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -312,7 +312,7 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
}
static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
- const void __user *const rule_attr)
+ const void __user *const rule_attr, int flags)
{
struct landlock_path_beneath_attr path_beneath_attr;
struct path path;
@@ -328,8 +328,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
* are ignored in path walks.
+ * (However, the rule is not useless if it is there to hold a quiet
+ * flag)
*/
- if (!path_beneath_attr.allowed_access)
+ if (!flags && !path_beneath_attr.allowed_access)
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
@@ -344,13 +346,13 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
/* Imports the new rule. */
err = landlock_append_fs_rule(ruleset, &path,
- path_beneath_attr.allowed_access);
+ path_beneath_attr.allowed_access, flags);
path_put(&path);
return err;
}
static int add_rule_net_port(struct landlock_ruleset *ruleset,
- const void __user *const rule_attr)
+ const void __user *const rule_attr, int flags)
{
struct landlock_net_port_attr net_port_attr;
int res;
@@ -364,8 +366,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
* are ignored by network actions.
+ * (However, the rule is not useless if it is there to hold a quiet
+ * flag)
*/
- if (!net_port_attr.allowed_access)
+ if (!flags && !net_port_attr.allowed_access)
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
@@ -379,7 +383,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
/* Imports the new rule. */
return landlock_append_net_rule(ruleset, net_port_attr.port,
- net_port_attr.allowed_access);
+ net_port_attr.allowed_access, flags);
}
/**
@@ -390,7 +394,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* @rule_type: Identify the structure type pointed to by @rule_attr:
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (matching the @rule_type).
- * @flags: Must be 0.
+ * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
*
* This system call enables to define a new rule and add it to an existing
* ruleset.
@@ -414,6 +418,9 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* @rule_attr is not the expected file descriptor type;
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
* - %EFAULT: @rule_attr was not a valid address.
+ *
+ * .. kernel-doc:: include/uapi/linux/landlock.h
+ * :identifiers: landlock_add_rule_flags
*/
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type,
@@ -424,8 +431,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
if (!is_initialized())
return -EOPNOTSUPP;
- /* No flag for now. */
- if (flags)
+ if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
return -EINVAL;
/* Gets and checks the ruleset. */
@@ -435,9 +441,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
switch (rule_type) {
case LANDLOCK_RULE_PATH_BENEATH:
- return add_rule_path_beneath(ruleset, rule_attr);
+ return add_rule_path_beneath(ruleset, rule_attr, flags);
case LANDLOCK_RULE_NET_PORT:
- return add_rule_net_port(ruleset, rule_attr);
+ return add_rule_net_port(ruleset, rule_attr, flags);
default:
return -EINVAL;
}
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 7b69002239d7..d07a0bf6927c 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -201,7 +201,7 @@ TEST(add_rule_checks_ordering)
ASSERT_LE(0, ruleset_fd);
/* Checks invalid flags. */
- ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
+ ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 100));
ASSERT_EQ(EINVAL, errno);
/* Checks invalid ruleset FD. */
--
2.51.0
^ permalink raw reply related
* [RFC PATCH 3/6] landlock/audit: Check for quiet flag in landlock_log_denial
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
Suppresses logging if the flag is effective on the youngest layer.
This does not handle optional access logging yet - to do that correctly we will
need to expand deny_masks to support representing "don't log anything"
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
security/landlock/audit.c | 16 +++++++++++++++-
security/landlock/audit.h | 3 ++-
security/landlock/fs.c | 20 +++++++++++---------
security/landlock/net.c | 3 ++-
security/landlock/task.c | 12 ++++++------
5 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index c52d079cdb77..2b3edd1ab374 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -386,9 +386,12 @@ static bool is_valid_request(const struct landlock_request *const request)
*
* @subject: The Landlock subject's credential denying an action.
* @request: Detail of the user space request.
+ * @rule_flags: The flags for the matched rule, or NULL if this is a
+ * scope request (no particular object involved).
*/
void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+ const struct landlock_request *const request,
+ const struct collected_rule_flags *const rule_flags)
{
struct audit_buffer *ab;
struct landlock_hierarchy *youngest_denied;
@@ -436,6 +439,17 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
if (!audit_enabled)
return;
+ /*
+ * Check if the object is marked quiet by the layer that denied the
+ * request. (If it's a different layer that marked it as quiet, but
+ * that layer is not the one that denied the request, we should still
+ * audit log the denial)
+ */
+ if (rule_flags &&
+ rule_flags->quiet_masks & BIT(youngest_layer)) {
+ return;
+ }
+
/* Checks if the current exec was restricting itself. */
if (subject->domain_exec & BIT(youngest_layer)) {
/* Ignores denials for the same execution. */
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 92428b7fc4d8..e6f76d417c2f 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -56,7 +56,8 @@ struct landlock_request {
void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy);
void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request);
+ const struct landlock_request *const request,
+ const struct collected_rule_flags *const rule_flags);
#else /* CONFIG_AUDIT */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index b566ae498df5..ba93b0de384c 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -984,7 +984,7 @@ static int current_check_access_path(const struct path *const path,
NULL, 0, NULL, NULL, NULL, NULL))
return 0;
- landlock_log_denial(subject, &request);
+ landlock_log_denial(subject, &request, &rule_flags);
return -EACCES;
}
@@ -1194,7 +1194,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
&request1, NULL, 0, NULL, NULL, NULL, NULL))
return 0;
- landlock_log_denial(subject, &request1);
+ landlock_log_denial(subject, &request1, &rule_flags_parent1);
return -EACCES;
}
@@ -1243,11 +1243,13 @@ static int current_check_refer_path(struct dentry *const old_dentry,
if (request1.access) {
request1.audit.u.path.dentry = old_parent;
- landlock_log_denial(subject, &request1);
+ landlock_log_denial(subject, &request1,
+ &rule_flags_parent1);
}
if (request2.access) {
request2.audit.u.path.dentry = new_dir->dentry;
- landlock_log_denial(subject, &request2);
+ landlock_log_denial(subject, &request2,
+ &rule_flags_parent2);
}
/*
@@ -1403,7 +1405,7 @@ log_fs_change_topology_path(const struct landlock_cred_security *const subject,
.u.path = *path,
},
.layer_plus_one = handle_layer + 1,
- });
+ }, NULL);
}
static void log_fs_change_topology_dentry(
@@ -1417,7 +1419,7 @@ static void log_fs_change_topology_dentry(
.u.dentry = dentry,
},
.layer_plus_one = handle_layer + 1,
- });
+ }, NULL);
}
/*
@@ -1705,7 +1707,7 @@ static int hook_file_open(struct file *const file)
/* Sets access to reflect the actual request. */
request.access = open_access_request;
- landlock_log_denial(subject, &request);
+ landlock_log_denial(subject, &request, &rule_flags);
return -EACCES;
}
@@ -1735,7 +1737,7 @@ static int hook_file_truncate(struct file *const file)
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
#endif /* CONFIG_AUDIT */
- });
+ }, NULL);
return -EACCES;
}
@@ -1774,7 +1776,7 @@ static int hook_file_ioctl_common(const struct file *const file,
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
#endif /* CONFIG_AUDIT */
- });
+ }, NULL);
return -EACCES;
}
diff --git a/security/landlock/net.c b/security/landlock/net.c
index bddbe93d69fd..d242bb9fa5b4 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -193,7 +193,8 @@ static int current_check_access_socket(struct socket *const sock,
.access = access_request,
.layer_masks = &layer_masks,
.layer_masks_size = ARRAY_SIZE(layer_masks),
- });
+ },
+ &rule_flags);
return -EACCES;
}
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 2385017418ca..dfea227ce1d7 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -115,7 +115,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
.u.tsk = child,
},
.layer_plus_one = parent_subject->domain->num_layers,
- });
+ }, NULL);
return err;
}
@@ -161,7 +161,7 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
.u.tsk = current,
},
.layer_plus_one = parent_subject->domain->num_layers,
- });
+ }, NULL);
return err;
}
@@ -290,7 +290,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- });
+ }, NULL);
return -EPERM;
}
@@ -327,7 +327,7 @@ static int hook_unix_may_send(struct socket *const sock,
},
},
.layer_plus_one = handle_layer + 1,
- });
+ }, NULL);
return -EPERM;
}
@@ -383,7 +383,7 @@ static int hook_task_kill(struct task_struct *const p,
.u.tsk = p,
},
.layer_plus_one = handle_layer + 1,
- });
+ }, NULL);
return -EPERM;
}
@@ -426,7 +426,7 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
#ifdef CONFIG_AUDIT
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
#endif /* CONFIG_AUDIT */
- });
+ }, NULL);
return -EPERM;
}
--
2.51.0
^ permalink raw reply related
* [RFC PATCH 4/6] landlock/audit: Fix wrong type usage
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
I think, based on my best understanding, that this type is likely a typo
(even though in the end both are u16)
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
security/landlock/audit.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 2b3edd1ab374..a67155c7f0c3 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -191,7 +191,7 @@ static size_t get_denied_layer(const struct landlock_ruleset *const domain,
long youngest_layer = -1;
for_each_set_bit(access_bit, &access_req, layer_masks_size) {
- const access_mask_t mask = (*layer_masks)[access_bit];
+ const layer_mask_t mask = (*layer_masks)[access_bit];
long layer;
if (!mask)
--
2.51.0
^ permalink raw reply related
* [RFC PATCH 5/6] landlock/access: Improve explanation on the deny_masks_t
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
Not really related to this series, but just something which took me a
while to realize, and would probably be helpful as a comment.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
security/landlock/access.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index 7961c6630a2d..5e2285575479 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -67,8 +67,10 @@ typedef u16 layer_mask_t;
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
/*
- * Tracks domains responsible of a denied access. This is required to avoid
- * storing in each object the full layer_masks[] required by update_request().
+ * Tracks domains responsible of a denied access, stored in the form of
+ * two 4-bit layer numbers packed into a byte (one for each optional
+ * access). This is required to avoid storing in each object the full
+ * layer_masks[] required by update_request().
*/
typedef u8 deny_masks_t;
--
2.51.0
^ permalink raw reply related
* [RFC PATCH 6/6] samples/landlock: Add FS quiet flag support to sandboxer
From: Tingmao Wang @ 2025-09-09 0:06 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Jan Kara, linux-security-module
In-Reply-To: <cover.1757376311.git.m@maowtm.org>
net rule support is TODO
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
samples/landlock/sandboxer.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e7af02f98208..77c99329b3ba 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,6 +58,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_FS_RO_NAME "LL_FS_RO"
#define ENV_FS_RW_NAME "LL_FS_RW"
+#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_SCOPED_NAME "LL_SCOPED"
@@ -116,7 +117,7 @@ static int parse_path(char *env_path, const char ***const path_list)
/* clang-format on */
static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
- const __u64 allowed_access)
+ const __u64 allowed_access, bool quiet)
{
int num_paths, i, ret = 1;
char *env_path_name;
@@ -166,7 +167,8 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
if (!S_ISDIR(statbuf.st_mode))
path_beneath.allowed_access &= ACCESS_FILE;
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
- &path_beneath, 0)) {
+ &path_beneath,
+ quiet ? LANDLOCK_ADD_RULE_QUIET : 0)) {
fprintf(stderr,
"Failed to update the ruleset with \"%s\": %s\n",
path_list[i], strerror(errno));
@@ -328,6 +330,7 @@ static const char help[] =
"\n"
"A sandboxer should not log denied access requests to avoid spamming logs, "
"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
+ ENV_FS_QUIET_NAME " can then be used to make access to some denied paths not trigger audit logging.\n"
"\n"
"Example:\n"
ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
@@ -497,12 +500,21 @@ int main(const int argc, char *const argv[], char *const *const envp)
return 1;
}
- if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+ if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro,
+ false)) {
goto err_close_ruleset;
}
- if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+ if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw,
+ false)) {
goto err_close_ruleset;
}
+ /* Don't require this env to be present */
+ if (getenv(ENV_FS_QUIET_NAME)) {
+ if (populate_ruleset_fs(ENV_FS_QUIET_NAME, ruleset_fd, 0,
+ true)) {
+ goto err_close_ruleset;
+ }
+ }
if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
LANDLOCK_ACCESS_NET_BIND_TCP)) {
--
2.51.0
^ permalink raw reply related
* [PATCH] ima: setting security.ima to fix security.evm for a file with IMA signature
From: Coiby Xu @ 2025-09-09 4:19 UTC (permalink / raw)
To: linux-integrity
Cc: Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
Paul Moore, James Morris, Serge E. Hallyn,
open list:SECURITY SUBSYSTEM, open list
When both IMA and EVM fix modes are enabled, accessing a file with IMA
signature won't cause security.evm to be fixed. But this doesn't happen
to a file with correct IMA hash already set because accessing it will
cause setting security.ima again which triggers fixing security.evm
thanks to security_inode_post_setxattr->evm_update_evmxattr.
Let's use the same mechanism to fix security.evm for a file with IMA
signature.
Signed-off-by: Coiby Xu <coxu@redhat.com>
---
security/integrity/ima/ima_appraise.c | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index f435eff4667f..18c3907c5e44 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -595,12 +595,27 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
integrity_audit_msg(audit_msgno, inode, filename,
op, cause, rc, 0);
} else if (status != INTEGRITY_PASS) {
- /* Fix mode, but don't replace file signatures. */
- if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig &&
- (!xattr_value ||
- xattr_value->type != EVM_IMA_XATTR_DIGSIG)) {
- if (!ima_fix_xattr(dentry, iint))
- status = INTEGRITY_PASS;
+ /*
+ * Fix mode, but don't replace file signatures.
+ *
+ * When EVM fix mode is also enabled, security.evm will be
+ * fixed automatically when security.ima is set because of
+ * security_inode_post_setxattr->evm_update_evmxattr.
+ */
+ if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig) {
+ if (!xattr_value ||
+ xattr_value->type != EVM_IMA_XATTR_DIGSIG) {
+ if (ima_fix_xattr(dentry, iint))
+ status = INTEGRITY_PASS;
+ } else if (xattr_value->type == EVM_IMA_XATTR_DIGSIG &&
+ evm_revalidate_status(XATTR_NAME_IMA)) {
+ if (!__vfs_setxattr_noperm(&nop_mnt_idmap,
+ dentry,
+ XATTR_NAME_IMA,
+ xattr_value,
+ xattr_len, 0))
+ status = INTEGRITY_PASS;
+ }
}
/*
base-commit: b320789d6883cc00ac78ce83bccbfe7ed58afcf0
--
2.51.0
^ permalink raw reply related
* Re: [PATCH] ima,evm: move initcalls to the LSM framework
From: Roberto Sassu @ 2025-09-09 7:47 UTC (permalink / raw)
To: Paul Moore, roberto.sassu
Cc: Mimi Zohar, linux-security-module, linux-integrity, selinux,
john.johansen, wufan, mic, kees, mortonm, casey, penguin-kernel,
nicolas.bouchinet, xiujianfeng
In-Reply-To: <CAHC9VhRWt54V3nvRDpN_=gb5Fc68KznwDd7xhNmyGJw5+TQ5Dw@mail.gmail.com>
On Mon, 2025-09-08 at 17:04 -0400, Paul Moore wrote:
> On Sun, Sep 7, 2025 at 10:46 PM Mimi Zohar <zohar@linux.ibm.com> wrote:
> > On Sun, 2025-09-07 at 21:08 -0400, Paul Moore wrote:
> > > On Sun, Sep 7, 2025 at 5:18 PM Mimi Zohar <zohar@linux.ibm.com> wrote:
> > > > On Tue, 2025-09-02 at 14:54 +0200, Roberto Sassu wrote:
> > > > > From: Paul Moore <paul@paul-moore.com>
> > > >
> > > > Remove above ...
> > > >
> > > > >
> > > > > This patch converts IMA and EVM to use the LSM frameworks's initcall
> > > > > mechanism. It moved the integrity_fs_init() call to ima_fs_init() and
> > > > > evm_init_secfs(), to work around the fact that there is no "integrity" LSM,
> > > > > and introduced integrity_fs_fini() to remove the integrity directory, if
> > > > > empty. Both integrity_fs_init() and integrity_fs_fini() support the
> > > > > scenario of being called by both the IMA and EVM LSMs.
> > > > >
> > > > > It is worth mentioning that this patch does not touch any of the
> > > > > "platform certs" code that lives in the security/integrity/platform_certs
> > > > > directory as the IMA/EVM maintainers have assured me that this code is
> > > > > unrelated to IMA/EVM, despite the location, and will be moved to a more
> > > >
> > > > This wording "unrelated to IMA/EVM" was taken from Paul's patch description, but
> > > > needs to be tweaked. Please refer to my comment on Paul's patch.
> > >
> > > Minim, Roberto, would both of you be okay if I changed the second
> > > paragraph to read as follows:
> > >
> > > "This patch does not touch any of the platform certificate code that
> > > lives under the security/integrity/platform_certs directory as the
> > > IMA/EVM developers would prefer to address that in a future patchset."
> >
> > That's fine.
>
> Roberto, is it okay if I update your patch with the text above and use
> it to replace my IMA/EVM patch in the LSM init patchset? I'll retain
> your From/Sign-off of course.
Yes, absolutely!
Roberto
^ permalink raw reply
* [PATCH] Audit: Fix skb leak when audit rate limit is exceeded
From: Gerald Yang @ 2025-09-09 13:10 UTC (permalink / raw)
To: Casey Schaufler, Paul Moore, linux-security-module, audit; +Cc: gerald.yang.tw
When configuring a small audit rate limit in
/etc/audit/rules.d/audit.rules:
-a always,exit -F arch=b64 -S openat -S truncate -S ftruncate
-F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access -r 100
And then repeatedly triggering permission denied as a normal user:
while :; do cat /proc/1/environ; done
We can see the messages in kernel log:
[ 2531.862184] audit: rate limit exceeded
The unreclaimable slab objects start to leak quickly. With kmemleak
enabled, many call traces appear like:
unreferenced object 0xffff99144b13f600 (size 232):
comm "cat", pid 1100, jiffies 4294739144
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace (crc 8540ec4f):
kmemleak_alloc+0x4a/0x90
kmem_cache_alloc_node+0x2ea/0x390
__alloc_skb+0x174/0x1b0
audit_log_start+0x198/0x3d0
audit_log_proctitle+0x32/0x160
audit_log_exit+0x6c6/0x780
__audit_syscall_exit+0xee/0x140
syscall_exit_work+0x12b/0x150
syscall_exit_to_user_mode_prepare+0x39/0x80
syscall_exit_to_user_mode+0x11/0x260
do_syscall_64+0x8c/0x180
entry_SYSCALL_64_after_hwframe+0x78/0x80
This shows that the skb allocated in audit_log_start() and queued
onto skb_list is never freed.
In audit_log_end(), each skb is dequeued from skb_list and passed
to __audit_log_end(). However, when the audit rate limit is exceeded,
__audit_log_end() simply prints "rate limit exceeded" and returns
without processing the skb. Since the skb is already removed from
skb_list, audit_buffer_free() cannot free it later, leading to a
memory leak.
Fix this by freeing the skb when the rate limit is exceeded.
Signed-off-by: Gerald Yang <gerald.yang@canonical.com>
---
kernel/audit.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/kernel/audit.c b/kernel/audit.c
index bd7474fd8d2c..89530ddf3807 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -2615,8 +2615,10 @@ static void __audit_log_end(struct sk_buff *skb)
/* queue the netlink packet */
skb_queue_tail(&audit_queue, skb);
- } else
+ } else {
audit_log_lost("rate limit exceeded");
+ kfree_skb(skb);
+ }
}
/**
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] ima: setting security.ima to fix security.evm for a file with IMA signature
From: Mimi Zohar @ 2025-09-09 15:31 UTC (permalink / raw)
To: Coiby Xu, linux-integrity
Cc: Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Paul Moore,
James Morris, Serge E. Hallyn, open list:SECURITY SUBSYSTEM,
open list
In-Reply-To: <20250909041954.1626914-1-coxu@redhat.com>
On Tue, 2025-09-09 at 12:19 +0800, Coiby Xu wrote:
> When both IMA and EVM fix modes are enabled, accessing a file with IMA
> signature won't cause security.evm to be fixed. But this doesn't happen
> to a file with correct IMA hash already set because accessing it will
> cause setting security.ima again which triggers fixing security.evm
> thanks to security_inode_post_setxattr->evm_update_evmxattr.
>
> Let's use the same mechanism to fix security.evm for a file with IMA
> signature.
>
> Signed-off-by: Coiby Xu <coxu@redhat.com>
Agreed, re-writing the file signature stored as security.ima would force
security.evm to be updated.
Unfortunately, I'm missing something. ima_appraise_measurement() first verifies
the existing security.evm xattr, before verifying the security.ima xattr. If
the EVM HMAC fails to verify, it immediately exits ima_appraise_measurement().
security.ima in this case is never verified.
This patch seems to address the case where the existing security.evm is valid,
but the file signature stored in security.ima is invalid. (To get to the new
code, the "status" flag is not INTEGRITY_PASS.) Re-writing the same invalid
file signature would solve an invalid security.evm, but not an invalid IMA file
signature. What am I missing?
thanks,
Mimi
> ---
> security/integrity/ima/ima_appraise.c | 27 +++++++++++++++++++++------
> 1 file changed, 21 insertions(+), 6 deletions(-)
>
> diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
> index f435eff4667f..18c3907c5e44 100644
> --- a/security/integrity/ima/ima_appraise.c
> +++ b/security/integrity/ima/ima_appraise.c
> @@ -595,12 +595,27 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
> integrity_audit_msg(audit_msgno, inode, filename,
> op, cause, rc, 0);
> } else if (status != INTEGRITY_PASS) {
> - /* Fix mode, but don't replace file signatures. */
> - if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig &&
> - (!xattr_value ||
> - xattr_value->type != EVM_IMA_XATTR_DIGSIG)) {
> - if (!ima_fix_xattr(dentry, iint))
> - status = INTEGRITY_PASS;
> + /*
> + * Fix mode, but don't replace file signatures.
> + *
> + * When EVM fix mode is also enabled, security.evm will be
> + * fixed automatically when security.ima is set because of
> + * security_inode_post_setxattr->evm_update_evmxattr.
> + */
> + if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig) {
> + if (!xattr_value ||
> + xattr_value->type != EVM_IMA_XATTR_DIGSIG) {
> + if (ima_fix_xattr(dentry, iint))
> + status = INTEGRITY_PASS;
> + } else if (xattr_value->type == EVM_IMA_XATTR_DIGSIG &&
> + evm_revalidate_status(XATTR_NAME_IMA)) {
> + if (!__vfs_setxattr_noperm(&nop_mnt_idmap,
> + dentry,
> + XATTR_NAME_IMA,
> + xattr_value,
> + xattr_len, 0))
> + status = INTEGRITY_PASS;
> + }
> }
>
> /*
>
> base-commit: b320789d6883cc00ac78ce83bccbfe7ed58afcf0
^ permalink raw reply
* [RFC 0/2] BPF signature hash chains
From: Blaise Boscaccy @ 2025-09-09 16:20 UTC (permalink / raw)
To: bpf, linux-security-module, kpsingh, bboscaccy, paul, kys, ast,
daniel, andrii, James.Bottomley, wufan
This patchset extends the currently proposed signature verification
patchset
https://lore.kernel.org/linux-security-module/20250813205526.2992911-1-kpsingh@kernel.org/
with hash-chain functionality to verify the contents of arbitrary maps.
The currently proposed loader + map signature verification
scheme—requested by Alexei and KP—is simple to implement and
acceptable if users/admins are satisfied with it. However, verifying
both the loader and the maps offers additional benefits beyond just
verifying the loader:
1. Simplified Loader Logic: The lskel loader becomes simpler since it
doesn’t need to verify program maps—this is already handled by
bpf_check_signature().
2. Security and Audit Integrity: A key advantage is that the LSM
(Linux Security Module) hook for authorizing BPF program loads can
operate after signature verification. This ensures:
* Access control decisions can be based on verified signature status.
* Accurate system state measurement and logging.
* Log events claiming a verified signature are fully truthful,
avoiding misleading entries that only the loader was verified
while the actual BPF program verification happens later without
logging.
This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments.
A working tree with this patchset is being maintained at
https://github.com/blaiseboscaccy/linux/tree/bpf-hash-chains
Blaise Boscaccy (2):
bpf: Add hash chain signature support for arbitrary maps
libbpf: Add hash chain signing support to light skeletons.
include/uapi/linux/bpf.h | 6 +++
kernel/bpf/syscall.c | 75 ++++++++++++++++++++++++++++++++--
tools/bpf/bpftool/gen.c | 25 ++++++++++++
tools/bpf/bpftool/main.c | 8 +++-
tools/bpf/bpftool/main.h | 1 +
tools/bpf/bpftool/sign.c | 17 ++++++--
tools/include/uapi/linux/bpf.h | 6 +++
tools/lib/bpf/libbpf.h | 3 +-
tools/lib/bpf/skel_internal.h | 6 ++-
9 files changed, 137 insertions(+), 10 deletions(-)
--
2.48.1
^ permalink raw reply
* [RFC 1/2] bpf: Add hash chain signature support for arbitrary maps
From: Blaise Boscaccy @ 2025-09-09 16:20 UTC (permalink / raw)
To: bpf, linux-security-module, kpsingh, bboscaccy, paul, kys, ast,
daniel, andrii, James.Bottomley, wufan
In-Reply-To: <20250909162345.569889-1-bboscaccy@linux.microsoft.com>
This patch introduces hash chain support for signature verification of
arbitrary bpf map objects which was described here:
https://lore.kernel.org/linux-security-module/20250721211958.1881379-1-kpsingh@kernel.org/
The UAPI is extended to allow for in-kernel checking of maps passed in
via the fd_array. A hash chain is constructed from the maps, in order
specified by the signature_maps field. The hash chain is terminated
with the hash of the program itself.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
include/uapi/linux/bpf.h | 6 +++
kernel/bpf/syscall.c | 75 ++++++++++++++++++++++++++++++++--
tools/include/uapi/linux/bpf.h | 6 +++
3 files changed, 83 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index b42c3740e053e..c83f2a34674fd 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1617,6 +1617,12 @@ union bpf_attr {
* verification.
*/
__u32 keyring_id;
+ /* Pointer to a buffer containing the maps used in the signature
+ * hash chain of the BPF program.
+ */
+ __aligned_u64 signature_maps;
+ /* Size of the signature maps buffer. */
+ __u32 signature_maps_size;
};
struct { /* anonymous struct used by BPF_OBJ_* commands */
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 10fd3ea5d91fd..f7e9bcabd9dcc 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2780,15 +2780,36 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
}
}
+static inline int bpf_map_get_hash(int map_fd, void *buffer)
+{
+ struct bpf_map *map;
+
+ CLASS(fd, f)(map_fd);
+ map = __bpf_map_get(f);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ if (!map->ops->map_get_hash)
+ return -EINVAL;
+
+ return map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, buffer);
+}
+
static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
union bpf_attr *attr,
bool is_kernel)
{
bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
- struct bpf_dynptr_kern sig_ptr, insns_ptr;
+ bpfptr_t umaps;
+ struct bpf_dynptr_kern sig_ptr, insns_ptr, hash_ptr;
struct bpf_key *key = NULL;
void *sig;
+ int *maps;
+ int map_fd;
int err = 0;
+ u64 buffer[8];
+ u64 hash[4];
+ int n;
if (system_keyring_id_check(attr->keyring_id) == 0)
key = bpf_lookup_system_key(attr->keyring_id);
@@ -2808,16 +2829,62 @@ static noinline int bpf_prog_verify_signature(struct bpf_prog *prog,
bpf_dynptr_init(&insns_ptr, prog->insnsi, BPF_DYNPTR_TYPE_LOCAL, 0,
prog->len * sizeof(struct bpf_insn));
- err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
- (struct bpf_dynptr *)&sig_ptr, key);
+ if (!attr->signature_maps_size) {
+ err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&insns_ptr,
+ (struct bpf_dynptr *)&sig_ptr, key);
+ } else {
+ bpf_dynptr_init(&hash_ptr, hash, BPF_DYNPTR_TYPE_LOCAL, 0,
+ sizeof(hash));
+ umaps = make_bpfptr(attr->signature_maps, is_kernel);
+ maps = kvmemdup_bpfptr(umaps, attr->signature_maps_size * sizeof(*maps));
+ if (!maps) {
+ err = -ENOMEM;
+ goto out;
+ }
+ n = attr->signature_maps_size - 1;
+ err = copy_from_bpfptr_offset(&map_fd, make_bpfptr(attr->fd_array, is_kernel),
+ maps[n] * sizeof(map_fd),
+ sizeof(map_fd));
+ if (err < 0)
+ goto free_maps;
+
+ err = bpf_map_get_hash(map_fd, hash);
+ if (err != 0)
+ goto free_maps;
+
+ n--;
+ while (n >= 0) {
+ memcpy(buffer, hash, sizeof(hash));
+ err = copy_from_bpfptr_offset(&map_fd,
+ make_bpfptr(attr->fd_array, is_kernel),
+ maps[n] * sizeof(map_fd),
+ sizeof(map_fd));
+ if (err < 0)
+ goto free_maps;
+
+ err = bpf_map_get_hash(map_fd, buffer+4);
+ if (err != 0)
+ goto free_maps;
+ sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
+ n--;
+ }
+ sha256((u8 *)prog->insnsi, prog->len * sizeof(struct bpf_insn), (u8 *)&buffer);
+ memcpy(buffer+4, hash, sizeof(hash));
+ sha256((u8 *)buffer, sizeof(buffer), (u8 *)&hash);
+ err = bpf_verify_pkcs7_signature((struct bpf_dynptr *)&hash_ptr,
+ (struct bpf_dynptr *)&sig_ptr, key);
+free_maps:
+ kvfree(maps);
+ }
+out:
bpf_key_put(key);
kvfree(sig);
return err;
}
/* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD keyring_id
+#define BPF_PROG_LOAD_LAST_FIELD signature_maps_size
static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
{
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index b42c3740e053e..c83f2a34674fd 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1617,6 +1617,12 @@ union bpf_attr {
* verification.
*/
__u32 keyring_id;
+ /* Pointer to a buffer containing the maps used in the signature
+ * hash chain of the BPF program.
+ */
+ __aligned_u64 signature_maps;
+ /* Size of the signature maps buffer. */
+ __u32 signature_maps_size;
};
struct { /* anonymous struct used by BPF_OBJ_* commands */
--
2.48.1
^ permalink raw reply related
* [RFC 2/2] libbpf: Add hash chain signing support to light skeletons.
From: Blaise Boscaccy @ 2025-09-09 16:20 UTC (permalink / raw)
To: bpf, linux-security-module, kpsingh, bboscaccy, paul, kys, ast,
daniel, andrii, James.Bottomley, wufan
In-Reply-To: <20250909162345.569889-1-bboscaccy@linux.microsoft.com>
This patch introduces a hash chain signing support for light-skeleton
assets. A new flag '-M' is added which constructs a hash chain with
the loader program and the target payload.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
tools/bpf/bpftool/gen.c | 25 +++++++++++++++++++++++++
tools/bpf/bpftool/main.c | 8 +++++++-
tools/bpf/bpftool/main.h | 1 +
tools/bpf/bpftool/sign.c | 17 ++++++++++++++---
tools/lib/bpf/libbpf.h | 3 ++-
tools/lib/bpf/skel_internal.h | 6 +++++-
6 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index ab6fc86598ad3..e660fbc701c5d 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -699,6 +699,9 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
if (sign_progs)
opts.gen_hash = true;
+ if (sign_maps)
+ opts.sign_maps = true;
+
err = bpf_object__gen_loader(obj, &opts);
if (err)
return err;
@@ -793,6 +796,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
if (sign_progs) {
sopts.insns = opts.insns;
sopts.insns_sz = opts.insns_sz;
+ sopts.data = opts.data;
+ sopts.data_sz = opts.data_sz;
sopts.excl_prog_hash = prog_sha;
sopts.excl_prog_hash_sz = sizeof(prog_sha);
sopts.signature = sig_buf;
@@ -821,6 +826,13 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
\n\
\";\n");
+ if (sign_maps) {
+ codegen("\
+ \n\
+ static const int opts_signature_maps[1] __attribute__((__aligned__(8))) = {0}; \n\
+ ");
+ }
+
codegen("\
\n\
opts.signature = (void *)opts_sig; \n\
@@ -829,6 +841,19 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
opts.excl_prog_hash_sz = sizeof(opts_excl_hash) - 1; \n\
opts.keyring_id = KEY_SPEC_SESSION_KEYRING; \n\
");
+ if (sign_maps) {
+ codegen("\
+ \n\
+ opts.signature_maps = (void *)opts_signature_maps; \n\
+ opts.signature_maps_sz = 1; \n\
+ ");
+ } else {
+ codegen("\
+ \n\
+ opts.signature_maps = (void *)NULL; \n\
+ opts.signature_maps_sz = 0; \n\
+ ");
+ }
}
codegen("\
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index fc25bb390ec71..287e8205494cb 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -34,6 +34,7 @@ bool use_loader;
struct btf *base_btf;
struct hashmap *refs_table;
bool sign_progs;
+bool sign_maps;
const char *private_key_path;
const char *cert_path;
@@ -477,7 +478,7 @@ int main(int argc, char **argv)
bin_name = "bpftool";
opterr = 0;
- while ((opt = getopt_long(argc, argv, "VhpjfLmndSi:k:B:l",
+ while ((opt = getopt_long(argc, argv, "VhpjfLmndSMi:k:B:l",
options, NULL)) >= 0) {
switch (opt) {
case 'V':
@@ -527,6 +528,11 @@ int main(int argc, char **argv)
sign_progs = true;
use_loader = true;
break;
+ case 'M':
+ sign_maps = true;
+ sign_progs = true;
+ use_loader = true;
+ break;
case 'k':
private_key_path = optarg;
break;
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index f921af3cda87f..805c3d87a1330 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -92,6 +92,7 @@ extern bool use_loader;
extern struct btf *base_btf;
extern struct hashmap *refs_table;
extern bool sign_progs;
+extern bool sign_maps;
extern const char *private_key_path;
extern const char *cert_path;
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index f0b5dd10a46b2..d5514b7d2b82d 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -22,6 +22,7 @@
#include <errno.h>
#include <bpf/skel_internal.h>
+#include <bpf/libbpf_internal.h>
#include "main.h"
@@ -129,8 +130,18 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
long actual_sig_len = 0;
X509 *x509 = NULL;
int err = 0;
-
- bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+ unsigned char hash[SHA256_DIGEST_LENGTH * 2];
+ unsigned char term[SHA256_DIGEST_LENGTH];
+
+ if (sign_maps) {
+ libbpf_sha256(opts->insns, opts->insns_sz, hash, SHA256_DIGEST_LENGTH);
+ libbpf_sha256(opts->data, opts->data_sz, hash + SHA256_DIGEST_LENGTH,
+ SHA256_DIGEST_LENGTH);
+ libbpf_sha256(hash, sizeof(hash), term, sizeof(term));
+ bd_in = BIO_new_mem_buf(term, sizeof(term));
+ } else {
+ bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+ }
if (!bd_in) {
err = -ENOMEM;
goto cleanup;
@@ -171,7 +182,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
&opts->excl_prog_hash_sz, EVP_sha256(), NULL);
- bd_out = BIO_new(BIO_s_mem());
+ bd_out = BIO_new(BIO_s_mem());
if (!bd_out) {
err = -ENOMEM;
goto cleanup;
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 7cad8470d9ebe..aad0288cd05e3 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1827,9 +1827,10 @@ struct gen_loader_opts {
__u32 data_sz;
__u32 insns_sz;
bool gen_hash;
+ bool sign_maps;
};
-#define gen_loader_opts__last_field gen_hash
+#define gen_loader_opts__last_field sign_maps
LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj,
struct gen_loader_opts *opts);
diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h
index 5b6d1b09dc8a6..c25a4f1308e44 100644
--- a/tools/lib/bpf/skel_internal.h
+++ b/tools/lib/bpf/skel_internal.h
@@ -74,6 +74,8 @@ struct bpf_load_and_run_opts {
__u32 keyring_id;
void * excl_prog_hash;
__u32 excl_prog_hash_sz;
+ const int *signature_maps;
+ __u32 signature_maps_sz;
};
long kern_sys_bpf(__u32 cmd, void *attr, __u32 attr_size);
@@ -351,7 +353,7 @@ static inline int skel_map_freeze(int fd)
static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
{
- const size_t prog_load_attr_sz = offsetofend(union bpf_attr, keyring_id);
+ const size_t prog_load_attr_sz = offsetofend(union bpf_attr, signature_maps_size);
const size_t test_run_attr_sz = offsetofend(union bpf_attr, test);
int map_fd = -1, prog_fd = -1, key = 0, err;
union bpf_attr attr;
@@ -394,6 +396,8 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
#ifndef __KERNEL__
attr.signature = (long) opts->signature;
attr.signature_size = opts->signature_sz;
+ attr.signature_maps = (long) opts->signature_maps;
+ attr.signature_maps_size = opts->signature_maps_sz;
#else
if (opts->signature || opts->signature_sz)
pr_warn("signatures are not supported from bpf_preload\n");
--
2.48.1
^ permalink raw reply related
* Re: [PATCH v2] memfd,selinux: call security_inode_init_security_anon
From: Paul Moore @ 2025-09-09 21:10 UTC (permalink / raw)
To: Stephen Smalley
Cc: Thiébaud Weksteen, James Morris, Hugh Dickins,
Jeff Vander Stoep, Nick Kralevich, Jeff Xu, Baolin Wang,
Isaac Manjarres, linux-kernel, linux-security-module, selinux,
linux-mm
In-Reply-To: <CAEjxPJ5q0eriGjo1tdfN+pzBBN5OeyfMaYp_sNQcOg-rDaXVCA@mail.gmail.com>
On Mon, Sep 8, 2025 at 12:32 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
> On Sun, Sep 7, 2025 at 9:34 PM Thiébaud Weksteen <tweek@google.com> wrote:
> >
> > Prior to this change, no security hooks were called at the creation of a
> > memfd file. It means that, for SELinux as an example, it will receive
> > the default type of the filesystem that backs the in-memory inode. In
> > most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
> > be hugetlbfs. Both can be considered implementation details of memfd.
> >
> > It also means that it is not possible to differentiate between a file
> > coming from memfd_create and a file coming from a standard tmpfs mount
> > point.
> >
> > Additionally, no permission is validated at creation, which differs from
> > the similar memfd_secret syscall.
> >
> > Call security_inode_init_security_anon during creation. This ensures
> > that the file is setup similarly to other anonymous inodes. On SELinux,
> > it means that the file will receive the security context of its task.
> >
> > The ability to limit fexecve on memfd has been of interest to avoid
> > potential pitfalls where /proc/self/exe or similar would be executed
> > [1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
> > similarly to the file class. These access vectors may not make sense for
> > the existing "anon_inode" class. Therefore, define and assign a new
> > class "memfd_file" to support such access vectors.
> >
> > Guard these changes behind a new policy capability named "memfd_class".
> >
> > [1] https://crbug.com/1305267
> > [2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/
> >
> > Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> > Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> > Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> > ---
> > Changes since v1:
> > - Move test of class earlier in selinux_bprm_creds_for_exec
> > - Remove duplicate call to security_transition_sid
> >
> > Changes since RFC:
> > - Remove enum argument, simply compare the anon inode name
> > - Introduce a policy capability for compatility
> > - Add validation of class in selinux_bprm_creds_for_exec
> >
> > include/linux/memfd.h | 2 ++
> > mm/memfd.c | 14 ++++++++++--
> > security/selinux/hooks.c | 26 +++++++++++++++++-----
> > security/selinux/include/classmap.h | 2 ++
> > security/selinux/include/policycap.h | 1 +
> > security/selinux/include/policycap_names.h | 1 +
> > security/selinux/include/security.h | 5 +++++
> > 7 files changed, 44 insertions(+), 7 deletions(-)
> >
>
> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > index c95a5874bf7d..6adf2f393ed9 100644
> > --- a/security/selinux/hooks.c
> > +++ b/security/selinux/hooks.c
> > @@ -2315,6 +2316,9 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
> > new_tsec = selinux_cred(bprm->cred);
> > isec = inode_security(inode);
> >
> > + if (isec->sclass != SECCLASS_FILE && isec->sclass != SECCLASS_MEMFD_FILE)
> > + return -EPERM;
> > +
>
> Sorry, I should have mentioned this earlier, but usually we try to
> avoid triggering silent denials from SELinux since it provides no hint
> to the user as to what went wrong or how to resolve.
Ooof, yeah, I should have noticed that too.
> Arguably reaching this code would be suggestive of a kernel bug but I
> know that BUG_ON() is frowned upon these days.
> Maybe we should WARN_ON_ONCE() here or similar?
BUG_ON() is definitely a no-no, but WARN_ON_ONCE()/WARN_ON() is still
considered okay last I checked (no forced panic). Of the two I think
WARN_ON() would be a better choice here.
> We also rarely return
> -EPERM from SELinux outside of capability checks since usually EPERM
> means a failed capability check
> (vs -EACCES). Defer to Paul on how/if he wants to handle this and
> whether it requires re-spinning this patch or just a follow-on one.
Another fair point.
Considering that we are at -rc5 right now, we only have a few more
days left in the current dev cycle, I'm going to merge this now (with
a subject line tweak and some unnecessary vertical whitespace
removed), and I'll put together a quick little patch to do the
WARN_ON()/EACCES conversion which you'll see on list shortly ...
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH v2] memfd,selinux: call security_inode_init_security_anon
From: Paul Moore @ 2025-09-09 21:32 UTC (permalink / raw)
To: Stephen Smalley
Cc: Thiébaud Weksteen, James Morris, Hugh Dickins,
Jeff Vander Stoep, Nick Kralevich, Jeff Xu, Baolin Wang,
Isaac Manjarres, linux-kernel, linux-security-module, selinux,
linux-mm
In-Reply-To: <CAHC9VhR1pEFYzSFaqqWsU8C6vDaH_E8uZZ5g=KyK6TJvA7a8MQ@mail.gmail.com>
On Tue, Sep 9, 2025 at 5:10 PM Paul Moore <paul@paul-moore.com> wrote:
>
> Considering that we are at -rc5 right now, we only have a few more
> days left in the current dev cycle, I'm going to merge this now (with
> a subject line tweak and some unnecessary vertical whitespace
> removed), and I'll put together a quick little patch to do the
> WARN_ON()/EACCES conversion which you'll see on list shortly ...
The patch can be found at the link below.
https://lore.kernel.org/selinux/20250909213020.343501-2-paul@paul-moore.com/
--
paul-moore.com
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox