Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH 2/3] lsm: add the security_mmap_backing_file() hook
From: Paul Moore @ 2026-03-16 21:35 UTC (permalink / raw)
  To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs
  Cc: Amir Goldstein, Gao Xiang
In-Reply-To: <20260316213606.374109-5-paul@paul-moore.com>

Add the security_mmap_backing_file() hook to allow LSMs to properly
enforce access controls on mmap() operations on stacked filesystems
such as overlayfs.

The existing security_mmap_file() hook exists as an access control point
for mmap() but on stacked filesystems it only provides a way to enforce
access controls on the user visible file.  In order to enforce access
controls on the underlying backing file, the new
security_mmap_backing_file() hook is needed.

In addition the LSM hook additions, this patch also constifies the file
struct field in the LSM common_audit_data struct to better support LSMs
that will likely need to pass a const file struct pointer from the new
backing_file_user_path_file() API into the common LSM audit code.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
 fs/backing-file.c             |  8 +++++++-
 fs/erofs/ishare.c             |  6 ++++++
 include/linux/lsm_audit.h     |  2 +-
 include/linux/lsm_hook_defs.h |  2 ++
 include/linux/security.h      | 10 ++++++++++
 security/security.c           | 25 +++++++++++++++++++++++++
 6 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/fs/backing-file.c b/fs/backing-file.c
index acabeea7efff..cfc7f6611313 100644
--- a/fs/backing-file.c
+++ b/fs/backing-file.c
@@ -13,6 +13,7 @@
 #include <linux/splice.h>
 #include <linux/uio.h>
 #include <linux/mm.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -338,8 +339,13 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
 
 	vma_set_file(vma, file);
 
-	scoped_with_creds(ctx->cred)
+	scoped_with_creds(ctx->cred) {
+		ret = security_mmap_backing_file(vma, file, user_file);
+		if (ret)
+			return ret;
+
 		ret = vfs_mmap(vma->vm_file, vma);
+	}
 
 	if (ctx->accessed)
 		ctx->accessed(user_file);
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index 17a4941d4518..d66c3a935d83 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -150,8 +150,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
 static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	struct file *realfile = file->private_data;
+	int err;
 
 	vma_set_file(vma, realfile);
+
+	err = security_mmap_backing_file(vma, realfile, file);
+	if (err)
+		return err;
+
 	return generic_file_readonly_mmap(file, vma);
 }
 
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 382c56a97bba..584db296e43b 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -94,7 +94,7 @@ struct common_audit_data {
 #endif
 		char *kmod_name;
 		struct lsm_ioctlop_audit *op;
-		struct file *file;
+		const struct file *file;
 		struct lsm_ibpkey_audit *ibpkey;
 		struct lsm_ibendport_audit *ibendport;
 		int reason;
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c..4150c50a0482 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -198,6 +198,8 @@ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
 LSM_HOOK(int, 0, mmap_addr, unsigned long addr)
 LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot,
 	 unsigned long prot, unsigned long flags)
+LSM_HOOK(int, 0, mmap_backing_file, struct vm_area_struct *vma,
+	 struct file *backing_file, struct file *user_file)
 LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma,
 	 unsigned long reqprot, unsigned long prot)
 LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd)
diff --git a/include/linux/security.h b/include/linux/security.h
index 83a646d72f6f..4017361d8cba 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -476,6 +476,9 @@ int security_file_ioctl_compat(struct file *file, unsigned int cmd,
 			       unsigned long arg);
 int security_mmap_file(struct file *file, unsigned long prot,
 			unsigned long flags);
+int security_mmap_backing_file(struct vm_area_struct *vma,
+			       struct file *backing_file,
+			       struct file *user_file);
 int security_mmap_addr(unsigned long addr);
 int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
 			   unsigned long prot);
@@ -1159,6 +1162,13 @@ static inline int security_mmap_file(struct file *file, unsigned long prot,
 	return 0;
 }
 
+static inline int security_mmap_backing_file(struct vm_area_struct *vma,
+					     struct file *backing_file,
+					     struct file *user_file)
+{
+	return 0;
+}
+
 static inline int security_mmap_addr(unsigned long addr)
 {
 	return cap_mmap_addr(addr);
diff --git a/security/security.c b/security/security.c
index 67af9228c4e9..8d10b184ce25 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2505,6 +2505,31 @@ int security_mmap_file(struct file *file, unsigned long prot,
 			     flags);
 }
 
+/**
+ * security_mmap_backing_file - Check if mmap'ing a backing file is allowed
+ * @vma: the vm_area_struct for the mmap'd region
+ * @backing_file: the backing file being mmap'd
+ * @user_file: the user file being mmap'd
+ *
+ * Check permissions for a mmap operation on a stacked filesystem.  This hook
+ * is called after the security_mmap_file() and is responsible for authorizing
+ * the mmap on @backing_file.  It is important to note that the mmap operation
+ * on @user_file has already been authorized and the @vma->vm_file has been
+ * set to @backing_file.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mmap_backing_file(struct vm_area_struct *vma,
+			       struct file *backing_file,
+			       struct file *user_file)
+{
+	/* recommended by the stackable filesystem devs */
+	if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING)))
+		return -EIO;
+
+	return call_int_hook(mmap_backing_file, vma, backing_file, user_file);
+}
+
 /**
  * security_mmap_addr() - Check if mmap'ing an address is allowed
  * @addr: address
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/3] selinux: fix overlayfs mmap() and mprotect() access checks
From: Paul Moore @ 2026-03-16 21:35 UTC (permalink / raw)
  To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs
  Cc: Amir Goldstein, Gao Xiang
In-Reply-To: <20260316213606.374109-5-paul@paul-moore.com>

The existing SELinux security model for overlayfs is to allow access if
the current task is able to access the top level file (the "user" file)
and the mounter's credentials are sufficient to access the lower
level file (the "backing" file).  Unfortunately, the current code does
not properly enforce these access controls for both mmap() and mprotect()
operations on overlayfs filesystems.

This patch makes use of the newly created security_mmap_backing_file()
LSM hook to provide the missing backing file enforcement for mmap()
operations on overlayfs files, and leverages the new
backing_file_user_path_file() VFS API to provide an equivalent to the
missing user file in mprotect().

Signed-off-by: Paul Moore <paul@paul-moore.com>
---
 security/selinux/hooks.c | 108 ++++++++++++++++++++++++++++++++-------
 1 file changed, 90 insertions(+), 18 deletions(-)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index d8224ea113d1..013e1e35a1ff 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -94,6 +94,7 @@
 #include <linux/io_uring/cmd.h>
 #include <uapi/linux/lsm.h>
 #include <linux/memfd.h>
+#include <linux/backing-file.h>
 
 #include "initcalls.h"
 #include "avc.h"
@@ -1754,7 +1755,7 @@ static int bpf_fd_pass(const struct file *file, u32 sid);
    access to the file is not checked, e.g. for cases
    where only the descriptor is affected like seek. */
 static int file_has_perm(const struct cred *cred,
-			 struct file *file,
+			 const struct file *file,
 			 u32 av)
 {
 	struct file_security_struct *fsec = selinux_file(file);
@@ -3942,9 +3943,9 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd,
 
 static int default_noexec __ro_after_init;
 
-static int file_map_prot_check(struct file *file, unsigned long prot, int shared)
+static int file_map_prot_check(const struct cred *cred, const struct file *file,
+			       unsigned long prot, bool shared)
 {
-	const struct cred *cred = current_cred();
 	u32 sid = cred_sid(cred);
 	int rc = 0;
 
@@ -3993,36 +3994,86 @@ static int selinux_mmap_addr(unsigned long addr)
 	return rc;
 }
 
-static int selinux_mmap_file(struct file *file,
-			     unsigned long reqprot __always_unused,
-			     unsigned long prot, unsigned long flags)
+static int selinux_mmap_file_common(const struct cred *cred, struct file *file,
+				    unsigned long prot, bool shared)
 {
-	struct common_audit_data ad;
-	int rc;
-
 	if (file) {
+		int rc;
+		struct common_audit_data ad;
+
 		ad.type = LSM_AUDIT_DATA_FILE;
 		ad.u.file = file;
-		rc = inode_has_perm(current_cred(), file_inode(file),
-				    FILE__MAP, &ad);
+		rc = inode_has_perm(cred, file_inode(file), FILE__MAP, &ad);
 		if (rc)
 			return rc;
 	}
 
-	return file_map_prot_check(file, prot,
-				   (flags & MAP_TYPE) == MAP_SHARED);
+	return file_map_prot_check(cred, file, prot, shared);
+}
+
+static int selinux_mmap_file(struct file *file,
+			     unsigned long reqprot __always_unused,
+			     unsigned long prot, unsigned long flags)
+{
+	return selinux_mmap_file_common(current_cred(), file, prot,
+					(flags & MAP_TYPE) == MAP_SHARED);
+}
+
+/**
+ * selinux_mmap_backing_file - Check mmap permissions on a backing file
+ * @vma: memory region
+ * @backing_file: stacked filesystem backing file
+ * @user_file: user visible file
+ *
+ * This is called after selinux_mmap_file() on stacked filesystems, and it
+ * is this function's responsibility to verify access to @backing_file and
+ * setup the SELinux state for possible later use in the mprotect() code path.
+ *
+ * By the time this function is called, mmap() access to @user_file has already
+ * been authorized and @vma->vm_file has been set to point to @backing_file.
+ *
+ * Return zero on success, negative values otherwise.
+ */
+static int selinux_mmap_backing_file(struct vm_area_struct *vma,
+				     struct file *backing_file,
+				     struct file *user_file __always_unused)
+{
+	unsigned long prot = 0;
+
+	/* translate vma->vm_flags perms into PROT perms */
+	if (vma->vm_flags & VM_READ)
+		prot |= PROT_READ;
+	if (vma->vm_flags & VM_WRITE)
+		prot |= PROT_WRITE;
+	if (vma->vm_flags & VM_EXEC)
+		prot |= PROT_EXEC;
+
+	return selinux_mmap_file_common(backing_file->f_cred, backing_file,
+					prot, vma->vm_flags & VM_SHARED);
 }
 
 static int selinux_file_mprotect(struct vm_area_struct *vma,
 				 unsigned long reqprot __always_unused,
 				 unsigned long prot)
 {
+	int rc;
 	const struct cred *cred = current_cred();
 	u32 sid = cred_sid(cred);
+	const struct file *file = vma->vm_file;
+	const struct file *backing_file = NULL;
+
+	/* check if adjustments are needed for stacked filesystems */
+	if (file && (file->f_mode & FMODE_BACKING)) {
+		backing_file = vma->vm_file;
+		file = backing_file_user_path_file(backing_file);
+
+		/* sanity check the special O_PATH user file */
+		if (WARN_ON(!(file->f_mode & FMODE_OPENED)))
+			return -EPERM;
+	}
 
 	if (default_noexec &&
 	    (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) {
-		int rc = 0;
 		/*
 		 * We don't use the vma_is_initial_heap() helper as it has
 		 * a history of problems and is currently broken on systems
@@ -4036,11 +4087,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
 		    vma->vm_end <= vma->vm_mm->brk) {
 			rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
 					  PROCESS__EXECHEAP, NULL);
-		} else if (!vma->vm_file && (vma_is_initial_stack(vma) ||
+			if (rc)
+				return rc;
+		} else if (!file && (vma_is_initial_stack(vma) ||
 			    vma_is_stack_for_current(vma))) {
 			rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
 					  PROCESS__EXECSTACK, NULL);
-		} else if (vma->vm_file && vma->anon_vma) {
+			if (rc)
+				return rc;
+		} else if (file && vma->anon_vma) {
 			/*
 			 * We are making executable a file mapping that has
 			 * had some COW done. Since pages might have been
@@ -4048,13 +4103,29 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
 			 * modified content.  This typically should only
 			 * occur for text relocations.
 			 */
-			rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
+			rc = file_has_perm(cred, file, FILE__EXECMOD);
+			if (rc)
+				return rc;
+			if (backing_file) {
+				rc = file_has_perm(backing_file->f_cred,
+						   backing_file, FILE__EXECMOD);
+				if (rc)
+					return rc;
+			}
 		}
+	}
+
+	rc = file_map_prot_check(cred, file, prot, vma->vm_flags & VM_SHARED);
+	if (rc)
+		return rc;
+	if (backing_file) {
+		rc = file_map_prot_check(backing_file->f_cred, backing_file,
+					 prot, vma->vm_flags & VM_SHARED);
 		if (rc)
 			return rc;
 	}
 
-	return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED);
+	return 0;
 }
 
 static int selinux_file_lock(struct file *file, unsigned int cmd)
@@ -7501,6 +7572,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl),
 	LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat),
 	LSM_HOOK_INIT(mmap_file, selinux_mmap_file),
+	LSM_HOOK_INIT(mmap_backing_file, selinux_mmap_backing_file),
 	LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr),
 	LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect),
 	LSM_HOOK_INIT(file_lock, selinux_file_lock),
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls
From: Paul Moore @ 2026-03-16 21:59 UTC (permalink / raw)
  To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs
  Cc: Amir Goldstein, Gao Xiang
In-Reply-To: <20260316213606.374109-5-paul@paul-moore.com>

On Mon, Mar 16, 2026 at 5:36 PM Paul Moore <paul@paul-moore.com> wrote:
>
> The existing mmap() and mprotect() LSM access control points for the
> overlayfs filesystem are incomplete in that they do not cover both the
> user and backing files.  This patchset corrects this through the addition
> of a new backing file specific LSM hook, security_mmap_backing_file(),
> a new user path file associated with a backing file that can be used by
> LSMs in the security_file_mprotect() code path, and the associated
> SELinux code changes.
>
> The security_mmap_backing_file() hook is intended to allow LSMs to apply
> access controls on mmap() operations accessing a backing file, similar to
> the security_mmap_file() for user files.  Due to the details around the
> accesses and the desire to distinguish between the two types of accesses,
> a new LSM hook was needed.  More information on this new hook can be
> found in the associated patch.
>
> The new user path file replaces the existing user path stored in the
> backing file.  This change was necessary to support LSM based access
> controls in the mprotect() code path where only one file is accessible
> via the vma->vm_file field.  Unfortunately, storing a reference to the
> user file inside the backing file does not work due to the cyclic
> ref counting so a stand-in was necessary, the new user O_PATH file.
> This new O_PATH file is intended to be representative of the original
> user file and can be used by LSMs to make access control decisions based
> on both the backing and user files.
>
> The SELinux changes in this patchset involve making use of the new
> security_mmap_backing_file() hook and updating the existing mprotect()
> access controls to take into account both the backing and user files.
> These changes preserve the existing SELinux approach of allowing access
> on overlayfs files if the current task has the necessary rights to the
> user file and the mounting process has the necessary rights to the
> underlying backing file.
>
> --
> Amir Goldstein (1):
>       backing_file: store user_path_file
>
> Paul Moore (2):
>       lsm: add the security_mmap_backing_file() hook
>       selinux: fix overlayfs mmap() and mprotect() access checks
>
>  fs/backing-file.c             |   28 +++++---
>  fs/erofs/ishare.c             |   12 ++-
>  fs/file_table.c               |   53 +++++++++++++---
>  fs/fuse/passthrough.c         |    3
>  fs/internal.h                 |    5 -
>  fs/overlayfs/dir.c            |    3
>  fs/overlayfs/file.c           |    1
>  include/linux/backing-file.h  |   29 ++++++++-
>  include/linux/file_ref.h      |   10 ---
>  include/linux/lsm_audit.h     |    2
>  include/linux/lsm_hook_defs.h |    2
>  include/linux/security.h      |   10 +++
>  security/security.c           |   25 +++++++
>  security/selinux/hooks.c      |  108 ++++++++++++++++++++++++++++------
>  14 files changed, 231 insertions(+), 60 deletions(-)

Due to the nature of the issue, I'm going to merge this into
lsm/stable-7.0 in a few moments so the changes can get some testing in
linux-next with the idea of sending this up to Linus' later in the
week.  If anyone has any concerns over this patchset, please let me
know as soon as possible.

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH 46/61] vfio: Prefer IS_ERR_OR_NULL over manual NULL check
From: Alex Williamson @ 2026-03-16 22:10 UTC (permalink / raw)
  To: Philipp Hahn
  Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
	gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
	linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
	linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
	linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
	linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
	linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
	linux-sctp, linux-security-module, linux-sh, linux-sound,
	linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
	netdev, ntfs3, samba-technical, sched-ext, target-devel,
	tipc-discussion, v9fs, alex
In-Reply-To: <20260310-b4-is_err_or_null-v1-46-bd63b656022d@avm.de>

On Tue, 10 Mar 2026 12:49:12 +0100
Philipp Hahn <phahn-oss@avm.de> wrote:

> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
> 
> Change generated with coccinelle.
> 
> To: Alex Williamson <alex@shazbot.org>
> Cc: kvm@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> ---
>  drivers/vfio/vfio_main.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
> index 742477546b15d4dbaf9ebcfb2e67627db71521e0..d71922dfde5885967398deddec3e9e04b05adfec 100644
> --- a/drivers/vfio/vfio_main.c
> +++ b/drivers/vfio/vfio_main.c
> @@ -923,7 +923,7 @@ vfio_ioctl_device_feature_mig_device_state(struct vfio_device *device,
>  
>  	/* Handle the VFIO_DEVICE_FEATURE_SET */
>  	filp = device->mig_ops->migration_set_state(device, mig.device_state);
> -	if (IS_ERR(filp) || !filp)
> +	if (IS_ERR_OR_NULL(filp))
>  		goto out_copy;
>  
>  	return vfio_ioct_mig_return_fd(filp, arg, &mig);
> 

As others have expressed in general, this doesn't seem to be cleaner
and tends to mask that we consider IS_ERR() and NULL as separate cases
in the goto.  This code looks like it could use some refactoring, and
likely that refactoring should handle the IS_ERR() and NULL cases
separately, but conflating them here is not an improvement.  Thanks,

Alex

^ permalink raw reply

* Re: [PATCH RFC 1/4] audit: Implement bpf_audit_log_*() wrappers
From: David Windsor @ 2026-03-16 22:14 UTC (permalink / raw)
  To: fred
  Cc: paul, bpf, linux-security-module, audit, ast, daniel, andrii,
	martin.lau, kpsingh
In-Reply-To: <20260311-bpf-auditd-send-message-v1-1-10a62db5c92f@cloudflare.com>

Hi Frederick,

On Wed, Mar 11, 2026 at 04:31:17PM -0500, Frederick Lawler wrote:
> +__bpf_kfunc int bpf_audit_log_cause(struct bpf_audit_context *ac,
> +				    const char *cause__str)
> +{
> +	if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_CAUSE)))
> +		return -EINVAL;
> +
> +	audit_log_format(ac->ab, " cause=");
> +	audit_log_untrustedstring(ac->ab, cause__str);
> +	return 0;
> +}

Rather than putting everything in the cause field, could we perhaps
have a separate kfunc here that appends normal stringpairs (not
format strings) to the audit record:

  bpf_audit_log_str(ac, "result", "denied");
  bpf_audit_log_str(ac, "op", "read");
  bpf_audit_log_str(ac, "scontext", ctx_str);

I know you didn't want to wrap audit_log_format(), which makes sense,
this would be a midway point between that and stuffing everything in
one field.

> +__bpf_kfunc int bpf_audit_log_net_sock(struct bpf_audit_context *ac, int netif,
> +				       const struct socket *sock)
> +{
> +	struct lsm_network_audit net = { .sk = sock->sk, .netif = netif };
> +	struct common_audit_data ad;
> +
> +	ad.type = LSM_AUDIT_DATA_NET;
> +	ad.u.net = &net;
> +	return __audit_log_lsm_data(ac, &ad);
> +}

Should we also wrap ipv4_skb_to_auditdata() /
ipv6_skb_to_auditdata()? Smack uses it in smack_socket_sock_rcv_skb.

Thanks,
David

^ permalink raw reply

* Re: [PATCH RFC bpf-next 1/4] audit: Implement bpf_audit_log_*() wrappers
From: Kumar Kartikeya Dwivedi @ 2026-03-17  2:04 UTC (permalink / raw)
  To: Frederick Lawler
  Cc: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Shuah Khan, Mickaël Salaün, Günther Noack,
	linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
	kernel-team
In-Reply-To: <20260311-bpf-auditd-send-message-v1-1-10a62db5c92f@cloudflare.com>

On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
>
> The primary use case is to provide LSM designers a direct API to report
> access allow/denies through the audit subsystem similar to how LSM's
> traditionally log their accesses.
>
> Left out from this API are functions that are potentially abuseable such as
> audit_log_format() where users may fill any field=value pair. Instead, the
> API mostly follows what is exposed through security/lsm_audit.c for
> consistency with user space audit expectations. Further calls to functions
> report once to avoid repeated-call abuse.
>
> Lastly, each audit record corresponds to the loaded BPF program's ID to
> track which program reported the log entry. This helps remove
> ambiguity in the event multiple programs are registered to the same
> security hook.
>
> Exposed functions:
>
>         bpf_audit_log_start()
>         bpf_audit_log_end()
>         bpf_audit_log_cause()
>         bpf_audit_log_cap()
>         bpf_audit_log_path()
>         bpf_audit_log_file()
>         bpf_audit_log_ioctl_op()
>         bpf_audit_log_dentry()
>         bpf_audit_log_inode()
>         bpf_audit_log_task()
>         bpf_audit_log_net_sock()
>         bpf_audit_log_net_sockaddr()
>
> Signed-off-by: Frederick Lawler <fred@cloudflare.com>
> ---
>  include/linux/lsm_audit.h   |   1 +
>  include/uapi/linux/audit.h  |   1 +
>  security/lsm_audit_kfuncs.c | 306 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 308 insertions(+)
>
> diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
> index 382c56a97bba1d0e5efe082553338229d541e267..859f51590de417ac246309eb75a760b8632224be 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -78,6 +78,7 @@ struct common_audit_data {
>  #define LSM_AUDIT_DATA_NOTIFICATION 16
>  #define LSM_AUDIT_DATA_ANONINODE       17
>  #define LSM_AUDIT_DATA_NLMSGTYPE       18
> +#define LSM_AUDIT_DATA_CAUSE 19 /* unused */
>         union   {
>                 struct path path;
>                 struct dentry *dentry;
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 14a1c1fe013acecb12ea6bf81690965421baa7ff..7a22e214fe3e421decfc4109d2e6a3cee996fe51 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -150,6 +150,7 @@
>  #define AUDIT_LANDLOCK_DOMAIN  1424    /* Landlock domain status */
>  #define AUDIT_MAC_TASK_CONTEXTS        1425    /* Multiple LSM task contexts */
>  #define AUDIT_MAC_OBJ_CONTEXTS 1426    /* Multiple LSM objext contexts */
> +#define AUDIT_BPF_LSM_ACCESS           1427    /* LSM BPF MAC events */
>
>  #define AUDIT_FIRST_KERN_ANOM_MSG   1700
>  #define AUDIT_LAST_KERN_ANOM_MSG    1799
> diff --git a/security/lsm_audit_kfuncs.c b/security/lsm_audit_kfuncs.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..0d4fb20be34a61db29aa2c48d2aefc39131e73bf
> --- /dev/null
> +++ b/security/lsm_audit_kfuncs.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (c) 2026 Cloudflare */
> +
> +#include <linux/audit.h>
> +#include <linux/bpf_mem_alloc.h>
> +#include <linux/gfp_types.h>
> +#include <linux/in6.h>
> +#include <linux/lsm_audit.h>
> +#include <linux/socket.h>
> +#include <linux/types.h>
> +
> +struct bpf_audit_context {
> +       struct audit_buffer *ab;
> +       u64 log_once_mask;
> +};
> +
> +static struct bpf_mem_alloc bpf_audit_context_ma;

bpf_mem_alloc will be deprecated, please use kmalloc_nolock().

> +
> +static inline u64 log_once(struct bpf_audit_context *ac, u64 mask)
> +{
> +       u64 set = (ac->log_once_mask & mask);
> +
> +       ac->log_once_mask |= mask;
> +       return set;
> +}
> +
> + [...]
> +
> +__bpf_kfunc int
> +bpf_audit_log_net_sockaddr(struct bpf_audit_context *ac, int netif,
> +                          const struct sockaddr *saddr__nullable,
> +                          const struct sockaddr *daddr__nullable, int addrlen)
> +{
> +       struct lsm_network_audit net;
> +       struct common_audit_data ad;
> +
> +       net.netif = netif;
> +
> +       if (!saddr__nullable && !daddr__nullable)
> +               return -EINVAL;

Code look a lot cleaner if you just stashed these locally in a better
named variable.

> +
> +       if (saddr__nullable && daddr__nullable &&
> +           saddr__nullable->sa_family != daddr__nullable->sa_family)
> +               return -EINVAL;
> +
> [...]
> +
> +/* The following have a recursion opportunity if a LSM is attached to any of
> + * the following functions, and a bpf_audit_log_*() is called.
> + *  security_current_getlsmprop_subj,
> + *  security_lsmprop_to_secctx, or
> + *  security_release_secctx
> + */
> +BTF_ID_FLAGS(func, bpf_audit_log_cause, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_cap, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_path, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_file, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_ioctl_op, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_dentry, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_inode, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_task, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_net_sock, KF_DESTRUCTIVE);
> +BTF_ID_FLAGS(func, bpf_audit_log_net_sockaddr, KF_DESTRUCTIVE);
> +
> +BTF_KFUNCS_END(lsm_audit_set_ids)
> +
> +static int bpf_lsm_audit_kfuncs_filter(const struct bpf_prog *prog,
> +                                      u32 kfunc_id)
> +{
> +       if (!btf_id_set8_contains(&lsm_audit_set_ids, kfunc_id))
> +               return 0;
> +
> +       return prog->type != BPF_PROG_TYPE_LSM ? -EACCES : 0;
> +}
> +
> +static const struct btf_kfunc_id_set bpf_lsm_audit_set = {
> +       .owner = THIS_MODULE,
> +       .set = &lsm_audit_set_ids,
> +       .filter = bpf_lsm_audit_kfuncs_filter,
> +};
> +
> +static int lsm_audit_init_bpf(void)
> +{
> +       int ret;
> +
> +       ret = bpf_mem_alloc_init(&bpf_audit_context_ma,
> +                                sizeof(struct bpf_audit_context), false);

One you switch to kmalloc_nolock you can drop this too.

> +       return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
> +                                                &bpf_lsm_audit_set);
> +}
> +
> +late_initcall(lsm_audit_init_bpf)
>
> --
> 2.43.0
>
>

^ permalink raw reply

* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
From: Kumar Kartikeya Dwivedi @ 2026-03-17  2:43 UTC (permalink / raw)
  To: Frederick Lawler
  Cc: Paul Moore, James Morris, Serge E. Hallyn, Eric Paris,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Shuah Khan, Mickaël Salaün, Günther Noack,
	linux-kernel, linux-security-module, audit, bpf, linux-kselftest,
	kernel-team
In-Reply-To: <20260311-bpf-auditd-send-message-v1-0-10a62db5c92f@cloudflare.com>

On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
>
> The motivation behind the change is to give BPF LSM developers the
> ability to report accesses via the audit subsystem much like how LSMs
> operate today.
>
> Series:
>
> Patch 1: Introduces bpf_audit_*() kfuncs
> Patch 2: Enables bpf_audit_*() kfuns
> Patch 3: Prepares audit helpers used for testing
> Patch 4: Adds self tests
>
> Documentation will be added when this becomes a versioned series.
>
> Key features:
>
> 1. Audit logs include type=AUDIT_BPF_LSM_ACCESS, BPF program ID, and comm
> that triggered the hook by default
>
> We wanted audit log consumers to be able to track who and what created
> the entry. prog-id=%d is already used for BPF LOAD/UNLOAD logs, thus
> is reused here for this distinction. Though, it may be better to use
> the tag instead to capture which _specific_ version of the program
> made the log, since prog-id can be reused.

I think just id is fine. Whoever is watching the prog can also watch
for LOAD/UNLOAD events and detect any recycling.

>
> 2. Leverages BPF KF_AQUIRE/KF_RELEASE semantics to force use of
>   bpf_audit_log_end().
>
> One side effect of this decision is that the BPF documentation states
> that these flags allow the pointer to struct bpf_audit_context to be
> stored in a map, and then exchanged through bpf_kptr_xchg(). However,

Yes, it is only allowed if you give a dtor. No dtor, then no kptr_xchg.

> there's prior work with net/netfilter/nf_conntrack_bpf.c such that the
> struct is not exposed as a kptr to support that functionality nor is
> that supplying a dtor function. The verifier will not allow this use case
> due to not exposing the __kptr. Ideally, we don't want the pointer to
> be exchanged anyway because the reporting program can become ambiguous.
> I am sure there are other edge cases WRT to keeping the audit buffer in a
> strange state too that I cannot think of at this moment.
>
> 3. All bpf_audit_log_*() functions are destructive
>
> The audit subsystem allows for AUDIT_FAIL_PANIC to be set when the
> subsystem can detect that missing events. Further, some call paths may
> invoke a BUG_ON(). Therefore all the functions are marked destructive.

I think the first part makes sense (i.e., the policy simply configured
the system to panic on failure).
However, in the second case, if the program is somehow able to trigger
BUG_ON() relied upon for internal invariants, it would be considered
broken.
I tried grepping through and didn't find anything that would cause
this, hence the whole thing about BUG_ON() in the cover letter only
adds to confusion.
Please drop it or describe cases which you were concerned about.

>
> 4. Functions are callable once per bpf_audit_context
>
> The rationale for this was to prevent abuse. Logs with repeated fields
> are not helpful, and may not be handled by user space audit coherently.
>

This rationale feels weak. What abuse are we talking about?
The LSM program is already written by a trusted entity.

> This is in the same vein as not providing a audit_format() wrapper.
>

I think the format() helper would allow for more flexibility.
I would like to hear what others think, but IMO we may not even want
to hardcode any fields by default and let the program decide what to
report.
prog-id, pid, comm, everything can be logged by the program itself, in
whatever order.

> Similarly, some functions such as bpf_audit_log_path() and
> bpf_audit_log_file() report the same information, thus can be
> interchangeable in use.
>
> 5. API wraps security/lsm_audit.c
>
> lsm_audit.c functions are multiplexed and not handled by BPF verifier
> very well, thus the wrapped functions are isolated to their sole
> purpose for use within hooks.

This part makes sense. I don't think we can (or should) support the
same multiplexing.

>
> Key considerations:
>
> 1. Audit field ordering
>
> AFAIK, user space audit is particular about what fields are
> present and their order. This patch series does not address ordering.
>
> My assumption is that the first three fields: type, prog-id, pid, comm
> are well known, and user space can make an assumption that other
> fields after those can appear in any order.
>
> If that is not acceptable, I would propose that we leverage the struct
> common_audit_data type order to be the order--much like how the type is
> used for log_once() functionality.
>
> I am open to other ideas.
>
> Signed-off-by: Frederick Lawler <fred@cloudflare.com>
> ---
> Frederick Lawler (4):
>       audit: Implement bpf_audit_log_*() wrappers
>       audit/security: Enable audit BPF kfuncs
>       selftests/bpf: Add audit helpers for BPF tests
>       selftests/bpf: Add lsm_audit_kfuncs tests
>
>  include/linux/lsm_audit.h                          |   1 +
>  include/uapi/linux/audit.h                         |   1 +
>  security/Makefile                                  |   2 +
>  security/lsm_audit_kfuncs.c                        | 306 +++++++++++
>  tools/testing/selftests/bpf/Makefile               |   3 +-
>  tools/testing/selftests/bpf/audit_helpers.c        | 281 ++++++++++
>  tools/testing/selftests/bpf/audit_helpers.h        |  55 ++
>  .../selftests/bpf/prog_tests/lsm_audit_kfuncs.c    | 598 +++++++++++++++++++++
>  .../selftests/bpf/progs/test_lsm_audit_kfuncs.c    | 263 +++++++++
>  9 files changed, 1509 insertions(+), 1 deletion(-)
> ---
> base-commit: ca0f39a369c5f927c3d004e63a5a778b08a9df94
> change-id: 20260105-bpf-auditd-send-message-4a883067aab8
>
> Best regards,
> --
> Frederick Lawler <fred@cloudflare.com>
>
>

^ permalink raw reply

* Re: [PATCH v2 1/2] keys/trusted_keys: clean up debug message logging in the tpm backend
From: Srish Srinivasan @ 2026-03-17  3:14 UTC (permalink / raw)
  To: Nayna Jain, linux-integrity, keyrings, Jarkko Sakkinen
  Cc: James.Bottomley, jarkko, zohar, stefanb, linux-kernel,
	linux-security-module
In-Reply-To: <7f8b8478-5cd8-4d97-bfd0-341fd5cf10f9@linux.ibm.com>


On 3/10/26 4:15 AM, Nayna Jain wrote:
>
> On 2/20/26 1:34 PM, Srish Srinivasan wrote:
>> The TPM trusted-keys backend uses a local TPM_DEBUG guard and pr_info()
>> for logging debug information.
>>
>> Replace pr_info() with pr_debug(), and use KERN_DEBUG for 
>> print_hex_dump().
>> Remove TPM_DEBUG.
>>
>> No functional change intended.
> There is functional change here.  This change allows secret and nonce 
> in the function dump_sess() to be logged to kernel logs when dynamic 
> debug is enabled. Previously, it was possible only in the debug builds 
> and not the production builds at runtime. With this change, it is 
> always there in production build. This can result in possible attack.


Hi Jarkko,
Could you please let us know your thoughts on this one?

And Nayna,
thanks for bringing this up.

thanks,
Srish.


>
> Instead of doing this change, I think add a comment to prevent this 
> sort of change in the future.
>
> Thanks & Regards,
>
>     - Nayna
>
>>
>> Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
>> Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
>> ---
>>   security/keys/trusted-keys/trusted_tpm1.c | 40 +++++++----------------
>>   1 file changed, 12 insertions(+), 28 deletions(-)
>>
>> diff --git a/security/keys/trusted-keys/trusted_tpm1.c 
>> b/security/keys/trusted-keys/trusted_tpm1.c
>> index c865c97aa1b4..216caef97ffc 100644
>> --- a/security/keys/trusted-keys/trusted_tpm1.c
>> +++ b/security/keys/trusted-keys/trusted_tpm1.c
>> @@ -46,28 +46,25 @@ enum {
>>       SRK_keytype = 4
>>   };
>>   -#define TPM_DEBUG 0
>> -
>> -#if TPM_DEBUG
>>   static inline void dump_options(struct trusted_key_options *o)
>>   {
>> -    pr_info("sealing key type %d\n", o->keytype);
>> -    pr_info("sealing key handle %0X\n", o->keyhandle);
>> -    pr_info("pcrlock %d\n", o->pcrlock);
>> -    pr_info("pcrinfo %d\n", o->pcrinfo_len);
>> -    print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE,
>> +    pr_debug("sealing key type %d\n", o->keytype);
>> +    pr_debug("sealing key handle %0X\n", o->keyhandle);
>> +    pr_debug("pcrlock %d\n", o->pcrlock);
>> +    pr_debug("pcrinfo %d\n", o->pcrinfo_len);
>> +    print_hex_dump(KERN_DEBUG, "pcrinfo ", DUMP_PREFIX_NONE,
>>                  16, 1, o->pcrinfo, o->pcrinfo_len, 0);
>>   }
>>     static inline void dump_sess(struct osapsess *s)
>>   {
>> -    print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE,
>> +    print_hex_dump(KERN_DEBUG, "trusted-key: handle ", 
>> DUMP_PREFIX_NONE,
>>                  16, 1, &s->handle, 4, 0);
>> -    pr_info("secret:\n");
>> -    print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
>> +    pr_debug("secret:\n");
>> +    print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE,
>>                  16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
>> -    pr_info("trusted-key: enonce:\n");
>> -    print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
>> +    pr_debug("trusted-key: enonce:\n");
>> +    print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE,
>>                  16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
>>   }
>>   @@ -75,23 +72,10 @@ static inline void dump_tpm_buf(unsigned char 
>> *buf)
>>   {
>>       int len;
>>   -    pr_info("\ntpm buffer\n");
>> +    pr_debug("\ntpm buffer\n");
>>       len = LOAD32(buf, TPM_SIZE_OFFSET);
>> -    print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 
>> 0);
>> -}
>> -#else
>> -static inline void dump_options(struct trusted_key_options *o)
>> -{
>> -}
>> -
>> -static inline void dump_sess(struct osapsess *s)
>> -{
>> -}
>> -
>> -static inline void dump_tpm_buf(unsigned char *buf)
>> -{
>> +    print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE, 16, 1, buf, 
>> len, 0);
>>   }
>> -#endif
>>     static int TSS_rawhmac(unsigned char *digest, const unsigned char 
>> *key,
>>                  unsigned int keylen, ...)

^ permalink raw reply

* Re: [PATCH 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls
From: Amir Goldstein @ 2026-03-17  7:25 UTC (permalink / raw)
  To: Paul Moore
  Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs, Gao Xiang
In-Reply-To: <CAHC9VhTEX-sRjyVi2p9j_jFjyWbzy54b=iteiTKGq-mnBaHkrg@mail.gmail.com>

On Mon, Mar 16, 2026 at 10:59 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Mon, Mar 16, 2026 at 5:36 PM Paul Moore <paul@paul-moore.com> wrote:
> >
> > The existing mmap() and mprotect() LSM access control points for the
> > overlayfs filesystem are incomplete in that they do not cover both the
> > user and backing files.  This patchset corrects this through the addition
> > of a new backing file specific LSM hook, security_mmap_backing_file(),
> > a new user path file associated with a backing file that can be used by
> > LSMs in the security_file_mprotect() code path, and the associated
> > SELinux code changes.
> >
> > The security_mmap_backing_file() hook is intended to allow LSMs to apply
> > access controls on mmap() operations accessing a backing file, similar to
> > the security_mmap_file() for user files.  Due to the details around the
> > accesses and the desire to distinguish between the two types of accesses,
> > a new LSM hook was needed.  More information on this new hook can be
> > found in the associated patch.
> >
> > The new user path file replaces the existing user path stored in the
> > backing file.  This change was necessary to support LSM based access
> > controls in the mprotect() code path where only one file is accessible
> > via the vma->vm_file field.  Unfortunately, storing a reference to the
> > user file inside the backing file does not work due to the cyclic
> > ref counting so a stand-in was necessary, the new user O_PATH file.
> > This new O_PATH file is intended to be representative of the original
> > user file and can be used by LSMs to make access control decisions based
> > on both the backing and user files.
> >
> > The SELinux changes in this patchset involve making use of the new
> > security_mmap_backing_file() hook and updating the existing mprotect()
> > access controls to take into account both the backing and user files.
> > These changes preserve the existing SELinux approach of allowing access
> > on overlayfs files if the current task has the necessary rights to the
> > user file and the mounting process has the necessary rights to the
> > underlying backing file.
> >
> > --
> > Amir Goldstein (1):
> >       backing_file: store user_path_file
> >
> > Paul Moore (2):
> >       lsm: add the security_mmap_backing_file() hook
> >       selinux: fix overlayfs mmap() and mprotect() access checks
> >
> >  fs/backing-file.c             |   28 +++++---
> >  fs/erofs/ishare.c             |   12 ++-
> >  fs/file_table.c               |   53 +++++++++++++---
> >  fs/fuse/passthrough.c         |    3
> >  fs/internal.h                 |    5 -
> >  fs/overlayfs/dir.c            |    3
> >  fs/overlayfs/file.c           |    1
> >  include/linux/backing-file.h  |   29 ++++++++-
> >  include/linux/file_ref.h      |   10 ---
> >  include/linux/lsm_audit.h     |    2
> >  include/linux/lsm_hook_defs.h |    2
> >  include/linux/security.h      |   10 +++
> >  security/security.c           |   25 +++++++
> >  security/selinux/hooks.c      |  108 ++++++++++++++++++++++++++++------
> >  14 files changed, 231 insertions(+), 60 deletions(-)
>
> Due to the nature of the issue, I'm going to merge this into
> lsm/stable-7.0 in a few moments so the changes can get some testing in
> linux-next with the idea of sending this up to Linus' later in the
> week.  If anyone has any concerns over this patchset, please let me
> know as soon as possible.
>

Since previous 4 revisions were not posted to public list,
let me repeat my concern from v4:

On Fri, Mar 6, 2026 at 5:24 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Fri, Mar 6, 2026 at 3:24 AM Amir Goldstein <amir73il@gmail.com> wrote:
...
> > My expectation is that the merge of this patch will be collaborated
> > with Christian ...
>
> Of course, that is one reason he is on the To/CC line.  More on this
> in my reply to your 0/4 comments.
>

I am sorry Paul. This must be a misunderstanding.

My expectation for collaborating the merge of my patch with
Christian was that an agreement would be reached regarding
which way it would be routed to Linus.

CC to Christian and sending the patch to Linus without getting any
ACK from Christian was not the way I expected this to go.

> > and that it will NOT be auto selected or rushed into stable.
>
> I haven't marked it with a 'Fixes:' tag or a stable Cc which in my
> experience are the two quickest ways to get pulled into a stable tree.
> I'm not sure what stable policy Al or Christian have for the VFS tree,
> but LSM and SELinux commits are not pulled into the stable trees
> unless explicitly marked with a stable Cc or requested by a dev after
> the fact.
>
> > I don't mind if you want to route the security_mmap_backing_file() hooks to
> > stable to use it for some simpler bandaid for stable, but rushing this
> > one to stable is not a good idea IMO.
>
> Once again, see my (upcoming) reply to your 0/4 comments.
>

TBH, I don't understand the logic of placing patches in lsm/stable-7.0
without the intent of backporting them to stable.

I perceive my patch as a risky patch for overlayfs and the vfs
this is why I wanted Christian do be part of the decision if and when
my patch is sent to Linus.

Thanks,
Amir.

^ permalink raw reply

* [PATCH] securityfs: use kstrdup_const() to manage symlink targets
From: Dmitry Antipov @ 2026-03-17 14:11 UTC (permalink / raw)
  To: Paul Moore, James Morris, Serge E. Hallyn
  Cc: linux-security-module, Dmitry Antipov

Since 'target' argument of 'securityfs_create_symlink()' is (for
now at least) a compile-time constant, it may be reasonable to
use 'kstrdup_const()' / 'kree_const()' to manage 'i_link' member
of the corresponding inode in attempt to reuse .rodata instance
rather than making a copy.

Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
 security/inode.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/security/inode.c b/security/inode.c
index 81fb5d6dd33e..080402367674 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -30,7 +30,7 @@ static int mount_count;
 static void securityfs_free_inode(struct inode *inode)
 {
 	if (S_ISLNK(inode->i_mode))
-		kfree(inode->i_link);
+		kfree_const(inode->i_link);
 	free_inode_nonrcu(inode);
 }
 
@@ -258,17 +258,17 @@ struct dentry *securityfs_create_symlink(const char *name,
 					 const struct inode_operations *iops)
 {
 	struct dentry *dent;
-	char *link = NULL;
+	const char *link = NULL;
 
 	if (target) {
-		link = kstrdup(target, GFP_KERNEL);
+		link = kstrdup_const(target, GFP_KERNEL);
 		if (!link)
 			return ERR_PTR(-ENOMEM);
 	}
 	dent = securityfs_create_dentry(name, S_IFLNK | 0444, parent,
-					link, NULL, iops);
+					(void *)link, NULL, iops);
 	if (IS_ERR(dent))
-		kfree(link);
+		kfree_const(link);
 
 	return dent;
 }
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v3 0/2] vfs: follow-on fixes for i_ino widening
From: Christian Brauner @ 2026-03-17 14:39 UTC (permalink / raw)
  To: Jeff Layton
  Cc: Christian Brauner, David Laight, linux-nilfs, linux-kernel,
	linux-integrity, linux-security-module, linux-fsdevel,
	kernel test robot, Ryusuke Konishi, Viacheslav Dubeyko,
	Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
	Paul Moore, James Morris, Serge E. Hallyn
In-Reply-To: <20260316-iino-u64-v3-0-d1076b8f7a20@kernel.org>

On Mon, 16 Mar 2026 15:02:21 -0400, Jeff Layton wrote:
> Just some patches to fix follow-on issues reported after the
> inode->i_ino widening series. Christian, could you toss these
> onto the vfs-7.1.kino branch?

Applied to the vfs-7.1.kino branch of the vfs/vfs.git tree.
Patches in the vfs-7.1.kino branch should appear in linux-next soon.

Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.

It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.

Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.1.kino

[1/2] EVM: add comment describing why ino field is still unsigned long
      https://git.kernel.org/vfs/vfs/c/bef5b11087ce
[2/2] nilfs2: fix 64-bit division operations in nilfs_bmap_find_target_in_group()
      https://git.kernel.org/vfs/vfs/c/81359c146fba

^ permalink raw reply

* Re: [PATCH 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls
From: Paul Moore @ 2026-03-17 18:16 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs, Gao Xiang
In-Reply-To: <CAOQ4uxi7+6Qt5K9s6Fq8deN-ep2gnxqJ6-wJy9pXJzszpfn-6A@mail.gmail.com>

On Tue, Mar 17, 2026 at 3:26 AM Amir Goldstein <amir73il@gmail.com> wrote:
> On Mon, Mar 16, 2026 at 10:59 PM Paul Moore <paul@paul-moore.com> wrote:
> > On Mon, Mar 16, 2026 at 5:36 PM Paul Moore <paul@paul-moore.com> wrote:

...

> > Due to the nature of the issue, I'm going to merge this into
> > lsm/stable-7.0 in a few moments so the changes can get some testing in
> > linux-next with the idea of sending this up to Linus' later in the
> > week.  If anyone has any concerns over this patchset, please let me
> > know as soon as possible.
>
> Since previous 4 revisions were not posted to public list,
> let me repeat my concern from v4:
>
> On Fri, Mar 6, 2026 at 5:24 PM Paul Moore <paul@paul-moore.com> wrote:
> > On Fri, Mar 6, 2026 at 3:24 AM Amir Goldstein <amir73il@gmail.com> wrote:
> ...
> > > My expectation is that the merge of this patch will be collaborated
> > > with Christian ...
> >
> > Of course, that is one reason he is on the To/CC line.  More on this
> > in my reply to your 0/4 comments.
> >
>
> I am sorry Paul. This must be a misunderstanding.
>
> My expectation for collaborating the merge of my patch with
> Christian was that an agreement would be reached regarding
> which way it would be routed to Linus.
>
> CC to Christian and sending the patch to Linus without getting any
> ACK from Christian was not the way I expected this to go.

To be honest I expected Christian to provide an ACK by now, as he
reviewed and commented on your earlier patches with the O_PATH file
approach.  He has had weeks, if not months, to comment further and/or
supply an ACK so at this point I'm proceeding ahead as I mentioned to
you (and Christian last week).  You were very anxious to bring these
patches on-list, which I did, however, now that everything is on-list
there is a responsibility to act on this quickly (this was the
motivation for discussing the solution privately at first).

Hopefully Christian will comment, and preferably ACK your patch, but
even if he does not we still need to go ahead and fix this soon in
Linus' tree.

> > > and that it will NOT be auto selected or rushed into stable.
> >
> > I haven't marked it with a 'Fixes:' tag or a stable Cc which in my
> > experience are the two quickest ways to get pulled into a stable tree.
> > I'm not sure what stable policy Al or Christian have for the VFS tree,
> > but LSM and SELinux commits are not pulled into the stable trees
> > unless explicitly marked with a stable Cc or requested by a dev after
> > the fact.
> >
> > > I don't mind if you want to route the security_mmap_backing_file() hooks to
> > > stable to use it for some simpler bandaid for stable, but rushing this
> > > one to stable is not a good idea IMO.
> >
> > Once again, see my (upcoming) reply to your 0/4 comments.
>
> TBH, I don't understand the logic of placing patches in lsm/stable-7.0
> without the intent of backporting them to stable.

I'm not going to copy-n-paste my previous off-list reply, as I have a
rather strict policy about forwarding private or off-list emails to a
public list without consent.  However, the quick answer is that
inclusion in a lsm/stable-X.Y branch does not mean a patch(set) is
automatically tagged for stable, in fact you'll notice none of the
patches have a stable marking, mostly due to your previous request.
As the LSM and SELinux trees are not pulled into the stables trees
unless explicitly marked, or requested, I do not expect those patches
to be automatically backported.  I do not know the stable backport
policy for VFS patches.

> I perceive my patch as a risky patch for overlayfs and the vfs
> this is why I wanted Christian do be part of the decision if and when
> my patch is sent to Linus.

Christian has been a part of the discussion for months now, and has
already provided feedback on the VFS portions of this patchset (which
you have incorporated).  I agree, I would appreciate it if Christian
could supply his ACK, or an alternate solution, but as you strongly
encouraged us to bring this issue on-list, we now need to get a fix in
Linus' tree soon.

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH RFC 1/4] audit: Implement bpf_audit_log_*() wrappers
From: Mickaël Salaün @ 2026-03-17 19:12 UTC (permalink / raw)
  To: David Windsor
  Cc: fred, paul, bpf, linux-security-module, audit, ast, daniel,
	andrii, martin.lau, kpsingh
In-Reply-To: <20260316221440.2043299-1-dwindsor@gmail.com>

On Mon, Mar 16, 2026 at 06:14:40PM -0400, David Windsor wrote:
> Hi Frederick,
> 
> On Wed, Mar 11, 2026 at 04:31:17PM -0500, Frederick Lawler wrote:
> > +__bpf_kfunc int bpf_audit_log_cause(struct bpf_audit_context *ac,
> > +				    const char *cause__str)
> > +{
> > +	if (log_once(ac, BIT_ULL(LSM_AUDIT_DATA_CAUSE)))
> > +		return -EINVAL;
> > +
> > +	audit_log_format(ac->ab, " cause=");
> > +	audit_log_untrustedstring(ac->ab, cause__str);
> > +	return 0;
> > +}
> 
> Rather than putting everything in the cause field, could we perhaps
> have a separate kfunc here that appends normal stringpairs (not
> format strings) to the audit record:
> 
>   bpf_audit_log_str(ac, "result", "denied");
>   bpf_audit_log_str(ac, "op", "read");
>   bpf_audit_log_str(ac, "scontext", ctx_str);

That would mean arbitrary audit keys (and values), which would not be
acceptable (i.e. no consistency).

> 
> I know you didn't want to wrap audit_log_format(), which makes sense,
> this would be a midway point between that and stuffing everything in
> one field.

^ permalink raw reply

* Re: [PATCH v3 1/3] ima: Remove ima_h_table structure
From: Mimi Zohar @ 2026-03-17 19:15 UTC (permalink / raw)
  To: Roberto Sassu, corbet, skhan, dmitry.kasatkin, eric.snowberg,
	paul, jmorris, serge
  Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
	gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260311171956.2317781-1-roberto.sassu@huaweicloud.com>

On Wed, 2026-03-11 at 18:19 +0100, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
> 
> With the upcoming change of dynamically allocating and replacing the hash
> table, we would need to keep the counters for number of measurements
> entries and violations.
> 
> Since anyway, those counters don't belong there, remove the ima_h_table
> structure instead and move the counters and the hash table as a separate
> variables.

There's no cover letter or motivation in this patch description for needing to
"dynamically allocating or replacing the existing hash table."

Saying that the htable, number of records in the measurement list, and violation
counter don't belong grouped together is insufficient.  There must have been a
valid reason for why they were grouped together originally (e.g. never removed
or reset).

Please provide a motivation for removing the ima_h_table struct and its usage
and defining them independently of each other.

thanks,

Mimi

^ permalink raw reply

* Re: [PATCH v3 3/3] ima: Add support for staging measurements for deletion
From: Mimi Zohar @ 2026-03-17 21:03 UTC (permalink / raw)
  To: Roberto Sassu, corbet, skhan, dmitry.kasatkin, eric.snowberg,
	paul, jmorris, serge
  Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
	gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260311171956.2317781-3-roberto.sassu@huaweicloud.com>

Hi Roberto,

On Wed, 2026-03-11 at 18:19 +0100, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
> 
> Introduce the ability of staging the IMA measurement list for deletion.
> Staging means moving the current content of the measurement list to a
> separate location, and allowing users to read and delete it. This causes
> the measurement list to be atomically truncated before new measurements can
> be added.

I really like this design of atomically moving and subsequently deleting the
measurement list.  However this is a solution, not the motivation for the patch.
Please include the motivation for the patch, before describing the solution.

> Staging can be done only once at a time. In the event of kexec(),
> staging is reverted and staged entries will be carried over to the new
> kernel.

> 
> Staged measurements can be deleted entirely, or partially, with the
> non-deleted ones added back to the IMA measurements list. 

This patch description is really long, which is an indication that the patch
needs to be split up.  Adding support for partially deleting the measurement
list records, by prepending the remaining measurement records, should be a
separate patch.

> This allows the
> remote attestation agents to easily separate the measurements that where
> verified (staged and deleted) from those that weren't due to the race
> between taking a TPM quote and reading the measurements list.
> 
> User space is responsible to concatenate the staged IMA measurements list
> portions (excluding the measurements added back to the IMA measurements
> list) following the temporal order in which the operations were done,
> together with the current measurement list. Then, it can send the collected
> data to the remote verifiers.

This belongs in a Documentation patch. 

> 
> The benefit of staging and deleting is the ability to free precious kernel
> memory, 

This is the motivation for the patch.

> in exchange of delegating user space to reconstruct the full
> measurement list from the chunks. No trust needs to be given to user space,
> since the integrity of the measurement list is protected by the TPM.

Agreed the measurement list, itself, is protected by the TPM.  However, relying
on userspace to reassemble the chunks is another concern. Support for staging
and deleting the measurement list should be configurable.  Defining a Kconfig
should be part of this initial patch.

> By default, staging the measurements list does not alter the hash table.
> When staging and deleting are done, IMA is still able to detect collisions
> on the staged and later deleted measurement entries, by keeping the entry
> digests (only template data are freed).
> 
> However, since during the measurements list serialization only the SHA1
> digest is passed, and since there are no template data to recalculate the
> other digests from, the hash table is currently not populated with digests
> from staged/deleted entries after kexec().
> 
> Introduce the new kernel option ima_flush_htable to decide whether or not
> the digests of staged measurement entries are flushed from the hash table,
> when they are deleted. Flushing the hash table is supported only when
> deleting all the staged measurements, since in that case the old hash table
> can be quickly swapped with a blank one (otherwise entries would have to be
> removed one by one for partial deletion).

Allowing the hash table to be deleted would be an example of another patch.

> 
> Then, introduce ascii_runtime_measurements_<algo>_staged and
> binary_runtime_measurements_<algo>_staged interfaces to stage and delete
> the measurements. Use 'echo A > <IMA interface>' and
> 'echo D > <IMA interface>' to respectively stage and delete the entire
> measurements list. Use 'echo N > <IMA interface>', with N between 1 and
> ULONG_MAX - 1, to delete the selected staged portion of the measurements
> list.
> 
> The ima_measure_users counter (protected by the ima_measure_mutex mutex)
> has been introduced to protect access to the measurements list and the
> staged part. The open method of all the measurement interfaces has been
> extended to allow only one writer at a time or, in alternative, multiple
> readers. The write permission is used to stage and delete the measurements,
> the read permission to read them. Write requires also the CAP_SYS_ADMIN
> capability.

Yes, this is part of the initial patch that adds support for staging the
measurement list.

> 
> Finally, introduce the binary_lists enum and make binary_runtime_size
> and ima_num_entries as arrays, to keep track of their values for the
> current IMA measurements list (BINARY), current list plus staged
> measurements (BINARY_STAGED) and the cumulative list since IMA
> initialization (BINARY_FULL).
> 
> Use BINARY in ima_show_measurements_count(), BINARY_STAGED in
> ima_add_kexec_buffer() and BINARY_FULL in ima_measure_kexec_event().
> 
> It should be noted that the BINARY_FULL counter is not passed through
> kexec. Thus, the number of entries included in the kexec critical data
> records refers to the entries since the previous kexec records.
> 
> Note: This code derives from the Alt-IMA Huawei project, whose license is
>       GPL-2.0 OR MIT.
> 
> Link: https://github.com/linux-integrity/linux/issues/1
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>

The design looks good.  As I mentioned above, this patch description is quite
long, which is an indication that the patch needs to be split up.  One method of
breaking it up would be:

- (Basic) support for staging measurements for deletion (based on a Kconfig)
- Support for removing the hash table
- Support for deleting N measurement records (and pre-pending the remaining
measurement records)
- Adding documentation

thanks,

Mimi

^ permalink raw reply

* Re: [PATCH] securityfs: use kstrdup_const() to manage symlink targets
From: Paul Moore @ 2026-03-17 21:13 UTC (permalink / raw)
  To: Dmitry Antipov, James Morris, Serge E. Hallyn
  Cc: linux-security-module, Dmitry Antipov
In-Reply-To: <20260317141135.133339-1-dmantipov@yandex.ru>

On Mar 17, 2026 Dmitry Antipov <dmantipov@yandex.ru> wrote:
> 
> Since 'target' argument of 'securityfs_create_symlink()' is (for
> now at least) a compile-time constant, it may be reasonable to
> use 'kstrdup_const()' / 'kree_const()' to manage 'i_link' member
> of the corresponding inode in attempt to reuse .rodata instance
> rather than making a copy.
> 
> Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
> ---
>  security/inode.c | 10 +++++-----
>  1 file changed, 5 insertions(+), 5 deletions(-)

Seems reasonable, we can easily back this out if we ever need to support
non-const link target strings.  Merged into lsm/dev, thanks.

--
paul-moore.com

^ permalink raw reply

* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
From: Mickaël Salaün @ 2026-03-17 21:14 UTC (permalink / raw)
  To: Paul Moore, Sebastian Andrzej Siewior, Kuniyuki Iwashima,
	Simon Horman, Jakub Kicinski, netdev
  Cc: Günther Noack, John Johansen, James Morris, Serge E . Hallyn,
	Tingmao Wang, Justin Suess, linux-security-module,
	Samasth Norway Ananda, Matthieu Buffet, Mikhail Ivanov,
	konstantin.meskhidze, Demi Marie Obenour, Alyssa Ross, Jann Horn,
	Tahera Fahimi, Alexander Viro, Christian Brauner
In-Reply-To: <20260315222150.121952-2-gnoack3000@gmail.com>

Paul and netdev folks, I think this new hook is now good.  I'd like to
push this series to -next this week.  Please let us know if you find any
issue, otherwise I guess we'll get more eyes and bots in the -next tree.

On Sun, Mar 15, 2026 at 11:21:42PM +0100, Günther Noack wrote:
> From: Justin Suess <utilityemal77@gmail.com>
> 
> Add a LSM hook security_unix_find.
> 
> This hook is called to check the path of a named unix socket before a
> connection is initiated. The peer socket may be inspected as well.
> 
> Why existing hooks are unsuitable:
> 
> Existing socket hooks, security_unix_stream_connect(),
> security_unix_may_send(), and security_socket_connect() don't provide
> TOCTOU-free / namespace independent access to the paths of sockets.
> 
> (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> This requires another path lookup. A change in the path between the
> two lookups will cause a TOCTOU bug.
> 
> (2) We cannot use the struct path from the listening socket, because it
> may be bound to a path in a different namespace than the caller,
> resulting in a path that cannot be referenced at policy creation time.
> 
> Cc: Günther Noack <gnoack3000@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
>  include/linux/lsm_hook_defs.h |  5 +++++
>  include/linux/security.h      | 11 +++++++++++
>  net/unix/af_unix.c            | 13 ++++++++++---
>  security/security.c           | 20 ++++++++++++++++++++
>  4 files changed, 46 insertions(+), 3 deletions(-)
> 
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..7a0fd3dbfa29 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -317,6 +317,11 @@ LSM_HOOK(int, 0, post_notification, const struct cred *w_cred,
>  LSM_HOOK(int, 0, watch_key, struct key *key)
>  #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> +	 int flags)
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
>  #ifdef CONFIG_SECURITY_NETWORK
>  LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
>  	 struct sock *newsk)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..99a33d8eb28d 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1931,6 +1931,17 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>  }
>  #endif	/* CONFIG_SECURITY_NETWORK */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +
> +#else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
>  #ifdef CONFIG_SECURITY_INFINIBAND
>  int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey);
>  int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num);
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 3756a93dc63a..aced28179bac 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1231,11 +1231,18 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
>  		goto path_put;
>  
>  	err = -EPROTOTYPE;
> -	if (sk->sk_type == type)
> -		touch_atime(&path);
> -	else
> +	if (sk->sk_type != type)
>  		goto sock_put;
>  
> +	/*
> +	 * We call the hook because we know that the inode is a socket and we
> +	 * hold a valid reference to it via the path.
> +	 */
> +	err = security_unix_find(&path, sk, flags);
> +	if (err)
> +		goto sock_put;
> +	touch_atime(&path);
> +
>  	path_put(&path);
>  
>  	return sk;
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..c73196b8db4b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -4731,6 +4731,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>  
>  #endif	/* CONFIG_SECURITY_NETWORK */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +/**
> + * security_unix_find() - Check if a named AF_UNIX socket can connect
> + * @path: path of the socket being connected to
> + * @other: peer sock
> + * @flags: flags associated with the socket
> + *
> + * This hook is called to check permissions before connecting to a named
> + * AF_UNIX socket.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +	return call_int_hook(unix_find, path, other, flags);
> +}
> +EXPORT_SYMBOL(security_unix_find);
> +
> +#endif	/* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +
>  #ifdef CONFIG_SECURITY_INFINIBAND
>  /**
>   * security_ib_pkey_access() - Check if access to an IB pkey is allowed
> -- 
> 2.53.0
> 
> 

^ permalink raw reply

* Re: [PATCH v5 2/9] landlock: Control pathname UNIX domain socket resolution by path
From: Mickaël Salaün @ 2026-03-17 21:14 UTC (permalink / raw)
  To: Günther Noack
  Cc: Günther Noack, John Johansen, Tingmao Wang, Justin Suess,
	Jann Horn, linux-security-module, Samasth Norway Ananda,
	Matthieu Buffet, Mikhail Ivanov, konstantin.meskhidze,
	Demi Marie Obenour, Alyssa Ross, Tahera Fahimi
In-Reply-To: <20260314.f83f9a697865@gnoack.org>

On Sun, Mar 15, 2026 at 12:15:10AM +0100, Günther Noack wrote:
> On Sun, Mar 08, 2026 at 12:50:06PM +0100, Mickaël Salaün wrote:
> > On Sun, Mar 08, 2026 at 10:09:52AM +0100, Mickaël Salaün wrote:
> > > On Thu, Feb 19, 2026 at 02:59:38PM +0100, Günther Noack wrote:
> > > > On Thu, Feb 19, 2026 at 10:45:44AM +0100, Mickaël Salaün wrote:
> > > > > On Wed, Feb 18, 2026 at 10:37:16AM +0100, Mickaël Salaün wrote:
> > > > > > On Sun, Feb 15, 2026 at 11:51:50AM +0100, Günther Noack wrote:
> > > > > > > * Add a new access right LANDLOCK_ACCESS_FS_RESOLVE_UNIX, which
> > > > > > >   controls the look up operations for named UNIX domain sockets.  The
> > > > > > >   resolution happens during connect() and sendmsg() (depending on
> > > > > > >   socket type).
> > > > > > > * Hook into the path lookup in unix_find_bsd() in af_unix.c, using a
> > > > > > >   LSM hook.  Make policy decisions based on the new access rights
> > > > > > > * Increment the Landlock ABI version.
> > > > > > > * Minor test adaptions to keep the tests working.
> > > > > > > 
> > > > > > > With this access right, access is granted if either of the following
> > > > > > > conditions is met:
> > > > > > > 
> > > > > > > * The target socket's filesystem path was allow-listed using a
> > > > > > >   LANDLOCK_RULE_PATH_BENEATH rule, *or*:
> > > > > > > * The target socket was created in the same Landlock domain in which
> > > > > > >   LANDLOCK_ACCESS_FS_RESOLVE_UNIX was restricted.
> > > > > > > 
> > > > > > > In case of a denial, connect() and sendmsg() return EACCES, which is
> > > > > > > the same error as it is returned if the user does not have the write
> > > > > > > bit in the traditional Unix file system permissions of that file.
> > > > > > > 
> > > > > > > This feature was created with substantial discussion and input from
> > > > > > > Justin Suess, Tingmao Wang and Mickaël Salaün.
> > > > > > > 
> > > > > > > Cc: Tingmao Wang <m@maowtm.org>
> > > > > > > Cc: Justin Suess <utilityemal77@gmail.com>
> > > > > > > Cc: Mickaël Salaün <mic@digikod.net>
> > > > > > > Suggested-by: Jann Horn <jannh@google.com>
> > > > > > > Link: https://github.com/landlock-lsm/linux/issues/36
> > > > > > > Signed-off-by: Günther Noack <gnoack3000@gmail.com>
> > > > > > > ---
> > > > > > >  include/uapi/linux/landlock.h                |  10 ++
> > > > > > >  security/landlock/access.h                   |  11 +-
> > > > > > >  security/landlock/audit.c                    |   1 +
> > > > > > >  security/landlock/fs.c                       | 102 ++++++++++++++++++-
> > > > > > >  security/landlock/limits.h                   |   2 +-
> > > > > > >  security/landlock/syscalls.c                 |   2 +-
> > > > > > >  tools/testing/selftests/landlock/base_test.c |   2 +-
> > > > > > >  tools/testing/selftests/landlock/fs_test.c   |   5 +-
> > > > > > >  8 files changed, 128 insertions(+), 7 deletions(-)
> > > > > 
> > > > > > > index 60ff217ab95b..8d0edf94037d 100644
> > > > > > > --- a/security/landlock/audit.c
> > > > > > > +++ b/security/landlock/audit.c
> > > > > > > @@ -37,6 +37,7 @@ static const char *const fs_access_strings[] = {
> > > > > > >  	[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
> > > > > > >  	[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
> > > > > > >  	[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
> > > > > > > +	[BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
> > > > > > >  };
> > > > > > >  
> > > > > > >  static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
> > > > > > > diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> > > > > > > index e764470f588c..76035c6f2bf1 100644
> > > > > > > --- a/security/landlock/fs.c
> > > > > > > +++ b/security/landlock/fs.c
> > > > > > > @@ -27,6 +27,7 @@
> > > > > > >  #include <linux/lsm_hooks.h>
> > > > > > >  #include <linux/mount.h>
> > > > > > >  #include <linux/namei.h>
> > > > > > > +#include <linux/net.h>
> > > > > > >  #include <linux/path.h>
> > > > > > >  #include <linux/pid.h>
> > > > > > >  #include <linux/rcupdate.h>
> > > > > > > @@ -314,7 +315,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
> > > > > > >  	LANDLOCK_ACCESS_FS_WRITE_FILE | \
> > > > > > >  	LANDLOCK_ACCESS_FS_READ_FILE | \
> > > > > > >  	LANDLOCK_ACCESS_FS_TRUNCATE | \
> > > > > > > -	LANDLOCK_ACCESS_FS_IOCTL_DEV)
> > > > > > > +	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
> > > > > > > +	LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> > > > > > >  /* clang-format on */
> > > > > > >  
> > > > > > >  /*
> > > > > > > @@ -1561,6 +1563,103 @@ static int hook_path_truncate(const struct path *const path)
> > > > > > >  	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
> > > > > > >  }
> > > > > > >  
> > > > > > > +/**
> > > > > > > + * unmask_scoped_access - Remove access right bits in @masks in all layers
> > > > > > > + *                        where @client and @server have the same domain
> > > > > > > + *
> > > > > > > + * This does the same as domain_is_scoped(), but unmasks bits in @masks.
> > > > > > > + * It can not return early as domain_is_scoped() does.
> > > > > 
> > > > > Why can't we use the same logic as for other scopes?
> > > > 
> > > > The other scopes, for which this is implemented in domain_is_scoped(),
> > > > do not need to do this layer-by-layer.
> > > > 
> > > > I have to admit, in my initial implementation, I was using
> > > > domain_is_scoped() directly, and the logic at the end of the hook was
> > > > roughly:
> > > > 
> > > >    --- BUGGY CODE START ---
> > > >        // ...
> > > >        
> > > >        if (!domain_is_scoped(..., ..., LANDLOCK_ACCESS_FS_RESOLVE_UNIX))
> > > >            return 0;  /* permitted */
> > > > 
> > > >        return current_check_access_path(path, LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
> > > >    }
> > > >    --- BUGGY CODE END ---
> > > > 
> > > > Unfortunately, that is a logic error though -- it implements the formula
> > > > 
> > > >    Access granted if:
> > > >    (FOR-ALL l ∈ layers scoped-access-ok(l)) OR (FOR-ALL l ∈ layers path-access-ok(l))     (WRONG!)
> > > > 
> > > > but the formula we want is:
> > > > 
> > > >    Access granted if:
> > > >    FOR-ALL l ∈ layers (scoped-access-ok(l) OR path-access-ok(l))     (CORRECT!)
> > > 
> > > It is worth it to add this explanation to the unmask_scoped_access()
> > > description, also pointing to the test that check this case.
> > > 
> > > > 
> > > > This makes a difference in the case where (pseudocode):
> > > > 
> > > >    1. landlock_restrict_self(RESOLVE_UNIX)  // d1
> > > >    2. create_unix_server("./sock")
> > > >    3. landlock_restrict_self(RESOLVE_UNIX, rule=Allow(".", RESOLVE_UNIX))  // d2
> > > >    4. connect_unix("./sock")
> > > > 
> > > >    ,------------------------------------------------d1--,
> > > >    |                                                    |
> > > >    |    ./sock server                                   |
> > > >    |       ^                                            |
> > > >    |       |                                            |
> > > >    |  ,------------------------------------------d2--,  |
> > > >    |  |    |                                         |  |
> > > >    |  |  client                                      |  |
> > > >    |  |                                              |  |
> > > >    |  '----------------------------------------------'  |
> > > >    |                                                    |
> > > >    '----------------------------------------------------'
> > > > 
> > > > (BTW, this scenario is covered in the selftests, that is why there is
> > > > a variant of these selftests where instead of applying "no domain", we
> > > > apply a domain with an exception rule like in step 3 in the pseudocode
> > > > above.  Applying that domain should behave the same as applying no
> > > > domain at all.)
> > > > 
> > > > Intuitively, it is clear that the access should be granted:
> > > > 
> > > >   - d1 does not restrict access to the server,
> > > >     because the socket was created within d1 itself.
> > > >   - d2 does not restrict access to the server,
> > > >     because it has a rule to allow it
> > > > 
> > > > But the "buggy code" logic above comes to a different conclusion:
> > > > 
> > > >   - the domain_is_scoped() check denies the access, because the server
> > > >     is in a more privileged domain relative to the client domain.
> > > >   - the current_check_access_path() check denies the access as well,
> > > >     because the socket's path is not allow-listed in d1.
> > > > 
> > > > In the 'intuitive' reasoning above, we are checking d1 and d2
> > > > independently of each other.  While Landlock is not implemented like
> > > > that internally, we need to stay consistent with it so that domains
> > > > compose correctly.  The way to do that is to track is access check
> > > > results on a per-layer basis again, and that is why
> > > > unmask_scoped_access() uses a layer mask for tracking.  The original
> > > > domain_is_scoped() does not use a layer mask, but that also means that
> > > > it can return early in some scenarios -- if for any of the relevant
> > > > layer depths, the client and server domains are not the same, it exits
> > > > early with failure because it's overall not fulfillable any more.  In
> > > > the RESOLVE_UNIX case though, we need to remember in which layers we
> > > > failed (both high an low ones), because these layers can still be
> > > > fulfilled with a PATH_BENEATH rule later.
> > > > 
> > > > Summary:
> > > > 
> > > > Option 1: We *can* unify this if you want.  It just might come at a
> > > > small performance penalty for domain_is_scoped(), which now uses the
> > > > larger layer mask data structure and can't do the same early returns
> > > > any more as before.
> > > > 
> > > > Option 2: Alternatively, if we move the two functions into the same
> > > > module, we can keep them separate but still test them against each
> > > > other to make sure they are in-line:
> > > > 
> > > > This invocation should return true...
> > > > 
> > > >   domain_is_scoped(cli, srv, access)
> > > > 
> > > > ...in the exactly the same situations where this invocation leaves any
> > > > bits set in layer_masks:
> > > > 
> > > >   landlock_init_layer_masks(dom, access, &layer_masks, LL_KEY_INODE);
> > > >   unmask_scoped_access(cli, srv, &layer_masks, access);
> > > > 
> > > > What do you prefer?
> > > 
> > > I was thinking about factoring out domain_is_scoped() with
> > > unmask_scoped_access() but, after some tests, it is not worth it.  Your
> > > approach is simple and good.
> > > 
> > > > 
> > > > 
> > > > > > > + *
> > > > > > > + * @client: Client domain
> > > > > > > + * @server: Server domain
> > > > > > > + * @masks: Layer access masks to unmask
> > > > > > > + * @access: Access bit that controls scoping
> > > > > > > + */
> > > > > > > +static void unmask_scoped_access(const struct landlock_ruleset *const client,
> > > > > > > +				 const struct landlock_ruleset *const server,
> > > > > > > +				 struct layer_access_masks *const masks,
> > > > > > > +				 const access_mask_t access)
> > > > > > 
> > > > > > This helper should be moved to task.c and factored out with
> > > > > > domain_is_scoped().  This should be a dedicated patch.
> > > > > 
> > > > > Well, if domain_is_scoped() can be refactored and made generic, it would
> > > > > make more sense to move it to domain.c
> > > > > 
> > > > > > 
> > > > > > > +{
> > > > > > > +	int client_layer, server_layer;
> > > > > > > +	const struct landlock_hierarchy *client_walker, *server_walker;
> > > > > > > +
> > > > > > > +	if (WARN_ON_ONCE(!client))
> > > > > > > +		return; /* should not happen */
> > > 
> > > Please no comment after ";"
> > > 
> > > > > > > +
> > > > > > > +	if (!server)
> > > > > > > +		return; /* server has no Landlock domain; nothing to clear */
> > > > > > > +
> > > > > > > +	client_layer = client->num_layers - 1;
> > > > > > > +	client_walker = client->hierarchy;
> > > > > > > +	server_layer = server->num_layers - 1;
> > > > > > > +	server_walker = server->hierarchy;
> > > > > > > +
> > > > > > > +	/*
> > > > > > > +	 * Clears the access bits at all layers where the client domain is the
> > > > > > > +	 * same as the server domain.  We start the walk at min(client_layer,
> > > > > > > +	 * server_layer).  The layer bits until there can not be cleared because
> > > > > > > +	 * either the client or the server domain is missing.
> > > > > > > +	 */
> > > > > > > +	for (; client_layer > server_layer; client_layer--)
> > > > > > > +		client_walker = client_walker->parent;
> > > > > > > +
> > > > > > > +	for (; server_layer > client_layer; server_layer--)
> > > > > > > +		server_walker = server_walker->parent;
> > > > > > > +
> > > > > > > +	for (; client_layer >= 0; client_layer--) {
> > > > > > > +		if (masks->access[client_layer] & access &&
> > > > > > > +		    client_walker == server_walker)
> > > 
> > > I'd prefer to first check client_walker == server_walker and then the
> > > access.  My main concern is that only one bit of access matching
> > > masks->access[client_layer] clear all the access request bits.  In
> > > practice there is only one, for now, but this code should be more strict
> > > by following a defensive approach.
> 
> This function works even if multiple access request bits with
> "scope-like" semantics were being checked in parallel; if you consider
> the logic in:
> 
>   if (masks->access[client_layer] & access &&
>       client_walker == server_walker)
>           masks->access[client_layer] &= ~access;
> 
> you'll realize that the check for "masks->access[client_layer] &
> access" is technically irrelevant - if that check fails, all the

Correct

> affected bits are already zero, so clearing them is a no-op.  This
> code is equivalent, but might perform slightly more writes (although
> it likely does not make a performance difference in practice):
> 
>   if (client_walker == server_walker)
>           masks->access[client_layer] &= ~access;
> 
> With that code it's a bit easier to see that "access" is actually only
> used to decide which bits to clear.  This works both with one and with
> multiple access rights.
> 
> This follows the same logic as outlined in the comment above in the
> code, where it says:
> 
>     Clears the access bits at all layers where the client domain is the
>     same as the server domain.  We start the walk at min(client_layer,
>     server_layer).  The layer bits until there can not be cleared because
>     either the client or the server domain is missing.
> 
> Clearing bits that aren't there is a no-op
> 
> 
> 
> <Optional Math>
> 
> I found it helpful to visualize the scoping logic, this is directly
> from my notes: (Web version is at https://wiki.gnoack.org/LandlockDomainIsScoped)
> 
> The domain_is_scoped() helper implements the following predicate:
> 
>   ∀ l ∈ (0,16): (hasbit(self, l) implies-that domain(self, l) == domain(other, l))
> 
> That is, we require for each layer l nesting depth that:
> 
>   * **If** scoping is active at the layer,
>   * **Then** the domains of self and other are the same
>              at the given nesting depth.
> 
> For example:
> 
>        [ ]
>         |
>        [x]     self and other have the same domain at this depth
>         |
>        [ ]
>       /   \
>     [x]   [ ]  self and other have differing domains at this depth
>      |     |
>     [ ]   [ ]
>      |
>     [ ]     "other"             "x" marks a domain where "self" has
>                                     set the scoping bit
>   "self"
> 
> </Optional Math>
> 
> 
> > > > > > > +			masks->access[client_layer] &= ~access;
> > 
> > Actually, why not removing the access argument and just reset
> > masks->access[client_layer]?  The doc would need some updates.
> 
> It would feel brittle to me if this function were to clear out
> unrelated access rights. It receives a struct layer_access_masks after
> all, where it is normally expected that multiple kinds of access
> rights are set.  In my understanding, the bit masking does not cost
> much extra performance compared to clearing it out entirely, so I'd
> prefer to have clearer semantics and only operate on the access rights
> that it's about, even when the other bits are all zero at the moment.
> 
> (For full disclosure, I have contemplated for a bit whether
> hook_unix_find() should take a layer_mask_t-like type where each bit
> indicates whether a given access right
> (LANDLOCK_ACCESS_FS_RESOLVE_UNIX, in this case) is set at a given
> layer, and then it would only clear out the bits there.  That would be
> in some ways simpler, but then the caller would still need to convert
> back and forth to a layer mask anyway, because that's what the other
> functions there take.  So it didn't seem like a good option in the
> bigger scheme (and I would also prefer to not re-introduce
> layer_mask_t after we just removed it).)
> 
> Maybe I did not understand your remark fully though;
> Does my argument sound reasonable?

Yes! Thanks for the deep explanation.

^ permalink raw reply

* Re: [PATCH v6 1/9] lsm: Add LSM hook security_unix_find
From: Paul Moore @ 2026-03-17 21:34 UTC (permalink / raw)
  To: Günther Noack, Mickaël Salaün, John Johansen,
	James Morris, Serge E . Hallyn
  Cc: Günther Noack, Tingmao Wang, Justin Suess,
	linux-security-module, Samasth Norway Ananda, Matthieu Buffet,
	Mikhail Ivanov, konstantin.meskhidze, Demi Marie Obenour,
	Alyssa Ross, Jann Horn, Tahera Fahimi, Sebastian Andrzej Siewior,
	Kuniyuki Iwashima, Simon Horman, netdev, Alexander Viro,
	Christian Brauner
In-Reply-To: <20260315222150.121952-2-gnoack3000@gmail.com>

On Mar 15, 2026 =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack3000@gmail.com> wrote:
> 
> Add a LSM hook security_unix_find.
> 
> This hook is called to check the path of a named unix socket before a
> connection is initiated. The peer socket may be inspected as well.
> 
> Why existing hooks are unsuitable:
> 
> Existing socket hooks, security_unix_stream_connect(),
> security_unix_may_send(), and security_socket_connect() don't provide
> TOCTOU-free / namespace independent access to the paths of sockets.
> 
> (1) We cannot resolve the path from the struct sockaddr in existing hooks.
> This requires another path lookup. A change in the path between the
> two lookups will cause a TOCTOU bug.
> 
> (2) We cannot use the struct path from the listening socket, because it
> may be bound to a path in a different namespace than the caller,
> resulting in a path that cannot be referenced at policy creation time.
> 
> Cc: Günther Noack <gnoack3000@gmail.com>
> Cc: Tingmao Wang <m@maowtm.org>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
>  include/linux/lsm_hook_defs.h |  5 +++++
>  include/linux/security.h      | 11 +++++++++++
>  net/unix/af_unix.c            | 13 ++++++++++---
>  security/security.c           | 20 ++++++++++++++++++++
>  4 files changed, 46 insertions(+), 3 deletions(-)

Some really minor nitpicky things (below), but nothing critical.
However, as we discussed, I would like to see the AppArmor folks comment
on the new hook before we merge anything as I know they have an interest
here.

> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..7a0fd3dbfa29 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -317,6 +317,11 @@ LSM_HOOK(int, 0, post_notification, const struct cred *w_cred,
>  LSM_HOOK(int, 0, watch_key, struct key *key)
>  #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> +	 int flags)
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */

I'd suggest moving this into the CONFIG_SECURITY_NETWORK that is directly
below this block so you only have to check the CONFIG_SECURITY_PATH
state.  You can place it directly after the existing security_unix*()
hooks.

>  #ifdef CONFIG_SECURITY_NETWORK
>  LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
>  	 struct sock *newsk)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..99a33d8eb28d 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1931,6 +1931,17 @@ static inline int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>  }
>  #endif	/* CONFIG_SECURITY_NETWORK */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +
> +#else /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */

Similar to above, I would suggest moving this into the
CONFIG_SECURITY_NETWORK block directly above this so you only need to
check for CONFIG_SECURITY_PATH when declaring the security_unix_find()
hook.

Extra bonus points if you locate it next to the existing security_unix*()
hooks.

>  #ifdef CONFIG_SECURITY_INFINIBAND
>  int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey);
>  int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num);
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 3756a93dc63a..aced28179bac 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1231,11 +1231,18 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
>  		goto path_put;
>  
>  	err = -EPROTOTYPE;
> -	if (sk->sk_type == type)
> -		touch_atime(&path);
> -	else
> +	if (sk->sk_type != type)
>  		goto sock_put;
>  
> +	/*
> +	 * We call the hook because we know that the inode is a socket and we
> +	 * hold a valid reference to it via the path.
> +	 */

I'm not entirely sure that this comment is necessary as it doesn't tell
us anything we don't already know from a quick glance at the code.  Is
there something sneaky, or hard to see, that we should know about?

> +	err = security_unix_find(&path, sk, flags);
> +	if (err)
> +		goto sock_put;
> +	touch_atime(&path);
> +

This is hyper nitpicky, but I'd probably put one line of vertical
whitespace before the touch_atime() call as it has nothing to do with
the LSM hook being called.

>  	path_put(&path);
>  
>  	return sk;
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..c73196b8db4b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -4731,6 +4731,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
>  
>  #endif	/* CONFIG_SECURITY_NETWORK */
>  
> +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> +/**
> + * security_unix_find() - Check if a named AF_UNIX socket can connect
> + * @path: path of the socket being connected to
> + * @other: peer sock
> + * @flags: flags associated with the socket
> + *
> + * This hook is called to check permissions before connecting to a named
> + * AF_UNIX socket.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +	return call_int_hook(unix_find, path, other, flags);
> +}
> +EXPORT_SYMBOL(security_unix_find);
> +
> +#endif	/* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */

You can probably guess that I'm going to suggest placing this inside the
existing CONFIG_SECURITY_NETWORK block, right after the existing UNIX
hooks :)

--
paul-moore.com

^ permalink raw reply

* Re: [PATCH 2/3] lsm: add the security_mmap_backing_file() hook
From: Paul Moore @ 2026-03-17 22:42 UTC (permalink / raw)
  To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
	linux-erofs
  Cc: Amir Goldstein, Gao Xiang
In-Reply-To: <20260316213606.374109-7-paul@paul-moore.com>

On Mon, Mar 16, 2026 at 5:36 PM Paul Moore <paul@paul-moore.com> wrote:
>
> Add the security_mmap_backing_file() hook to allow LSMs to properly
> enforce access controls on mmap() operations on stacked filesystems
> such as overlayfs.
>
> The existing security_mmap_file() hook exists as an access control point
> for mmap() but on stacked filesystems it only provides a way to enforce
> access controls on the user visible file.  In order to enforce access
> controls on the underlying backing file, the new
> security_mmap_backing_file() hook is needed.
>
> In addition the LSM hook additions, this patch also constifies the file
> struct field in the LSM common_audit_data struct to better support LSMs
> that will likely need to pass a const file struct pointer from the new
> backing_file_user_path_file() API into the common LSM audit code.
>
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: Paul Moore <paul@paul-moore.com>
> ---
>  fs/backing-file.c             |  8 +++++++-
>  fs/erofs/ishare.c             |  6 ++++++
>  include/linux/lsm_audit.h     |  2 +-
>  include/linux/lsm_hook_defs.h |  2 ++
>  include/linux/security.h      | 10 ++++++++++
>  security/security.c           | 25 +++++++++++++++++++++++++
>  6 files changed, 51 insertions(+), 2 deletions(-)

...

> diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
> index 17a4941d4518..d66c3a935d83 100644
> --- a/fs/erofs/ishare.c
> +++ b/fs/erofs/ishare.c
> @@ -150,8 +150,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
>  static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
>  {
>         struct file *realfile = file->private_data;
> +       int err;
>
>         vma_set_file(vma, realfile);
> +
> +       err = security_mmap_backing_file(vma, realfile, file);
> +       if (err)
> +               return err;
> +
>         return generic_file_readonly_mmap(file, vma);
>  }

The kernel test robot helpfully pointed out that this patch was
missing a security.h include for the newly added LSM hook.  The fixup
below has been applied to the patch in lsm/stable-7.0.

diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index d66c3a935d83..f80925b66599 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -4,6 +4,7 @@
 */
#include <linux/xxhash.h>
#include <linux/mount.h>
+#include <linux/security.h>
#include "internal.h"
#include "xattr.h"

-- 
paul-moore.com

^ permalink raw reply related

* [PATCH v7 1/9] lsm: Add LSM hook security_unix_find
From: Justin Suess @ 2026-03-17 23:20 UTC (permalink / raw)
  To: paul
  Cc: bigeasy, brauner, demiobenour, fahimitahera, gnoack3000, hi,
	horms, ivanov.mikhail1, jannh, jmorris, john.johansen,
	konstantin.meskhidze, kuniyu, linux-security-module, m, matthieu,
	mic, netdev, samasth.norway.ananda, serge, utilityemal77, viro
In-Reply-To: <2697b9f672967b1318630f2ffa21914f@paul-moore.com>

Add a LSM hook security_unix_find.

This hook is called to check the path of a named unix socket before a
connection is initiated. The peer socket may be inspected as well.

Why existing hooks are unsuitable:

Existing socket hooks, security_unix_stream_connect(),
security_unix_may_send(), and security_socket_connect() don't provide
TOCTOU-free / namespace independent access to the paths of sockets.

(1) We cannot resolve the path from the struct sockaddr in existing hooks.
This requires another path lookup. A change in the path between the
two lookups will cause a TOCTOU bug.

(2) We cannot use the struct path from the listening socket, because it
may be bound to a path in a different namespace than the caller,
resulting in a path that cannot be referenced at policy creation time.

Consumers of the hook wishing to reference @other are responsible
for acquiring the unix_state_lock and checking for the SOCK_DEAD flag
therein, ensuring the socket hasn't died since lookup.

Cc: Günther Noack <gnoack3000@gmail.com>
Cc: Tingmao Wang <m@maowtm.org>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---

Paul,

I updated the hook placement as per your suggestions. Moving the hook into
the block does require duplicate stubs, but I don't see another way to move the
stub into that block and properly handle the case where CONFIG_SECURITY_PATH is
defined but CONFIG_SECURITY_NETWORK isn't. If the stub is moved into that #else
block it will never be defined in that case.

I removed the self-evident comment as well from security_unix_find and added
the whitespace.

I also updated the comments and commit message with respect to locking.

 include/linux/lsm_hook_defs.h |  6 ++++++
 include/linux/security.h      | 13 +++++++++++++
 net/unix/af_unix.c            | 10 +++++++---
 security/security.c           | 19 +++++++++++++++++++
 4 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c..0017a540c2fb 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -321,6 +321,12 @@ LSM_HOOK(int, 0, watch_key, struct key *key)
 LSM_HOOK(int, 0, unix_stream_connect, struct sock *sock, struct sock *other,
 	 struct sock *newsk)
 LSM_HOOK(int, 0, unix_may_send, struct socket *sock, struct socket *other)
+
+#ifdef CONFIG_SECURITY_PATH
+LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
+	 int flags)
+#endif /* CONFIG_SECURITY_PATH */
+
 LSM_HOOK(int, 0, socket_create, int family, int type, int protocol, int kern)
 LSM_HOOK(int, 0, socket_post_create, struct socket *sock, int family, int type,
 	 int protocol, int kern)
diff --git a/include/linux/security.h b/include/linux/security.h
index 83a646d72f6f..3f8c23ad1199 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1641,6 +1641,14 @@ static inline int security_watch_key(struct key *key)
 int security_netlink_send(struct sock *sk, struct sk_buff *skb);
 int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);
 int security_unix_may_send(struct socket *sock,  struct socket *other);
+#ifdef CONFIG_SECURITY_PATH
+int security_unix_find(const struct path *path, struct sock *other, int flags);
+#else /* CONFIG_SECURITY_PATH */
+static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+	return 0;
+}
+#endif /* CONFIG_SECURITY_PATH */
 int security_socket_create(int family, int type, int protocol, int kern);
 int security_socket_post_create(struct socket *sock, int family,
 				int type, int protocol, int kern);
@@ -1712,6 +1720,11 @@ static inline int security_unix_may_send(struct socket *sock,
 	return 0;
 }
 
+static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+	return 0;
+}
+
 static inline int security_socket_create(int family, int type,
 					 int protocol, int kern)
 {
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 3756a93dc63a..5ef3c2e31757 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1231,11 +1231,15 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 		goto path_put;
 
 	err = -EPROTOTYPE;
-	if (sk->sk_type == type)
-		touch_atime(&path);
-	else
+	if (sk->sk_type != type)
 		goto sock_put;
 
+	err = security_unix_find(&path, sk, flags);
+	if (err)
+		goto sock_put;
+
+	touch_atime(&path);
+
 	path_put(&path);
 
 	return sk;
diff --git a/security/security.c b/security/security.c
index 67af9228c4e9..f8df5e1b55e6 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4073,6 +4073,25 @@ int security_unix_may_send(struct socket *sock,  struct socket *other)
 }
 EXPORT_SYMBOL(security_unix_may_send);
 
+#ifdef CONFIG_SECURITY_PATH
+/**
+ * security_unix_find() - Check if a named AF_UNIX socket can connect
+ * @path: path of the socket being connected to
+ * @other: peer sock
+ * @flags: flags associated with the socket
+ *
+ * This hook is called to check permissions before connecting to a named
+ * AF_UNIX socket. The caller does not hold any locks on @other.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_unix_find(const struct path *path, struct sock *other, int flags)
+{
+	return call_int_hook(unix_find, path, other, flags);
+}
+EXPORT_SYMBOL(security_unix_find);
+#endif	/* CONFIG_SECURITY_PATH */
+
 /**
  * security_socket_create() - Check if creating a new socket is allowed
  * @family: protocol family
-- 
2.51.0


^ permalink raw reply related

* [PATCH RESEND] apparmor: Use sysfs_emit in param_get_{audit,mode}
From: Thorsten Blum @ 2026-03-18  0:08 UTC (permalink / raw)
  To: John Johansen, Paul Moore, James Morris, Serge E. Hallyn
  Cc: Thorsten Blum, apparmor, linux-security-module, linux-kernel

Replace sprintf() with sysfs_emit() in param_get_audit() and
param_get_mode(). sysfs_emit() is preferred for formatting sysfs output
because it provides safer bounds checking.  Add terminating newlines as
suggested by checkpatch.

Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
 security/apparmor/lsm.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c1d42fc72fdb..cdf19a5e7626 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -17,6 +17,7 @@
 #include <linux/ptrace.h>
 #include <linux/ctype.h>
 #include <linux/sysctl.h>
+#include <linux/sysfs.h>
 #include <linux/audit.h>
 #include <linux/user_namespace.h>
 #include <linux/netfilter_ipv4.h>
@@ -2073,7 +2074,7 @@ static int param_get_audit(char *buffer, const struct kernel_param *kp)
 		return -EINVAL;
 	if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
 		return -EPERM;
-	return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
+	return sysfs_emit(buffer, "%s\n", audit_mode_names[aa_g_audit]);
 }
 
 static int param_set_audit(const char *val, const struct kernel_param *kp)
@@ -2101,8 +2102,7 @@ static int param_get_mode(char *buffer, const struct kernel_param *kp)
 		return -EINVAL;
 	if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
 		return -EPERM;
-
-	return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]);
+	return sysfs_emit(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]);
 }
 
 static int param_set_mode(const char *val, const struct kernel_param *kp)

^ permalink raw reply related

* [PATCH RESEND] apparmor: Remove redundant if check in sk_peer_get_label
From: Thorsten Blum @ 2026-03-18  0:21 UTC (permalink / raw)
  To: John Johansen, Paul Moore, James Morris, Serge E. Hallyn
  Cc: Thorsten Blum, apparmor, linux-security-module, linux-kernel

Remove the redundant if check in sk_peer_get_label() and return
ERR_PTR(-ENOPROTOOPT) directly.

Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
 security/apparmor/lsm.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c1d42fc72fdb..f7bcfed40222 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1528,15 +1528,11 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
 static struct aa_label *sk_peer_get_label(struct sock *sk)
 {
 	struct aa_sk_ctx *ctx = aa_sock(sk);
-	struct aa_label *label = ERR_PTR(-ENOPROTOOPT);
 
 	if (rcu_access_pointer(ctx->peer))
 		return aa_get_label_rcu(&ctx->peer);
 
-	if (sk->sk_family != PF_UNIX)
-		return ERR_PTR(-ENOPROTOOPT);
-
-	return label;
+	return ERR_PTR(-ENOPROTOOPT);
 }
 
 /**

^ permalink raw reply related

* Re: [PATCH RFC bpf-next 0/4] audit: Expose audit subsystem to BPF LSM programs via BPF kfuncs
From: Alexei Starovoitov @ 2026-03-18  1:15 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi
  Cc: Frederick Lawler, Paul Moore, James Morris, Serge E. Hallyn,
	Eric Paris, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Shuah Khan, Mickaël Salaün, Günther Noack, LKML,
	LSM List, audit, bpf, open list:KERNEL SELFTEST FRAMEWORK,
	kernel-team
In-Reply-To: <CAP01T77VyW=5SHDvM3HXPNHaxRdzs8H__MOh2zx1dQ6STeAUtg@mail.gmail.com>

On Mon, Mar 16, 2026 at 7:44 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Wed, 11 Mar 2026 at 22:31, Frederick Lawler <fred@cloudflare.com> wrote:
> >
> > The motivation behind the change is to give BPF LSM developers the
> > ability to report accesses via the audit subsystem much like how LSMs
> > operate today.

Sure, but bpf lsm-s don't need to follow such conventions.
audit is nothing but a message passing from kernel to user space
and done in a very inefficient way by wrapping strings into skb/netlink.
bpf progs can do this message passing already via various ways:
perfbuf, ringbuf, streams.
Teach your user space to consume one of them.

^ permalink raw reply

* Re: [PATCH v7 1/9] lsm: Add LSM hook security_unix_find
From: Paul Moore @ 2026-03-18  1:28 UTC (permalink / raw)
  To: Justin Suess
  Cc: bigeasy, brauner, demiobenour, fahimitahera, gnoack3000, hi,
	horms, ivanov.mikhail1, jannh, jmorris, john.johansen,
	konstantin.meskhidze, kuniyu, linux-security-module, m, matthieu,
	mic, netdev, samasth.norway.ananda, serge, viro
In-Reply-To: <20260317232041.330576-1-utilityemal77@gmail.com>

On Tue, Mar 17, 2026 at 7:21 PM Justin Suess <utilityemal77@gmail.com> wrote:
>
> Paul,
>
> I updated the hook placement as per your suggestions. Moving the hook into
> the block does require duplicate stubs, but I don't see another way to move the
> stub into that block and properly handle the case where CONFIG_SECURITY_PATH is
> defined but CONFIG_SECURITY_NETWORK isn't. If the stub is moved into that #else
> block it will never be defined in that case.

Oof, yes, my apologies, I must have still been thinking about the
LSM_HOOK() change and didn't think through the problems with moving
the declaration.  If you aren't too upset about changing it back, I
would prefer it back the way you had it in security.h originally.

Sorry for the noise :/

> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..3f8c23ad1199 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1641,6 +1641,14 @@ static inline int security_watch_key(struct key *key)
>  int security_netlink_send(struct sock *sk, struct sk_buff *skb);
>  int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk);
>  int security_unix_may_send(struct socket *sock,  struct socket *other);
> +#ifdef CONFIG_SECURITY_PATH
> +int security_unix_find(const struct path *path, struct sock *other, int flags);
> +#else /* CONFIG_SECURITY_PATH */
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +       return 0;
> +}
> +#endif /* CONFIG_SECURITY_PATH */
>  int security_socket_create(int family, int type, int protocol, int kern);
>  int security_socket_post_create(struct socket *sock, int family,
>                                 int type, int protocol, int kern);
> @@ -1712,6 +1720,11 @@ static inline int security_unix_may_send(struct socket *sock,
>         return 0;
>  }
>
> +static inline int security_unix_find(const struct path *path, struct sock *other, int flags)
> +{
> +       return 0;
> +}
> +

-- 
paul-moore.com

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox