Linux Security Modules development
 help / color / mirror / Atom feed
* Re: [PATCH v3 4/5] xfs: replace ns_capable_noaudit
From: Carlos Maiolino @ 2026-07-07  9:26 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Jan Kara, Christoph Hellwig, Serge E. Hallyn, Dave Chinner,
	Eric Sandeen, linux-xfs, linux-fsdevel, linux-security-module,
	linux-kernel
In-Reply-To: <20260702155856.GF9392@frogsfrogsfrogs>

On Thu, Jul 02, 2026 at 08:58:56AM -0700, Darrick J. Wong wrote:
> On Thu, Jul 02, 2026 at 11:33:23AM +0200, cem@kernel.org wrote:
> > From: Carlos Maiolino <cem@kernel.org>
> > 
> > Now that capable_noaudit() is available, we don't need to keep
> > using ns_capable_noaudit() and specifying the usernaspace every single
> > time.
> > 
> > Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
> > Cc: Jan Kara <jack@suse.cz>
> > Cc: Christoph Hellwig <hch@lst.de>
> > Cc: Serge E. Hallyn <serge@hallyn.com>
> > Cc: Darrick J. Wong <djwong@kernel.org>
> > Cc: Dave Chinner <david@fromorbit.com>
> > Cc: Eric Sandeen <sandeen@redhat.com>
> > Cc: Dr. Thomas Orgis" <thomas.orgis@uni-hamburg.de>
> > Cc: linux-xfs@vger.kernel.org
> > Cc: linux-fsdevel@vger.kernel.org
> > Cc: linux-security-module@vger.kernel.org
> > Cc: linux-kernel@vger.kernel.org
> > ---
> >  fs/xfs/xfs_fsmap.c | 3 +--
> >  fs/xfs/xfs_ioctl.c | 2 +-
> >  fs/xfs/xfs_iops.c  | 2 +-
> >  3 files changed, 3 insertions(+), 4 deletions(-)
> > 
> > diff --git a/fs/xfs/xfs_fsmap.c b/fs/xfs/xfs_fsmap.c
> > index 7c79fbe0a74c..041bb2105ec6 100644
> > --- a/fs/xfs/xfs_fsmap.c
> > +++ b/fs/xfs/xfs_fsmap.c
> > @@ -1174,8 +1174,7 @@ xfs_getfsmap(
> >  	if (!xfs_getfsmap_check_keys(&head->fmh_keys[0], &head->fmh_keys[1]))
> >  		return -EINVAL;
> >  
> > -	use_rmap = xfs_has_rmapbt(mp) &&
> > -		   ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN);
> > +	use_rmap = xfs_has_rmapbt(mp) && capable_noaudit(CAP_SYS_ADMIN);
> >  	head->fmh_entries = 0;
> >  
> >  	/* Set up our device handlers. */
> > diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> > index 1a8af827dde1..374b488f0416 100644
> > --- a/fs/xfs/xfs_ioctl.c
> > +++ b/fs/xfs/xfs_ioctl.c
> > @@ -647,7 +647,7 @@ xfs_ioctl_setattr_get_trans(
> >  		goto out_error;
> >  
> >  	error = xfs_trans_alloc_ichange(ip, NULL, NULL, pdqp,
> > -			ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp);
> > +					capable_noaudit(CAP_FOWNER), &tp);
> 
> Not sure why the indentation changed, otherwise the patch looks fine to
> me.

Purely my screw up. I fixed it already to send to the next version. Was
just waiting for more comments

> 
> --D
> 
> >  	if (error)
> >  		goto out_error;
> >  
> > diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
> > index 205fe2dae732..ce9f8b8468fc 100644
> > --- a/fs/xfs/xfs_iops.c
> > +++ b/fs/xfs/xfs_iops.c
> > @@ -835,7 +835,7 @@ xfs_setattr_nonsize(
> >  	}
> >  
> >  	error = xfs_trans_alloc_ichange(ip, udqp, gdqp, NULL,
> > -					ns_capable_noaudit(&init_user_ns, CAP_FOWNER),
> > +					capable_noaudit(CAP_FOWNER),
> >  					&tp);
> >  	if (error)
> >  		goto out_dqrele;
> > -- 
> > 2.54.0
> > 
> > 
> 

^ permalink raw reply

* Re: [PATCH v3 2/5] capability: Add new capable_noaudit
From: Carlos Maiolino @ 2026-07-07  9:25 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Jan Kara, Dave Chinner, Eric Sandeen, linux-xfs, linux-fsdevel,
	linux-security-module, linux-kernel, Christoph Hellwig,
	Serge Hallyn
In-Reply-To: <20260702155605.GE9392@frogsfrogsfrogs>

On Thu, Jul 02, 2026 at 08:56:05AM -0700, Darrick J. Wong wrote:
> On Thu, Jul 02, 2026 at 11:33:19AM +0200, cem@kernel.org wrote:
> > From: Carlos Maiolino <cem@kernel.org>
> > 
> > In some situations (quota enforcement bypass in this case) we'd like to
> > check for a specific capability without triggering spurious audit
> > messages from security modules like selinux.
> > 
> > Add a new helper so we don't need to use ns_capable_noaudit() directly.
> > 
> > V3: remove the extern declaration
> > 
> > Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
> > Cc: Jan Kara <jack@suse.cz>
> > Cc: Darrick J. Wong <djwong@kernel.org>
> > Cc: Dave Chinner <david@fromorbit.com>
> > Cc: Eric Sandeen <sandeen@redhat.com>
> > Cc: Dr. Thomas Orgis" <thomas.orgis@uni-hamburg.de>
> > Cc: linux-xfs@vger.kernel.org
> > Cc: linux-fsdevel@vger.kernel.org
> > Cc: linux-security-module@vger.kernel.org
> > Cc: linux-kernel@vger.kernel.org
> > Reviewed-by: Christoph Hellwig <hch@lst.de>
> > Reviewed-by: Serge Hallyn <serge@hallyn.com>
> > ---
> >  include/linux/capability.h |  5 +++++
> >  kernel/capability.c        | 17 +++++++++++++++++
> >  2 files changed, 22 insertions(+)
> > 
> > diff --git a/include/linux/capability.h b/include/linux/capability.h
> > index 37db92b3d6f8..f8532d92fcad 100644
> > --- a/include/linux/capability.h
> > +++ b/include/linux/capability.h
> > @@ -145,6 +145,7 @@ extern bool has_capability_noaudit(struct task_struct *t, int cap);
> >  extern bool has_ns_capability_noaudit(struct task_struct *t,
> >  				      struct user_namespace *ns, int cap);
> >  extern bool capable(int cap);
> > +bool capable_noaudit(int cap);
> >  extern bool ns_capable(struct user_namespace *ns, int cap);
> >  extern bool ns_capable_noaudit(struct user_namespace *ns, int cap);
> >  extern bool ns_capable_setid(struct user_namespace *ns, int cap);
> > @@ -167,6 +168,10 @@ static inline bool capable(int cap)
> >  {
> >  	return true;
> >  }
> > +static inline bool capable_noaudit(int cap)
> > +{
> > +	return true;
> > +}
> >  static inline bool ns_capable(struct user_namespace *ns, int cap)
> >  {
> >  	return true;
> > diff --git a/kernel/capability.c b/kernel/capability.c
> > index 829f49ae07b9..2c2d1e8300bd 100644
> > --- a/kernel/capability.c
> > +++ b/kernel/capability.c
> > @@ -416,6 +416,23 @@ bool capable(int cap)
> >  	return ns_capable(&init_user_ns, cap);
> >  }
> >  EXPORT_SYMBOL(capable);
> > +
> > +/**
> > + * capable_noaudit - Determine if the current task has a superior
> > + * capability in effect (unaudited).
> > + * @cap: The capability to be tested for
> > + *
> > + * This is the same as capable(), except it uses CAP_OPT_NOAUDIT as to prevent
> > + * issuing spurious audit messages.
> > + *
> > + * This sets PF_SUPERPRIV on the task if the capability is available on the
> > + * assumption that it's about to be used.
> 
> Same complaint about the documentation as last time:
> https://lore.kernel.org/linux-fsdevel/20260626151656.GT6078@frogsfrogsfrogs/

Right. I assumed "capability in effect" would suffice that, but I can
add some extra comment here.

> 
> --D
> 
> > + */
> > +bool capable_noaudit(int cap)
> > +{
> > +	return ns_capable_noaudit(&init_user_ns, cap);
> > +}
> > +EXPORT_SYMBOL(capable_noaudit);
> >  #endif /* CONFIG_MULTIUSER */
> >  
> >  /**
> > -- 
> > 2.54.0
> > 
> > 
> 

^ permalink raw reply

* [PATCH v2 -next 2/2] security: Fix call security_backing_file_free second time
From: Cai Xinchen @ 2026-07-07  8:06 UTC (permalink / raw)
  To: paul, jmorris, serge, amir73il, brauner, caixinchen1
  Cc: linux-security-module, linux-kernel, lujialin4
In-Reply-To: <20260707080615.1235830-1-caixinchen1@huawei.com>

I found the following path:

alloc_empty_backing-file
    init_file(&ff->file, xxx)
        -> file_ref_init(&f->f_ref, 1); // only 1
    error = init_backing_file
        -> security_backing_file_alloc
        -> rc = call_int_hook(backing_file_alloc, ...)
           if (unlikely(rc))
           	security_backing_file_free(backing_file); // first call
    if (unlikely(error)) {
        fput(&ff->file);
         -> if (unlikely(file_ref_put(&file->f_ref))) // zero
                __fput_deferred(file);
                 -> ____fput -> __fput -> file_free(file);
                 -> backing_file_free(backing_file(f));
                 -> security_backing_file_free(&ff->file); // second call

Currently, only SELinux has the lsm backing_file_alloc hook, and it always
return 0. When security_backing_file_free is called for the first time,
the blobs pointer is set to NULL. Therefore, double free will not occur in
the code.

Fixes: 6af36aeb147a ("lsm: add backing_file LSM hooks")
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 security/security.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..bec2f4ebea34 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2486,9 +2486,8 @@ void security_backing_file_free(struct file *backing_file)
 {
 	void *blob = backing_file_security(backing_file);
 
-	call_void_hook(backing_file_free, backing_file);
-
 	if (blob) {
+		call_void_hook(backing_file_free, backing_file);
 		backing_file_set_security(backing_file, NULL);
 		kmem_cache_free(lsm_backing_file_cache, blob);
 	}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 -next 0/2] security: Fix call security_backing_file_free second time
From: Cai Xinchen @ 2026-07-07  8:06 UTC (permalink / raw)
  To: paul, jmorris, serge, amir73il, brauner, caixinchen1
  Cc: linux-security-module, linux-kernel, lujialin4

v2: Move the call_void_hook(backing_file_free, ...) call in
security_backing_file_free() into the if-statment true block before we
set the backing file's LSM blob pointer to NULL and free the LSM blob.

I found the following path:

alloc_empty_backing-file
    init_file(&ff->file, xxx)
        -> file_ref_init(&f->f_ref, 1); // only 1
    error = init_backing_file
        -> security_backing_file_alloc
        -> rc = call_int_hook(backing_file_alloc, ...)
           if (unlikely(rc))
                security_backing_file_free(backing_file); // first call
    if (unlikely(error)) {
        fput(&ff->file);
         -> if (unlikely(file_ref_put(&file->f_ref))) // zero
                __fput_deferred(file);
                 -> ____fput -> __fput -> file_free(file);
                 -> backing_file_free(backing_file(f));
                 -> security_backing_file_free(&ff->file); // second call

Currently, only SELinux has the lsm backing_file_alloc hook, and it always
return 0. When security_backing_file_free is called for the first time,
the blobs pointer is set to NULL. Therefore, double free will not occur in
the code.

Cai Xinchen (2):
  security: Delete dumplicate assignment
  security: Fix call security_backing_file_free second time

 security/lsm_init.c | 1 -
 security/security.c | 3 +--
 2 files changed, 1 insertion(+), 3 deletions(-)

-- 
2.34.1


^ permalink raw reply

* [PATCH v2 -next 1/2] security: Delete dumplicate assignment
From: Cai Xinchen @ 2026-07-07  8:06 UTC (permalink / raw)
  To: paul, jmorris, serge, amir73il, brauner, caixinchen1
  Cc: linux-security-module, linux-kernel, lujialin4
In-Reply-To: <20260707080615.1235830-1-caixinchen1@huawei.com>

Delete a blobs variable with duplicate assignment.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 security/lsm_init.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/security/lsm_init.c b/security/lsm_init.c
index 7c0fd17f1601..d7384866e3a5 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -290,7 +290,6 @@ static void __init lsm_prepare(struct lsm_info *lsm)
 		return;
 
 	/* Register the LSM blob sizes. */
-	blobs = lsm->blobs;
 	lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
 	lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
 	lsm_blob_size_update(&blobs->lbs_backing_file,
-- 
2.34.1


^ permalink raw reply related

* [PATCH v6 8/8] lsm: Remove security_sb_mount and security_move_mount
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Now that all LSMs have been converted to granular mount hooks and
fs/namespace.c calls the new hooks, remove the old hooks:

- security_sb_mount(): removed from lsm_hook_defs.h, security.h,
  security.c.
- security_move_mount(): removed from lsm_hook_defs.h, security.h,
  security.c, and bpf_lsm.c.

Code generated with the assistance of Claude, reviewed by human.

Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
Signed-off-by: Song Liu <song@kernel.org>
---
 include/linux/lsm_hook_defs.h |  4 ----
 include/linux/security.h      | 16 ---------------
 kernel/bpf/bpf_lsm.c          |  2 --
 security/security.c           | 38 -----------------------------------
 4 files changed, 60 deletions(-)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 06c904cb478e..4015f6b579c3 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -69,8 +69,6 @@ LSM_HOOK(int, 0, sb_remount, struct super_block *sb, void *mnt_opts)
 LSM_HOOK(int, 0, sb_kern_mount, const struct super_block *sb)
 LSM_HOOK(int, 0, sb_show_options, struct seq_file *m, struct super_block *sb)
 LSM_HOOK(int, 0, sb_statfs, struct dentry *dentry)
-LSM_HOOK(int, 0, sb_mount, const char *dev_name, const struct path *path,
-	 const char *type, unsigned long flags, void *data)
 LSM_HOOK(int, 0, sb_umount, struct vfsmount *mnt, int flags)
 LSM_HOOK(int, 0, sb_pivotroot, const struct path *old_path,
 	 const struct path *new_path)
@@ -79,8 +77,6 @@ LSM_HOOK(int, 0, sb_set_mnt_opts, struct super_block *sb, void *mnt_opts,
 LSM_HOOK(int, 0, sb_clone_mnt_opts, const struct super_block *oldsb,
 	 struct super_block *newsb, unsigned long kern_flags,
 	 unsigned long *set_kern_flags)
-LSM_HOOK(int, 0, move_mount, const struct path *from_path,
-	 const struct path *to_path)
 LSM_HOOK(int, 0, mount_bind, const struct path *from, const struct path *to,
 	 bool recurse)
 LSM_HOOK(int, 0, mount_new, struct fs_context *fc, const struct path *mp,
diff --git a/include/linux/security.h b/include/linux/security.h
index 5e207e38c004..ff16831e8064 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -373,8 +373,6 @@ int security_sb_remount(struct super_block *sb, void *mnt_opts);
 int security_sb_kern_mount(const struct super_block *sb);
 int security_sb_show_options(struct seq_file *m, struct super_block *sb);
 int security_sb_statfs(struct dentry *dentry);
-int security_sb_mount(const char *dev_name, const struct path *path,
-		      const char *type, unsigned long flags, void *data);
 int security_sb_umount(struct vfsmount *mnt, int flags);
 int security_sb_pivotroot(const struct path *old_path, const struct path *new_path);
 int security_sb_set_mnt_opts(struct super_block *sb,
@@ -385,7 +383,6 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
 				struct super_block *newsb,
 				unsigned long kern_flags,
 				unsigned long *set_kern_flags);
-int security_move_mount(const struct path *from_path, const struct path *to_path);
 int security_mount_bind(const struct path *from, const struct path *to,
 			bool recurse);
 int security_mount_new(struct fs_context *fc, const struct path *mp,
@@ -826,13 +823,6 @@ static inline int security_sb_statfs(struct dentry *dentry)
 	return 0;
 }
 
-static inline int security_sb_mount(const char *dev_name, const struct path *path,
-				    const char *type, unsigned long flags,
-				    void *data)
-{
-	return 0;
-}
-
 static inline int security_sb_umount(struct vfsmount *mnt, int flags)
 {
 	return 0;
@@ -860,12 +850,6 @@ static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb,
 	return 0;
 }
 
-static inline int security_move_mount(const struct path *from_path,
-				      const struct path *to_path)
-{
-	return 0;
-}
-
 static inline int security_mount_bind(const struct path *from,
 				      const struct path *to, bool recurse)
 {
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 4b1ee9c67d6f..9cece66a0c49 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -353,7 +353,6 @@ BTF_ID(func, bpf_lsm_release_secctx)
 BTF_ID(func, bpf_lsm_sb_alloc_security)
 BTF_ID(func, bpf_lsm_sb_eat_lsm_opts)
 BTF_ID(func, bpf_lsm_sb_kern_mount)
-BTF_ID(func, bpf_lsm_sb_mount)
 BTF_ID(func, bpf_lsm_sb_remount)
 BTF_ID(func, bpf_lsm_sb_set_mnt_opts)
 BTF_ID(func, bpf_lsm_sb_show_options)
@@ -385,7 +384,6 @@ BTF_ID(func, bpf_lsm_task_setscheduler)
 BTF_ID(func, bpf_lsm_userns_create)
 BTF_ID(func, bpf_lsm_bdev_alloc_security)
 BTF_ID(func, bpf_lsm_bdev_setintegrity)
-BTF_ID(func, bpf_lsm_move_mount)
 BTF_ID(func, bpf_lsm_mount_bind)
 BTF_ID(func, bpf_lsm_mount_new)
 BTF_ID(func, bpf_lsm_mount_remount)
diff --git a/security/security.c b/security/security.c
index 1ede060db3ca..0456680738f2 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1065,29 +1065,6 @@ int security_sb_statfs(struct dentry *dentry)
 	return call_int_hook(sb_statfs, dentry);
 }
 
-/**
- * security_sb_mount() - Check permission for mounting a filesystem
- * @dev_name: filesystem backing device
- * @path: mount point
- * @type: filesystem type
- * @flags: mount flags
- * @data: filesystem specific data
- *
- * Check permission before an object specified by @dev_name is mounted on the
- * mount point named by @nd.  For an ordinary mount, @dev_name identifies a
- * device if the file system type requires a device.  For a remount
- * (@flags & MS_REMOUNT), @dev_name is irrelevant.  For a loopback/bind mount
- * (@flags & MS_BIND), @dev_name identifies the	pathname of the object being
- * mounted.
- *
- * Return: Returns 0 if permission is granted.
- */
-int security_sb_mount(const char *dev_name, const struct path *path,
-		      const char *type, unsigned long flags, void *data)
-{
-	return call_int_hook(sb_mount, dev_name, path, type, flags, data);
-}
-
 /**
  * security_sb_umount() - Check permission for unmounting a filesystem
  * @mnt: mounted filesystem
@@ -1167,21 +1144,6 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
 }
 EXPORT_SYMBOL(security_sb_clone_mnt_opts);
 
-/**
- * security_move_mount() - Check permissions for moving a mount
- * @from_path: source mount point
- * @to_path: destination mount point
- *
- * Check permission before a mount is moved.
- *
- * Return: Returns 0 if permission is granted.
- */
-int security_move_mount(const struct path *from_path,
-			const struct path *to_path)
-{
-	return call_int_hook(move_mount, from_path, to_path);
-}
-
 /**
  * security_mount_bind() - Check permissions for a bind mount
  * @from: source path
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 7/8] vfs: Replace security_sb_mount/security_move_mount with granular hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Replace the monolithic security_sb_mount() call in path_mount() and
the security_move_mount() call on the mount-move paths with the new
granular mount hooks:

- do_loopback(): call security_mount_bind()
- do_new_mount(): call security_mount_new()
- do_remount(): call security_mount_remount()
- do_reconfigure_mnt(): call security_mount_reconfigure()
- do_change_type(): call security_mount_change_type()
- do_move_mount(): call security_mount_move()
- do_set_group(): call security_mount_move()

The new hooks are called at the individual operation level with
appropriate context (resolved paths, fs_context), rather than at
the top of path_mount() with raw string arguments.

security_mount_bind() and security_mount_move() are called under the
namespace semaphore, after the mountpoint has been resolved and pinned
by LOCK_MOUNT(). This ensures the LSM inspects the mount that will
actually be used, instead of a mountpoint that could be overmounted
between the check and the operation. Placing the move hook in
do_move_mount() and do_set_group() also unifies the old mount(2)
MS_MOVE path (do_move_mount_old()) and the move_mount(2) path
(vfs_move_mount()) on a single call site.

For MOVE_MOUNT_BENEATH, security_mount_move() additionally receives the
mount that ends up on top, so an LSM can tell a plain move (target ==
top) apart from a mount-beneath operation.

Code generated with the assistance of Claude, reviewed by human.

Signed-off-by: Song Liu <song@kernel.org>
---
 fs/namespace.c | 77 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 61 insertions(+), 16 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 3d5cd5bf3b05..bc9401a2efac 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2893,6 +2893,10 @@ static int do_change_type(const struct path *path, int ms_flags)
 	if (!type)
 		return -EINVAL;
 
+	err = security_mount_change_type(path, ms_flags);
+	if (err)
+		return err;
+
 	guard(namespace_excl)();
 
 	err = may_change_propagation(mnt);
@@ -3003,6 +3007,7 @@ static int do_loopback(const struct path *path, const char *old_name,
 {
 	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL;
+	struct path dest;
 	int err;
 
 	if (!old_name || !*old_name)
@@ -3021,6 +3026,17 @@ static int do_loopback(const struct path *path, const char *old_name,
 	if (!check_mnt(mp.parent))
 		return -EINVAL;
 
+	/*
+	 * Check permission against the mountpoint that was actually pinned
+	 * under the namespace semaphore, rather than the caller-supplied
+	 * @path which may have been overmounted before the lock was taken.
+	 */
+	dest.mnt = &mp.parent->mnt;
+	dest.dentry = mp.mp->m_dentry;
+	err = security_mount_bind(&old_path, &dest, recurse);
+	if (err)
+		return err;
+
 	mnt = __do_loopback(&old_path, recurse, CL_COPY_MNT_NS_FILE);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
@@ -3335,7 +3351,8 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
  * superblock it refers to.  This is triggered by specifying MS_REMOUNT|MS_BIND
  * to mount(2).
  */
-static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags,
+			      unsigned long flags)
 {
 	struct super_block *sb = path->mnt->mnt_sb;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3350,6 +3367,10 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
 	if (!can_change_locked_flags(mnt, mnt_flags))
 		return -EPERM;
 
+	ret = security_mount_reconfigure(path, mnt_flags, flags);
+	if (ret)
+		return ret;
+
 	/*
 	 * We're only checking whether the superblock is read-only not
 	 * changing it, so only take down_read(&sb->s_umount).
@@ -3373,7 +3394,7 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
  * on it - tough luck.
  */
 static int do_remount(const struct path *path, int sb_flags,
-		      int mnt_flags, void *data)
+		      int mnt_flags, void *data, unsigned long flags)
 {
 	int err;
 	struct super_block *sb = path->mnt->mnt_sb;
@@ -3400,6 +3421,9 @@ static int do_remount(const struct path *path, int sb_flags,
 	fc->oldapi = true;
 
 	err = parse_monolithic_mount_data(fc, data);
+	if (!err)
+		err = security_mount_remount(fc, path, mnt_flags, flags,
+					    data);
 	if (!err) {
 		down_write(&sb->s_umount);
 		err = -EPERM;
@@ -3438,6 +3462,16 @@ static int do_set_group(const struct path *from_path, const struct path *to_path
 
 	guard(namespace_excl)();
 
+	/*
+	 * Setting a sharing group does not overmount anything, so the
+	 * source, target and top mount all refer to @to_path.  The check
+	 * runs under the namespace semaphore for the same reason as the
+	 * move case.
+	 */
+	err = security_mount_move(from_path, to_path, to_path);
+	if (err)
+		return err;
+
 	err = may_change_propagation(from);
 	if (err)
 		return err;
@@ -3634,6 +3668,7 @@ static int do_move_mount(const struct path *old_path,
 			 enum mnt_tree_flags_t flags)
 {
 	struct mount *old = real_mount(old_path->mnt);
+	struct path target, top;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
@@ -3647,6 +3682,17 @@ static int do_move_mount(const struct path *old_path,
 	if (IS_ERR(mp.parent))
 		return PTR_ERR(mp.parent);
 
+	/*
+	 * The destination that was actually pinned under the namespace
+	 * semaphore.  For a plain move the source is attached on top of
+	 * @target, so @target is also the mount that ends up on top; for
+	 * MOVE_MOUNT_BENEATH the source is inserted below the existing top
+	 * mount, which is reported separately below.
+	 */
+	target.mnt = &mp.parent->mnt;
+	target.dentry = mp.mp->m_dentry;
+	top = target;
+
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
@@ -3687,8 +3733,14 @@ static int do_move_mount(const struct path *old_path,
 		err = can_move_mount_beneath(old, over, &mp);
 		if (err)
 			return err;
+		top.mnt = &over->mnt;
+		top.dentry = over->mnt.mnt_root;
 	}
 
+	err = security_mount_move(old_path, &target, &top);
+	if (err)
+		return err;
+
 	/*
 	 * Don't move a mount tree containing unbindable mounts to a destination
 	 * mount which is shared.
@@ -3793,7 +3845,7 @@ static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
  */
 static int do_new_mount(const struct path *path, const char *fstype,
 			int sb_flags, int mnt_flags,
-			const char *name, void *data)
+			const char *name, void *data, unsigned long flags)
 {
 	struct file_system_type *type;
 	struct fs_context *fc;
@@ -3837,6 +3889,9 @@ static int do_new_mount(const struct path *path, const char *fstype,
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
 		err = -EPERM;
+
+	if (!err)
+		err = security_mount_new(fc, path, mnt_flags, flags, data);
 	if (!err)
 		err = do_new_mount_fc(fc, path, mnt_flags);
 
@@ -4087,7 +4142,6 @@ int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page)
 {
 	unsigned int mnt_flags = 0, sb_flags;
-	int ret;
 
 	/* Discard magic */
 	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
@@ -4100,9 +4154,6 @@ int path_mount(const char *dev_name, const struct path *path,
 	if (flags & MS_NOUSER)
 		return -EINVAL;
 
-	ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
-	if (ret)
-		return ret;
 	if (!may_mount())
 		return -EPERM;
 	if (flags & SB_MANDLOCK)
@@ -4148,9 +4199,9 @@ int path_mount(const char *dev_name, const struct path *path,
 			    SB_I_VERSION);
 
 	if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
-		return do_reconfigure_mnt(path, mnt_flags);
+		return do_reconfigure_mnt(path, mnt_flags, flags);
 	if (flags & MS_REMOUNT)
-		return do_remount(path, sb_flags, mnt_flags, data_page);
+		return do_remount(path, sb_flags, mnt_flags, data_page, flags);
 	if (flags & MS_BIND)
 		return do_loopback(path, dev_name, flags & MS_REC);
 	if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
@@ -4159,7 +4210,7 @@ int path_mount(const char *dev_name, const struct path *path,
 		return do_move_mount_old(path, dev_name);
 
 	return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
-			    data_page);
+			    data_page, flags);
 }
 
 int do_mount(const char *dev_name, const char __user *dir_name,
@@ -4554,12 +4605,6 @@ static inline int vfs_move_mount(const struct path *from_path,
 				 const struct path *to_path,
 				 enum mnt_tree_flags_t mflags)
 {
-	int ret;
-
-	ret = security_move_mount(from_path, to_path);
-	if (ret)
-		return ret;
-
 	if (mflags & MNT_TREE_PROPAGATION)
 		return do_set_group(from_path, to_path);
 
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 6/8] tomoyo: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Replace tomoyo_sb_mount() with granular mount hooks. Each hook
reconstructs the MS_* flags expected by tomoyo_mount_permission()
using the original flags parameter where available.

Key changes:
- mount_bind: passes the pre-resolved source path to
  tomoyo_mount_acl() via a new dev_path parameter, instead of
  re-resolving dev_name via kern_path(). This eliminates a TOCTOU
  vulnerability.
- mount_new, mount_remount, mount_reconfigure: use the original
  mount(2) flags for policy matching.
- mount_move: passes pre-resolved paths for both source and
  destination; the new top_path argument is unused.
- mount_change_type: passes raw ms_flags directly.

Also removes the unused data_page parameter from
tomoyo_mount_permission().

Code generated with the assistance of Claude, reviewed by human.

Acked-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Song Liu <song@kernel.org>
---
 security/tomoyo/common.h |   2 +-
 security/tomoyo/mount.c  |  31 +++++++----
 security/tomoyo/tomoyo.c | 111 +++++++++++++++++++++++++++++++++++----
 3 files changed, 123 insertions(+), 21 deletions(-)

diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index d098cf8aae61..9241034cfede 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -1013,7 +1013,7 @@ int tomoyo_mkdev_perm(const u8 operation, const struct path *path,
 		      const unsigned int mode, unsigned int dev);
 int tomoyo_mount_permission(const char *dev_name, const struct path *path,
 			    const char *type, unsigned long flags,
-			    void *data_page);
+			    const struct path *dev_path);
 int tomoyo_open_control(const u8 type, struct file *file);
 int tomoyo_path2_perm(const u8 operation, const struct path *path1,
 		      const struct path *path2);
diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c
index 322dfd188ada..82ffe7d02814 100644
--- a/security/tomoyo/mount.c
+++ b/security/tomoyo/mount.c
@@ -70,6 +70,7 @@ static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
  * @dir:      Pointer to "struct path".
  * @type:     Name of filesystem type.
  * @flags:    Mount options.
+ * @dev_path: Pre-resolved device/source path. Maybe NULL.
  *
  * Returns 0 on success, negative value otherwise.
  *
@@ -78,11 +79,11 @@ static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
 static int tomoyo_mount_acl(struct tomoyo_request_info *r,
 			    const char *dev_name,
 			    const struct path *dir, const char *type,
-			    unsigned long flags)
+			    unsigned long flags,
+			    const struct path *dev_path)
 	__must_hold_shared(&tomoyo_ss)
 {
 	struct tomoyo_obj_info obj = { };
-	struct path path;
 	struct file_system_type *fstype = NULL;
 	const char *requested_type = NULL;
 	const char *requested_dir_name = NULL;
@@ -134,13 +135,23 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
 			need_dev = 1;
 	}
 	if (need_dev) {
-		/* Get mount point or device file. */
-		if (!dev_name || kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+		if (dev_path) {
+			/* Use pre-resolved path to avoid TOCTOU issues. */
+			obj.path1 = *dev_path;
+			path_get(&obj.path1);
+		} else if (!dev_name) {
 			error = -ENOENT;
 			goto out;
+		} else {
+			struct path path;
+
+			if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+				error = -ENOENT;
+				goto out;
+			}
+			obj.path1 = path;
 		}
-		obj.path1 = path;
-		requested_dev_name = tomoyo_realpath_from_path(&path);
+		requested_dev_name = tomoyo_realpath_from_path(&obj.path1);
 		if (!requested_dev_name) {
 			error = -ENOENT;
 			goto out;
@@ -173,7 +184,7 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
 	if (fstype)
 		put_filesystem(fstype);
 	kfree(requested_type);
-	/* Drop refcount obtained by kern_path(). */
+	/* Drop refcount obtained by kern_path() or path_get(). */
 	if (obj.path1.dentry)
 		path_put(&obj.path1);
 	return error;
@@ -186,13 +197,13 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
  * @path:      Pointer to "struct path".
  * @type:      Name of filesystem type. Maybe NULL.
  * @flags:     Mount options.
- * @data_page: Optional data. Maybe NULL.
+ * @dev_path:  Pre-resolved device/source path. Maybe NULL.
  *
  * Returns 0 on success, negative value otherwise.
  */
 int tomoyo_mount_permission(const char *dev_name, const struct path *path,
 			    const char *type, unsigned long flags,
-			    void *data_page)
+			    const struct path *dev_path)
 {
 	struct tomoyo_request_info r;
 	int error;
@@ -236,7 +247,7 @@ int tomoyo_mount_permission(const char *dev_name, const struct path *path,
 	if (!type)
 		type = "<NULL>";
 	idx = tomoyo_read_lock();
-	error = tomoyo_mount_acl(&r, dev_name, path, type, flags);
+	error = tomoyo_mount_acl(&r, dev_name, path, type, flags, dev_path);
 	tomoyo_read_unlock(idx);
 	return error;
 }
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index c66e02ed8ee3..eec3df35e28e 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -6,6 +6,8 @@
  */
 
 #include <linux/lsm_hooks.h>
+#include <linux/fs_context.h>
+#include <uapi/linux/mount.h>
 #include <uapi/linux/lsm.h>
 #include "common.h"
 
@@ -399,20 +401,104 @@ static int tomoyo_path_chroot(const struct path *path)
 }
 
 /**
- * tomoyo_sb_mount - Target for security_sb_mount().
+ * tomoyo_mount_bind - Target for security_mount_bind().
  *
- * @dev_name: Name of device file. Maybe NULL.
- * @path:     Pointer to "struct path".
- * @type:     Name of filesystem type. Maybe NULL.
- * @flags:    Mount options.
- * @data:     Optional data. Maybe NULL.
+ * @from:    Pointer to "struct path".
+ * @to:      Pointer to "struct path".
+ * @recurse: Whether recursive bind mount or not.
  *
  * Returns 0 on success, negative value otherwise.
  */
-static int tomoyo_sb_mount(const char *dev_name, const struct path *path,
-			   const char *type, unsigned long flags, void *data)
+static int tomoyo_mount_bind(const struct path *from, const struct path *to,
+			     bool recurse)
 {
-	return tomoyo_mount_permission(dev_name, path, type, flags, data);
+	unsigned long flags = MS_BIND | (recurse ? MS_REC : 0);
+
+	return tomoyo_mount_permission(NULL, to, NULL, flags, from);
+}
+
+/**
+ * tomoyo_mount_new - Target for security_mount_new().
+ *
+ * @fc:        Pointer to "struct fs_context".
+ * @mp:        Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags:     Original mount options.
+ * @data:      Optional data. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_new(struct fs_context *fc, const struct path *mp,
+			    int mnt_flags, unsigned long flags, void *data)
+{
+	/* Use original MS_* flags for policy matching */
+	return tomoyo_mount_permission(fc->source, mp, fc->fs_type->name,
+				       flags, NULL);
+}
+
+/**
+ * tomoyo_mount_remount - Target for security_mount_remount().
+ *
+ * @fc:        Pointer to "struct fs_context".
+ * @mp:        Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags:     Original mount options.
+ * @data:      Optional data. Maybe NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_remount(struct fs_context *fc, const struct path *mp,
+				int mnt_flags, unsigned long flags, void *data)
+{
+	/* Use original MS_* flags for policy matching */
+	return tomoyo_mount_permission(NULL, mp, NULL, flags, NULL);
+}
+
+/**
+ * tomoyo_mount_reconfigure - Target for security_mount_reconfigure().
+ *
+ * @mp:        Pointer to "struct path".
+ * @mnt_flags: Mount options.
+ * @flags:     Original mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_reconfigure(const struct path *mp,
+				    unsigned int mnt_flags,
+				    unsigned long flags)
+{
+	/* Use original MS_* flags for policy matching */
+	return tomoyo_mount_permission(NULL, mp, NULL, flags, NULL);
+}
+
+/**
+ * tomoyo_mount_change_type - Target for security_mount_change_type().
+ *
+ * @mp:       Pointer to "struct path".
+ * @ms_flags: Mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_change_type(const struct path *mp, int ms_flags)
+{
+	return tomoyo_mount_permission(NULL, mp, NULL, ms_flags, NULL);
+}
+
+/**
+ * tomoyo_mount_move - Target for security_mount_move().
+ *
+ * @from_path: Pointer to "struct path".
+ * @to_path:   Pointer to "struct path".
+ * @top_path:  Pointer to "struct path" of the mount ending up on top.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_mount_move(const struct path *from_path,
+			     const struct path *to_path,
+			     const struct path *top_path)
+{
+	return tomoyo_mount_permission(NULL, to_path, NULL, MS_MOVE,
+				       from_path);
 }
 
 /**
@@ -576,7 +662,12 @@ static struct security_hook_list tomoyo_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_chmod, tomoyo_path_chmod),
 	LSM_HOOK_INIT(path_chown, tomoyo_path_chown),
 	LSM_HOOK_INIT(path_chroot, tomoyo_path_chroot),
-	LSM_HOOK_INIT(sb_mount, tomoyo_sb_mount),
+	LSM_HOOK_INIT(mount_bind, tomoyo_mount_bind),
+	LSM_HOOK_INIT(mount_new, tomoyo_mount_new),
+	LSM_HOOK_INIT(mount_remount, tomoyo_mount_remount),
+	LSM_HOOK_INIT(mount_reconfigure, tomoyo_mount_reconfigure),
+	LSM_HOOK_INIT(mount_change_type, tomoyo_mount_change_type),
+	LSM_HOOK_INIT(mount_move, tomoyo_mount_move),
 	LSM_HOOK_INIT(sb_umount, tomoyo_sb_umount),
 	LSM_HOOK_INIT(sb_pivotroot, tomoyo_sb_pivotroot),
 	LSM_HOOK_INIT(socket_bind, tomoyo_socket_bind),
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 5/8] landlock: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Replace hook_sb_mount() with granular mount hooks. Landlock denies
all mount operations for sandboxed processes regardless of flags,
so all new hooks share a common hook_mount_deny() helper. The
mount_move hook reuses hook_move_mount() (its new top_path argument
is unused).

Code generated with the assistance of Claude, reviewed by human.

Signed-off-by: Song Liu <song@kernel.org>
---
 security/landlock/fs.c | 44 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 38 insertions(+), 6 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f7e5e4ef9eac..0c56a3aa45ea 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1427,9 +1427,7 @@ static void log_fs_change_topology_dentry(
  * inherit these new constraints.  Anyway, for backward compatibility reasons,
  * a dedicated user space option would be required (e.g. as a ruleset flag).
  */
-static int hook_sb_mount(const char *const dev_name,
-			 const struct path *const path, const char *const type,
-			 const unsigned long flags, void *const data)
+static int hook_mount_deny(const struct path *const path)
 {
 	size_t handle_layer;
 	const struct landlock_cred_security *const subject =
@@ -1443,8 +1441,38 @@ static int hook_sb_mount(const char *const dev_name,
 	return -EPERM;
 }
 
+static int hook_mount_bind(const struct path *const from,
+			   const struct path *const to, bool recurse)
+{
+	return hook_mount_deny(to);
+}
+
+static int hook_mount_new(struct fs_context *fc, const struct path *const mp,
+			  int mnt_flags, unsigned long flags, void *data)
+{
+	return hook_mount_deny(mp);
+}
+
+static int hook_mount_remount(struct fs_context *fc, const struct path *mp,
+			      int mnt_flags, unsigned long flags, void *data)
+{
+	return hook_mount_deny(mp);
+}
+
+static int hook_mount_reconfigure(const struct path *const mp,
+				  unsigned int mnt_flags, unsigned long flags)
+{
+	return hook_mount_deny(mp);
+}
+
+static int hook_mount_change_type(const struct path *const mp, int ms_flags)
+{
+	return hook_mount_deny(mp);
+}
+
 static int hook_move_mount(const struct path *const from_path,
-			   const struct path *const to_path)
+			   const struct path *const to_path,
+			   const struct path *const top_path)
 {
 	size_t handle_layer;
 	const struct landlock_cred_security *const subject =
@@ -1981,8 +2009,12 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
 
 	LSM_HOOK_INIT(sb_delete, hook_sb_delete),
-	LSM_HOOK_INIT(sb_mount, hook_sb_mount),
-	LSM_HOOK_INIT(move_mount, hook_move_mount),
+	LSM_HOOK_INIT(mount_bind, hook_mount_bind),
+	LSM_HOOK_INIT(mount_new, hook_mount_new),
+	LSM_HOOK_INIT(mount_remount, hook_mount_remount),
+	LSM_HOOK_INIT(mount_reconfigure, hook_mount_reconfigure),
+	LSM_HOOK_INIT(mount_change_type, hook_mount_change_type),
+	LSM_HOOK_INIT(mount_move, hook_move_mount),
 	LSM_HOOK_INIT(sb_umount, hook_sb_umount),
 	LSM_HOOK_INIT(sb_remount, hook_sb_remount),
 	LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 4/8] selinux: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Replace selinux_mount() with granular mount hooks, preserving the
same permission checks:

- mount_bind, mount_new, mount_change_type: FILE__MOUNTON
- mount_remount, mount_reconfigure: FILESYSTEM__REMOUNT
- mount_move: FILE__MOUNTON (reuses selinux_move_mount)

The flags, data, and top_path parameters are unused by SELinux.

Code generated with the assistance of Claude, reviewed by human.

Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Song Liu <song@kernel.org>
---
 security/selinux/hooks.c | 52 ++++++++++++++++++++++++++++------------
 1 file changed, 37 insertions(+), 15 deletions(-)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1a713d96206f..6cbe52375b5c 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2802,23 +2802,42 @@ static int selinux_sb_statfs(struct dentry *dentry)
 	return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad);
 }
 
-static int selinux_mount(const char *dev_name,
-			 const struct path *path,
-			 const char *type,
-			 unsigned long flags,
-			 void *data)
+static int selinux_mount_bind(const struct path *from, const struct path *to,
+			      bool recurse)
 {
-	const struct cred *cred = current_cred();
+	return path_has_perm(current_cred(), to, FILE__MOUNTON);
+}
 
-	if (flags & MS_REMOUNT)
-		return superblock_has_perm(cred, path->dentry->d_sb,
-					   FILESYSTEM__REMOUNT, NULL);
-	else
-		return path_has_perm(cred, path, FILE__MOUNTON);
+static int selinux_mount_new(struct fs_context *fc, const struct path *mp,
+			     int mnt_flags, unsigned long flags, void *data)
+{
+	return path_has_perm(current_cred(), mp, FILE__MOUNTON);
+}
+
+static int selinux_mount_remount(struct fs_context *fc, const struct path *mp,
+				 int mnt_flags, unsigned long flags,
+				 void *data)
+{
+	return superblock_has_perm(current_cred(), fc->root->d_sb,
+				   FILESYSTEM__REMOUNT, NULL);
+}
+
+static int selinux_mount_reconfigure(const struct path *mp,
+				     unsigned int mnt_flags,
+				     unsigned long flags)
+{
+	return superblock_has_perm(current_cred(), mp->dentry->d_sb,
+				   FILESYSTEM__REMOUNT, NULL);
+}
+
+static int selinux_mount_change_type(const struct path *mp, int ms_flags)
+{
+	return path_has_perm(current_cred(), mp, FILE__MOUNTON);
 }
 
 static int selinux_move_mount(const struct path *from_path,
-			      const struct path *to_path)
+			      const struct path *to_path,
+			      const struct path *top_path)
 {
 	const struct cred *cred = current_cred();
 
@@ -7554,13 +7573,16 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
 	LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
 	LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
-	LSM_HOOK_INIT(sb_mount, selinux_mount),
+	LSM_HOOK_INIT(mount_bind, selinux_mount_bind),
+	LSM_HOOK_INIT(mount_new, selinux_mount_new),
+	LSM_HOOK_INIT(mount_remount, selinux_mount_remount),
+	LSM_HOOK_INIT(mount_reconfigure, selinux_mount_reconfigure),
+	LSM_HOOK_INIT(mount_change_type, selinux_mount_change_type),
+	LSM_HOOK_INIT(mount_move, selinux_move_mount),
 	LSM_HOOK_INIT(sb_umount, selinux_umount),
 	LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts),
 	LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts),
 
-	LSM_HOOK_INIT(move_mount, selinux_move_mount),
-
 	LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security),
 	LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as),
 
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 3/8] apparmor: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Replace AppArmor's monolithic apparmor_sb_mount() with granular
mount hooks.

Key changes:
- mount_bind: uses the pre-resolved struct path from VFS instead of
  re-resolving dev_name via kern_path(), eliminating a TOCTOU
  vulnerability. aa_bind_mount() now takes a struct path instead of
  a string for the source.
- mount_new, mount_remount: receive the original mount(2) flags and
  data parameters for policy matching via match_mnt_flags() and
  AA_MNT_CONT_MATCH data matching.
- mount_reconfigure: handles MS_REMOUNT|MS_BIND (mount attribute
  reconfiguration) which was previously handled as a remount.
- mount_move: wired to apparmor_move_mount(); its signature gains the
  new top_path argument (currently unused, reserved for
  MOVE_MOUNT_BENEATH policy).
- mount_change_type: propagation type changes.

aa_move_mount_old() is removed since move mounts now go through
security_mount_move() with pre-resolved struct path pointers for
both the old mount(2) and new move_mount(2) APIs.

Code generated with the assistance of Claude, reviewed by human.

Signed-off-by: Song Liu <song@kernel.org>
---
 security/apparmor/include/mount.h |   5 +-
 security/apparmor/lsm.c           | 103 +++++++++++++++++++++++-------
 security/apparmor/mount.c         |  37 ++---------
 3 files changed, 85 insertions(+), 60 deletions(-)

diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h
index 46834f828179..088e2f938cc1 100644
--- a/security/apparmor/include/mount.h
+++ b/security/apparmor/include/mount.h
@@ -31,16 +31,13 @@ int aa_remount(const struct cred *subj_cred,
 
 int aa_bind_mount(const struct cred *subj_cred,
 		  struct aa_label *label, const struct path *path,
-		  const char *old_name, unsigned long flags);
+		  const struct path *old_path, bool recurse);
 
 
 int aa_mount_change_type(const struct cred *subj_cred,
 			 struct aa_label *label, const struct path *path,
 			 unsigned long flags);
 
-int aa_move_mount_old(const struct cred *subj_cred,
-		      struct aa_label *label, const struct path *path,
-		      const char *old_name);
 int aa_move_mount(const struct cred *subj_cred,
 		  struct aa_label *label, const struct path *from_path,
 		  const struct path *to_path);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 71f30dd43f97..d9d5822e4022 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -13,6 +13,7 @@
 #include <linux/mm.h>
 #include <linux/mman.h>
 #include <linux/mount.h>
+#include <linux/fs_context.h>
 #include <linux/namei.h>
 #include <linux/ptrace.h>
 #include <linux/ctype.h>
@@ -698,41 +699,91 @@ static int apparmor_uring_sqpoll(void)
 }
 #endif /* CONFIG_IO_URING */
 
-static int apparmor_sb_mount(const char *dev_name, const struct path *path,
-			     const char *type, unsigned long flags, void *data)
+static int apparmor_mount_bind(const struct path *from, const struct path *to,
+			       bool recurse)
 {
 	struct aa_label *label;
 	int error = 0;
 	bool needput;
 
-	flags &= ~AA_MS_IGNORE_MASK;
+	label = __begin_current_label_crit_section(&needput);
+	if (!unconfined(label))
+		error = aa_bind_mount(current_cred(), label, to, from,
+				      recurse);
+	__end_current_label_crit_section(label, needput);
 
+	return error;
+}
+
+static int apparmor_mount_new(struct fs_context *fc, const struct path *mp,
+			      int mnt_flags, unsigned long flags, void *data)
+{
+	struct aa_label *label;
+	int error = 0;
+	bool needput;
+
+	/* flags and data are from the original mount(2) call */
 	label = __begin_current_label_crit_section(&needput);
-	if (!unconfined(label)) {
-		if (flags & MS_REMOUNT)
-			error = aa_remount(current_cred(), label, path, flags,
-					   data);
-		else if (flags & MS_BIND)
-			error = aa_bind_mount(current_cred(), label, path,
-					      dev_name, flags);
-		else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE |
-				  MS_UNBINDABLE))
-			error = aa_mount_change_type(current_cred(), label,
-						     path, flags);
-		else if (flags & MS_MOVE)
-			error = aa_move_mount_old(current_cred(), label, path,
-						  dev_name);
-		else
-			error = aa_new_mount(current_cred(), label, dev_name,
-					     path, type, flags, data);
-	}
+	if (!unconfined(label))
+		error = aa_new_mount(current_cred(), label, fc->source,
+				     mp, fc->fs_type->name, flags, data);
+	__end_current_label_crit_section(label, needput);
+
+	return error;
+}
+
+static int apparmor_mount_remount(struct fs_context *fc, const struct path *mp,
+				  int mnt_flags, unsigned long flags,
+				  void *data)
+{
+	struct aa_label *label;
+	int error = 0;
+	bool needput;
+
+	/* flags and data are from the original mount(2) call */
+	label = __begin_current_label_crit_section(&needput);
+	if (!unconfined(label))
+		error = aa_remount(current_cred(), label, mp, flags, data);
+	__end_current_label_crit_section(label, needput);
+
+	return error;
+}
+
+static int apparmor_mount_reconfigure(const struct path *mp,
+				      unsigned int mnt_flags,
+				      unsigned long flags)
+{
+	struct aa_label *label;
+	int error = 0;
+	bool needput;
+
+	/* flags are from the original mount(2) call */
+	label = __begin_current_label_crit_section(&needput);
+	if (!unconfined(label))
+		error = aa_remount(current_cred(), label, mp, flags, NULL);
+	__end_current_label_crit_section(label, needput);
+
+	return error;
+}
+
+static int apparmor_mount_change_type(const struct path *mp, int ms_flags)
+{
+	struct aa_label *label;
+	int error = 0;
+	bool needput;
+
+	label = __begin_current_label_crit_section(&needput);
+	if (!unconfined(label))
+		error = aa_mount_change_type(current_cred(), label, mp,
+					     ms_flags);
 	__end_current_label_crit_section(label, needput);
 
 	return error;
 }
 
 static int apparmor_move_mount(const struct path *from_path,
-			       const struct path *to_path)
+			       const struct path *to_path,
+			       const struct path *top_path)
 {
 	struct aa_label *label;
 	int error = 0;
@@ -1669,8 +1720,12 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(capget, apparmor_capget),
 	LSM_HOOK_INIT(capable, apparmor_capable),
 
-	LSM_HOOK_INIT(move_mount, apparmor_move_mount),
-	LSM_HOOK_INIT(sb_mount, apparmor_sb_mount),
+	LSM_HOOK_INIT(mount_bind, apparmor_mount_bind),
+	LSM_HOOK_INIT(mount_new, apparmor_mount_new),
+	LSM_HOOK_INIT(mount_remount, apparmor_mount_remount),
+	LSM_HOOK_INIT(mount_reconfigure, apparmor_mount_reconfigure),
+	LSM_HOOK_INIT(mount_move, apparmor_move_mount),
+	LSM_HOOK_INIT(mount_change_type, apparmor_mount_change_type),
 	LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
 	LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot),
 
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index 2f5d918832c1..1ea0d680437c 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -418,25 +418,17 @@ int aa_remount(const struct cred *subj_cred,
 }
 
 int aa_bind_mount(const struct cred *subj_cred,
-		  struct aa_label *label, const struct path *path,
-		  const char *dev_name, unsigned long flags)
+		       struct aa_label *label, const struct path *path,
+		       const struct path *old_path, bool recurse)
 {
 	struct aa_profile *profile;
 	char *buffer = NULL, *old_buffer = NULL;
-	struct path old_path;
+	unsigned long flags = MS_BIND | (recurse ? MS_REC : 0);
 	int error;
 
 	AA_BUG(!label);
 	AA_BUG(!path);
-
-	if (!dev_name || !*dev_name)
-		return -EINVAL;
-
-	flags &= MS_REC | MS_BIND;
-
-	error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
-	if (error)
-		return error;
+	AA_BUG(!old_path);
 
 	buffer = aa_get_buffer(false);
 	old_buffer = aa_get_buffer(false);
@@ -445,12 +437,11 @@ int aa_bind_mount(const struct cred *subj_cred,
 		goto out;
 
 	error = fn_for_each_confined(label, profile,
-			match_mnt(subj_cred, profile, path, buffer, &old_path,
+			match_mnt(subj_cred, profile, path, buffer, old_path,
 				  old_buffer, NULL, flags, NULL, false));
 out:
 	aa_put_buffer(buffer);
 	aa_put_buffer(old_buffer);
-	path_put(&old_path);
 
 	return error;
 }
@@ -514,24 +505,6 @@ int aa_move_mount(const struct cred *subj_cred,
 	return error;
 }
 
-int aa_move_mount_old(const struct cred *subj_cred, struct aa_label *label,
-		      const struct path *path, const char *orig_name)
-{
-	struct path old_path;
-	int error;
-
-	if (!orig_name || !*orig_name)
-		return -EINVAL;
-	error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
-	if (error)
-		return error;
-
-	error = aa_move_mount(subj_cred, label, &old_path, path);
-	path_put(&old_path);
-
-	return error;
-}
-
 int aa_new_mount(const struct cred *subj_cred, struct aa_label *label,
 		 const char *dev_name, const struct path *path,
 		 const char *type, unsigned long flags, void *data)
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 2/8] apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

path_mount() already strips the magic number from flags before
calling security_sb_mount(), so this check in apparmor_sb_mount()
is a no-op. Remove it.

Code generated with the assistance of Claude, reviewed by human.

Signed-off-by: Song Liu <song@kernel.org>
---
 security/apparmor/lsm.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 88d12e89d115..71f30dd43f97 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -705,10 +705,6 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,
 	int error = 0;
 	bool needput;
 
-	/* Discard magic */
-	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
-		flags &= ~MS_MGC_MSK;
-
 	flags &= ~AA_MS_IGNORE_MASK;
 
 	label = __begin_current_label_crit_section(&needput);
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 1/8] lsm: Add granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu
In-Reply-To: <20260706235053.4104951-1-song@kernel.org>

Add the new granular mount hook declarations and implementations
to the LSM framework:

  mount_bind        - bind mount (pre-resolved source path)
  mount_new         - new filesystem mount (with fs_context)
  mount_remount     - filesystem remount (with fs_context)
  mount_reconfigure - mount flag reconfiguration (MS_REMOUNT|MS_BIND)
  mount_move        - move mount (pre-resolved source, target and top
                      paths; target == top unless MOVE_MOUNT_BENEATH)
  mount_change_type - propagation type changes

These hooks are added alongside the existing security_sb_mount() and
security_move_mount() hooks, which remain in place until all LSMs
are converted.

Code generated with the assistance of Claude, reviewed by human.

Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
Signed-off-by: Song Liu <song@kernel.org>
---
 include/linux/lsm_hook_defs.h |  12 ++++
 include/linux/security.h      |  52 +++++++++++++++++
 kernel/bpf/bpf_lsm.c          |   7 +++
 security/security.c           | 107 ++++++++++++++++++++++++++++++++++
 4 files changed, 178 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec207..06c904cb478e 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -81,6 +81,18 @@ LSM_HOOK(int, 0, sb_clone_mnt_opts, const struct super_block *oldsb,
 	 unsigned long *set_kern_flags)
 LSM_HOOK(int, 0, move_mount, const struct path *from_path,
 	 const struct path *to_path)
+LSM_HOOK(int, 0, mount_bind, const struct path *from, const struct path *to,
+	 bool recurse)
+LSM_HOOK(int, 0, mount_new, struct fs_context *fc, const struct path *mp,
+	 int mnt_flags, unsigned long flags, void *data)
+LSM_HOOK(int, 0, mount_remount, struct fs_context *fc,
+	 const struct path *mp, int mnt_flags, unsigned long flags,
+	 void *data)
+LSM_HOOK(int, 0, mount_reconfigure, const struct path *mp,
+	 unsigned int mnt_flags, unsigned long flags)
+LSM_HOOK(int, 0, mount_move, const struct path *from_path,
+	 const struct path *to_path, const struct path *top_path)
+LSM_HOOK(int, 0, mount_change_type, const struct path *mp, int ms_flags)
 LSM_HOOK(int, -EOPNOTSUPP, dentry_init_security, struct dentry *dentry,
 	 int mode, const struct qstr *name, const char **xattr_name,
 	 struct lsm_context *cp)
diff --git a/include/linux/security.h b/include/linux/security.h
index 153e9043058f..5e207e38c004 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -386,6 +386,18 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
 				unsigned long kern_flags,
 				unsigned long *set_kern_flags);
 int security_move_mount(const struct path *from_path, const struct path *to_path);
+int security_mount_bind(const struct path *from, const struct path *to,
+			bool recurse);
+int security_mount_new(struct fs_context *fc, const struct path *mp,
+		       int mnt_flags, unsigned long flags, void *data);
+int security_mount_remount(struct fs_context *fc, const struct path *mp,
+			   int mnt_flags, unsigned long flags, void *data);
+int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
+			       unsigned long flags);
+int security_mount_move(const struct path *from_path,
+			const struct path *to_path,
+			const struct path *top_path);
+int security_mount_change_type(const struct path *mp, int ms_flags);
 int security_dentry_init_security(struct dentry *dentry, int mode,
 				  const struct qstr *name,
 				  const char **xattr_name,
@@ -854,6 +866,46 @@ static inline int security_move_mount(const struct path *from_path,
 	return 0;
 }
 
+static inline int security_mount_bind(const struct path *from,
+				      const struct path *to, bool recurse)
+{
+	return 0;
+}
+
+static inline int security_mount_new(struct fs_context *fc,
+				     const struct path *mp, int mnt_flags,
+				     unsigned long flags, void *data)
+{
+	return 0;
+}
+
+static inline int security_mount_remount(struct fs_context *fc,
+					 const struct path *mp, int mnt_flags,
+					 unsigned long flags, void *data)
+{
+	return 0;
+}
+
+static inline int security_mount_reconfigure(const struct path *mp,
+					     unsigned int mnt_flags,
+					     unsigned long flags)
+{
+	return 0;
+}
+
+static inline int security_mount_move(const struct path *from_path,
+				      const struct path *to_path,
+				      const struct path *top_path)
+{
+	return 0;
+}
+
+static inline int security_mount_change_type(const struct path *mp,
+					     int ms_flags)
+{
+	return 0;
+}
+
 static inline int security_path_notify(const struct path *path, u64 mask,
 				unsigned int obj_type)
 {
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 1433809bb166..4b1ee9c67d6f 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -385,6 +385,13 @@ BTF_ID(func, bpf_lsm_task_setscheduler)
 BTF_ID(func, bpf_lsm_userns_create)
 BTF_ID(func, bpf_lsm_bdev_alloc_security)
 BTF_ID(func, bpf_lsm_bdev_setintegrity)
+BTF_ID(func, bpf_lsm_move_mount)
+BTF_ID(func, bpf_lsm_mount_bind)
+BTF_ID(func, bpf_lsm_mount_new)
+BTF_ID(func, bpf_lsm_mount_remount)
+BTF_ID(func, bpf_lsm_mount_reconfigure)
+BTF_ID(func, bpf_lsm_mount_move)
+BTF_ID(func, bpf_lsm_mount_change_type)
 BTF_SET_END(sleepable_lsm_hooks)
 
 BTF_SET_START(untrusted_lsm_hooks)
diff --git a/security/security.c b/security/security.c
index 71aea8fdf014..1ede060db3ca 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1182,6 +1182,113 @@ int security_move_mount(const struct path *from_path,
 	return call_int_hook(move_mount, from_path, to_path);
 }
 
+/**
+ * security_mount_bind() - Check permissions for a bind mount
+ * @from: source path
+ * @to: destination mount point
+ * @recurse: whether this is a recursive bind mount
+ *
+ * Check permission before a bind mount is performed. Called with the
+ * source path already resolved, eliminating TOCTOU issues with
+ * string-based dev_name in security_sb_mount().
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_bind(const struct path *from, const struct path *to,
+			bool recurse)
+{
+	return call_int_hook(mount_bind, from, to, recurse);
+}
+
+/**
+ * security_mount_new() - Check permissions for a new mount
+ * @fc: filesystem context with parsed options
+ * @mp: mount point path
+ * @mnt_flags: mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ * @data: filesystem specific data (used by AppArmor)
+ *
+ * Check permission before a new filesystem is mounted. Called after
+ * mount options are parsed, providing access to the fs_context.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_new(struct fs_context *fc, const struct path *mp,
+		       int mnt_flags, unsigned long flags, void *data)
+{
+	return call_int_hook(mount_new, fc, mp, mnt_flags, flags, data);
+}
+
+/**
+ * security_mount_remount() - Check permissions for a remount
+ * @fc: filesystem context with parsed options
+ * @mp: mount point path
+ * @mnt_flags: mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ * @data: filesystem specific data (used by AppArmor)
+ *
+ * Check permission before a filesystem is remounted. Called after
+ * mount options are parsed, providing access to the fs_context.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_remount(struct fs_context *fc, const struct path *mp,
+			   int mnt_flags, unsigned long flags, void *data)
+{
+	return call_int_hook(mount_remount, fc, mp, mnt_flags, flags, data);
+}
+
+/**
+ * security_mount_reconfigure() - Check permissions for mount reconfiguration
+ * @mp: mount point path
+ * @mnt_flags: new mount flags (MNT_*)
+ * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
+ *
+ * Check permission before mount flags are reconfigured (MS_REMOUNT|MS_BIND).
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
+			       unsigned long flags)
+{
+	return call_int_hook(mount_reconfigure, mp, mnt_flags, flags);
+}
+
+/**
+ * security_mount_move() - Check permissions for moving a mount
+ * @from_path: source mount path
+ * @to_path: destination mount point path
+ * @top_path: mount that ends up on top at the destination
+ *
+ * Check permission before a mount is moved.  Called with the destination
+ * pinned under the namespace semaphore, so @to_path reflects the mount
+ * point that will actually be used.  For a plain move @top_path equals
+ * @to_path; for a MOVE_MOUNT_BENEATH move the source is inserted below
+ * @top_path, allowing a policy over the (source, target, top) triple.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_move(const struct path *from_path,
+			const struct path *to_path,
+			const struct path *top_path)
+{
+	return call_int_hook(mount_move, from_path, to_path, top_path);
+}
+
+/**
+ * security_mount_change_type() - Check permissions for propagation changes
+ * @mp: mount point path
+ * @ms_flags: propagation flags (MS_SHARED, MS_PRIVATE, etc.)
+ *
+ * Check permission before mount propagation type is changed.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_change_type(const struct path *mp, int ms_flags)
+{
+	return call_int_hook(mount_change_type, mp, ms_flags);
+}
+
 /**
  * security_path_notify() - Check if setting a watch is allowed
  * @path: file path
-- 
2.53.0-Meta


^ permalink raw reply related

* [PATCH v6 0/8] lsm: Replace security_sb_mount with granular mount hooks
From: Song Liu @ 2026-07-06 23:50 UTC (permalink / raw)
  To: linux-security-module, linux-fsdevel, selinux, apparmor
  Cc: paul, jmorris, serge, viro, brauner, jack, john.johansen,
	stephen.smalley.work, omosnace, mic, gnoack, takedakn,
	penguin-kernel, herton, kernel-team, Song Liu

This series replaces the monolithic security_sb_mount() hook with
per-operation mount hooks, addressing two main issues:

1. TOCTOU: security_sb_mount() receives dev_name as a string, which
   LSMs like AppArmor and Tomoyo re-resolve via kern_path(). The new
   hooks pass pre-resolved struct path pointers where possible (bind
   mount, move mount), eliminating the double-resolution.

2. Conflation: security_sb_mount() handles bind, new mount, remount,
   move, propagation changes, and mount reconfiguration through a
   single hook, requiring LSMs to dispatch on flags internally. The
   new hooks are called at the operation level with appropriate
   context.

The new hooks are:
  mount_bind        - bind mount (pre-resolved source path)
  mount_new         - new filesystem mount (with fs_context)
  mount_remount     - filesystem remount (with fs_context)
  mount_reconfigure - mount flag reconfiguration (MS_REMOUNT|MS_BIND)
  mount_move        - move mount (pre-resolved paths)
  mount_change_type - propagation type changes

mount_new and mount_remount are called after parse_monolithic_mount_data(),
so LSMs have access to the fs_context with parsed mount options. They also
receive the original mount(2) flags and data pointer for LSMs (AppArmor,
Tomoyo) that need them for policy matching.

The series also replaces security_move_mount() with the new mount_move
hook, unifying the old mount(2) MS_MOVE path with the move_mount(2)
syscall path.

All existing LSM behaviors are preserved:
  AppArmor: same policy matching, TOCTOU fixed for bind/move
  SELinux:  same permission checks (FILE__MOUNTON, FILESYSTEM__REMOUNT)
  Landlock: same deny-all for sandboxed processes
  Tomoyo:   same policy matching, TOCTOU fixed for bind/move, unused
            data_page parameter removed


This work is inspired by earlier discussions:

[1] https://lore.kernel.org/bpf/20251127005011.1872209-1-song@kernel.org/
[2] https://lore.kernel.org/linux-security-module/20250708230504.3994335-1-song@kernel.org/

Changes v5 => v6:
1. Move security_mount_bind() and security_mount_move() under
   LOCK_MOUNT()/LOCK_MOUNT_MAYBE_BENEATH(). (Christian Brauner)
2. Rebase.

v5: https://lore.kernel.org/all/20260528182607.3150386-1-song@kernel.org/

Changes v4 => v5:
1. Restructure series: add new hooks in security/ first, then convert
   individual LSMs, then replace old hooks with new hooks in
   fs/namespace.c (single patch), then remove old hooks. This keeps
   all fs/namespace.c changes in one patch. (Christian Brauner)
2. Rebase.

v4: https://lore.kernel.org/linux-security-module/20260515200158.4081915-1-song@kernel.org/

Changes v3 => v4:
1. Move LSM_HOOK_INIT(move_mount, ...) removal from patch 7/7 to each
   per-LSM conversion patch (3/7, 4/7, 5/7). (Paul Moore)
2. Add kdoc comments to tomoyo mount hook functions and rename
   tomoyo_move_mount to tomoyo_mount_move in patch 6/7. (Tetsuo Handa)
3. Add Acked-by from Tetsuo Handa to patch 6/7.

v3: https://lore.kernel.org/linux-security-module/20260509015208.3853132-1-song@kernel.org/

Changes v2 => v3:
1. Rebase.
2. Move security_mount_move() call in vfs_move_mount() from patch 7/7
   to patch 1/7. (Paul Moore)

v2: https://lore.kernel.org/linux-security-module/20260430000315.918964-1-song@kernel.org/

Changes v1 => v2:
1. Rebase.
2. Add Reviewed-by and Tested-by from Stephen Smalley.

v1: https://lore.kernel.org/linux-security-module/20260318184400.3502908-1-song@kernel.org/

Song Liu (8):
  lsm: Add granular mount hooks
  apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
  apparmor: Convert from sb_mount to granular mount hooks
  selinux: Convert from sb_mount to granular mount hooks
  landlock: Convert from sb_mount to granular mount hooks
  tomoyo: Convert from sb_mount to granular mount hooks
  vfs: Replace security_sb_mount/security_move_mount with granular hooks
  lsm: Remove security_sb_mount and security_move_mount

 fs/namespace.c                    |  77 ++++++++++++++----
 include/linux/lsm_hook_defs.h     |  16 +++-
 include/linux/security.h          |  60 +++++++++++---
 kernel/bpf/bpf_lsm.c              |   7 +-
 security/apparmor/include/mount.h |   5 +-
 security/apparmor/lsm.c           | 105 +++++++++++++++++-------
 security/apparmor/mount.c         |  37 ++-------
 security/landlock/fs.c            |  44 ++++++++--
 security/security.c               | 129 +++++++++++++++++++++++-------
 security/selinux/hooks.c          |  52 ++++++++----
 security/tomoyo/common.h          |   2 +-
 security/tomoyo/mount.c           |  31 ++++---
 security/tomoyo/tomoyo.c          | 111 ++++++++++++++++++++++---
 13 files changed, 508 insertions(+), 168 deletions(-)

--
2.53.0-Meta

^ permalink raw reply

* Re: [PATCH bpf-next v4 0/9] Verify BPF signed loader at load time
From: Paul Moore @ 2026-07-06 19:20 UTC (permalink / raw)
  To: Daniel Borkmann
  Cc: ast, kpsingh, James.Bottomley, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <35678f78-2068-4c2a-a1aa-19abf007067a@iogearbox.net>

On Mon, Jul 6, 2026 at 1:47 PM Daniel Borkmann <daniel@iogearbox.net> wrote:
> On 7/6/26 7:13 PM, Paul Moore wrote:
> > On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
> >>
> >> The BPF signing scheme signs a light skeleton's loader program and lets
> >> the loader vouch for everything else: bpftool bakes the SHA256 of the
> >> metadata map into the loader's instructions, signs the instructions, and
> >> the loader compares the (frozen, exclusive) map against that hash from
> >> within BPF once it runs. The construction is sound as a trusted hash
> >> chain, but the kernel itself never attests the metadata, and that split
> >> has been the recurring objection from the LSM / integrity side since the
> >> scheme was proposed.
> >>
> >> This proposal closes both gaps by having the kernel verify the metadata
> >> at BPF_PROG_LOAD time, before the LSM admission hook and before the
> >> verifier, /without/ growing the UAPI. A signed loader binds its metadata
> >> map(s) through the existing fd_array/fd_array_cnt, and exclusive maps
> >> are already bound to the loader's digest via excl_prog_hash. When a
> >> signature is present, the kernel collects the exclusive maps from the
> >> fd_array and appends their frozen contents to the instructions before
> >> PKCS#7 verification, so the signature covers ...
> >>
> >>      insns || metadata_0 || metadata_1 || [...]
> >>
> >> ... in fd_array order. The in-loader hash check is dropped from the
> >> gen_loader entirely: generated loaders carry no verification logic
> >> anymore, and signing or verifying a skeleton becomes an ordinary CMS
> >> operation over bytes that sit verbatim in the skeleton, reproducible
> >> offline. A signed program is either BPF_SIG_UNSIGNED or BPF_SIG_VERIFIED
> >> with nothing in between.
> >>
> >> There is no new UAPI, we now have a single signature scheme, no LSM
> >> code reaching into BPF internals, no new LSM hook, and unsigned loads
> >> are completely unaffected. It is also less complex since the loader
> >> does not need to deal with BTF, an extra kfunc, etc, as proposed in
> >> an earlier series [0]. Tested against full BPF CI which came back
> >> green. For more details and examples, see the documentation patch in
> >> this series.
> >>
> >>    [0] https://lore.kernel.org/bpf/20260522023234.3778588-1-kpsingh@kernel.org/
> >>
> >> v3 -> v4:
> >>    - Fix upper limit in MAX_FD_ARRAY_CNT (Anton)
> >>    - Reject !fd_array && attr->fd_array_cnt (Anton)
> >>    - Add bpftool patch wrt ignored return value of EVP_Digest() (sashiko)
> >>    - Fix setting of gen_loader_fixture_init (sashiko)
> >>    - Fix unused map_fd cleanup branch in selftest (bot+bpf-ci)
> >>    - Remove now unused map->excl member and adjust selftests
> >>    - Added more BPF signed_loader corner case selftest coverage
> >>    - Added Paul's Nack wrt bpf_prog_load LSM hook dispute
> >>    - Added patch 2 to move bigger allocations below fd_array
> >>      resolution (Paul)
> >
> > If you want to squash patch 2/9 and 3/9 together so that one can't
> > easily merge 3/9 without the vzalloc(program) relocation I'll gladly
> > drop my NACK on patch 3/9.
>
> Okay, I can do that and send a v5.

Great, thanks.

> Btw, I saw one sashiko complaint
> about the security_bpf_prog_load() internally not needing the:
>
>    [...]
>          if (unlikely(rc))
>                  security_bpf_prog_free(prog);
>    [...]
>
> since the security_bpf_prog_free() is already called via the regular
> teardown path now. While there are no in-tree LSM users that are
> affected by this, I'll include this as well into the squashed patch,
> so its really only called once and not twice.

I'd prefer to handle it like it was handled in commit 868d43cf8f97
("bpf: Fix security_bpf_prog_load() error handling").  Since
security_bpf_prog_load() acts not only as an access control point, but
potentially an allocation function as well, I think it is more
consistent in the error case to have the hook do whatever cleanup is
necessary before returning to the caller.

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH bpf-next v4 0/9] Verify BPF signed loader at load time
From: Daniel Borkmann @ 2026-07-06 17:47 UTC (permalink / raw)
  To: Paul Moore
  Cc: ast, kpsingh, James.Bottomley, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <CAHC9VhQbqxJFBX+3-JiHR7+QqgWsiAAak7J4-q3QykdGqB7g+g@mail.gmail.com>

Hi Paul,

On 7/6/26 7:13 PM, Paul Moore wrote:
> On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
>>
>> The BPF signing scheme signs a light skeleton's loader program and lets
>> the loader vouch for everything else: bpftool bakes the SHA256 of the
>> metadata map into the loader's instructions, signs the instructions, and
>> the loader compares the (frozen, exclusive) map against that hash from
>> within BPF once it runs. The construction is sound as a trusted hash
>> chain, but the kernel itself never attests the metadata, and that split
>> has been the recurring objection from the LSM / integrity side since the
>> scheme was proposed.
>>
>> This proposal closes both gaps by having the kernel verify the metadata
>> at BPF_PROG_LOAD time, before the LSM admission hook and before the
>> verifier, /without/ growing the UAPI. A signed loader binds its metadata
>> map(s) through the existing fd_array/fd_array_cnt, and exclusive maps
>> are already bound to the loader's digest via excl_prog_hash. When a
>> signature is present, the kernel collects the exclusive maps from the
>> fd_array and appends their frozen contents to the instructions before
>> PKCS#7 verification, so the signature covers ...
>>
>>      insns || metadata_0 || metadata_1 || [...]
>>
>> ... in fd_array order. The in-loader hash check is dropped from the
>> gen_loader entirely: generated loaders carry no verification logic
>> anymore, and signing or verifying a skeleton becomes an ordinary CMS
>> operation over bytes that sit verbatim in the skeleton, reproducible
>> offline. A signed program is either BPF_SIG_UNSIGNED or BPF_SIG_VERIFIED
>> with nothing in between.
>>
>> There is no new UAPI, we now have a single signature scheme, no LSM
>> code reaching into BPF internals, no new LSM hook, and unsigned loads
>> are completely unaffected. It is also less complex since the loader
>> does not need to deal with BTF, an extra kfunc, etc, as proposed in
>> an earlier series [0]. Tested against full BPF CI which came back
>> green. For more details and examples, see the documentation patch in
>> this series.
>>
>>    [0] https://lore.kernel.org/bpf/20260522023234.3778588-1-kpsingh@kernel.org/
>>
>> v3 -> v4:
>>    - Fix upper limit in MAX_FD_ARRAY_CNT (Anton)
>>    - Reject !fd_array && attr->fd_array_cnt (Anton)
>>    - Add bpftool patch wrt ignored return value of EVP_Digest() (sashiko)
>>    - Fix setting of gen_loader_fixture_init (sashiko)
>>    - Fix unused map_fd cleanup branch in selftest (bot+bpf-ci)
>>    - Remove now unused map->excl member and adjust selftests
>>    - Added more BPF signed_loader corner case selftest coverage
>>    - Added Paul's Nack wrt bpf_prog_load LSM hook dispute
>>    - Added patch 2 to move bigger allocations below fd_array
>>      resolution (Paul)
> 
> If you want to squash patch 2/9 and 3/9 together so that one can't
> easily merge 3/9 without the vzalloc(program) relocation I'll gladly
> drop my NACK on patch 3/9.

Okay, I can do that and send a v5. Btw, I saw one sashiko complaint
about the security_bpf_prog_load() internally not needing the:

   [...]
         if (unlikely(rc))
                 security_bpf_prog_free(prog);
   [...]

since the security_bpf_prog_free() is already called via the regular
teardown path now. While there are no in-tree LSM users that are
affected by this, I'll include this as well into the squashed patch,
so its really only called once and not twice.

>> v2 -> v3:
>>    - Added first commit to cache and work on objects in fd_array
>>      which was the most recent issue sashiko rightfully complained
>>    - Added more BPF signed_loader selftest coverage to cover that
>>      usage of sparse fd_array or map fds gets rejected
>>    - I left the security_bpf_prog_load as in v2 given preference
>>      from BPF side over adding new hook
>> v1 -> v2:
>>    - Addressed both sashiko complaints, the TOCTOU bug regarding
>>      fd_array processing, as well as exclusive map checking to
>>      only allow array maps. The validation is now moved into the
>>      verifier before the main verification work happens. This also
>>      gives the opportunity to utilize the verifier log.
>>
>> Daniel Borkmann (9):
>>    bpf: Resolve and cache fd_array objects at load time
>>    bpf: Move bigger allocations below fd_array resolution
>>    bpf: Verify signed loader metadata at load time
>>    libbpf: Drop in-loader metadata check for load-time verification
>>    bpftool: Check EVP_Digest when computing excl_prog_hash
>>    bpftool: Cover loader metadata with the program signature
>>    selftests/bpf: Adjust bpf_map layout in verifier_map_ptr
>>    selftests/bpf: Verify load-time signed loader metadata
>>    Documentation/bpf: Add BPF signing and enforcement doc
>>
>>   Documentation/bpf/index.rst                   |    1 +
>>   Documentation/bpf/signing.rst                 |  496 ++++++++
>>   include/linux/bpf.h                           |    1 -
>>   include/linux/bpf_verifier.h                  |   23 +-
>>   kernel/bpf/syscall.c                          |   83 +-
>>   kernel/bpf/verifier.c                         |  450 ++++++--
>>   tools/bpf/bpftool/gen.c                       |    2 +
>>   tools/bpf/bpftool/sign.c                      |   24 +-
>>   tools/lib/bpf/bpf_gen_internal.h              |    1 -
>>   tools/lib/bpf/gen_loader.c                    |   76 +-
>>   tools/lib/bpf/libbpf_internal.h               |    1 -
>>   tools/lib/bpf/skel_internal.h                 |   31 +-
>>   .../selftests/bpf/prog_tests/signed_loader.c  | 1004 ++++++++++++++---
>>   .../selftests/bpf/progs/test_signed_loader.c  |    9 +-
>>   .../selftests/bpf/progs/verifier_map_ptr.c    |   23 +-
>>   15 files changed, 1786 insertions(+), 439 deletions(-)
>>   create mode 100644 Documentation/bpf/signing.rst

Thanks,
Daniel


^ permalink raw reply

* [PATCH v2] ipe: fix typo
From: Manuel Ebner @ 2026-07-06 17:44 UTC (permalink / raw)
  To: Fan Wu, Jonathan Corbet, Shuah Khan, Paul Moore, Deven Bowers
  Cc: Manuel Ebner, Fan Wu, linux-security-module, linux-doc,
	linux-kernel

Correct Set Group ID to 'sgid=0'

Fixes: ac6731870ed9 ("documentation: add IPE documentation")
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
[v2]
 Add Fixes tag.
 Fix commit message>>
 I assume it's a typo because on english keyboards [shift] + [0] -> ")"
---
 Documentation/admin-guide/LSM/ipe.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
index a756d8158531..bebf14fc2411 100644
--- a/Documentation/admin-guide/LSM/ipe.rst
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -502,11 +502,11 @@ The following table lists the error codes that may appear in the errno field whi
 Event Examples::
 
    type=1404 audit(1653425689.008:55): enforcing=0 old_enforcing=1 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
-   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
+   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0
    type=1327 audit(1653425689.008:55): proctitle="-bash"
 
    type=1404 audit(1653425689.008:55): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
-   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
+   type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0
    type=1327 audit(1653425689.008:55): proctitle="-bash"
 
 This record will always be emitted in conjunction with a ``AUDITSYSCALL`` record for the ``write`` syscall.
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH bpf-next v4 3/9] bpf: Verify signed loader metadata at load time
From: Paul Moore @ 2026-07-06 17:16 UTC (permalink / raw)
  To: Daniel Borkmann
  Cc: ast, kpsingh, James.Bottomley, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <20260706135644.326006-4-daniel@iogearbox.net>

On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
>
> A signed gen_loader program carries the programs, maps and relocations
> it installs in a metadata array map. The loader instructions are covered
> by the PKCS#7 signature, but the metadata map is not: Today the loader
> compares the map contents from within BPF against a hash baked into its
> (signed) instructions, using the kernel-cached map hash. The kernel
> itself never actually attests that the metadata the loader installs is
> the metadata that was signed.
>
> This split is the core of the long-standing objection to the BPF signing
> scheme from the LSM / integrity side: the integrity check of a light
> skeleton only completes once the loader program runs, that is, after the
> security_bpf_prog_load() hook, so at admission time an LSM observes a
> program whose payload has not yet been verified. Auditing the chain
> link is also not a purely cryptographic operation: whoever signs or
> reviews an lskel has to disassemble the loader's preamble to convince
> themselves that the embedded hash check is present and correct [0][1].
> Two acceptable fixes were identified in those threads: Complete the
> integrity check before the admission hook fires, or add a second hook
> that collects the verification result after the loader ran [2]. Covering
> both the loader and its maps directly with the PKCS#7 signature is what
> Blaise Boscaccy's patchsets proposed in several forms. Let's implement
> the former, without growing the UAPI, and in particular as a single
> unified scheme where the signature spans the raw bytes rather than
> derived hashes.
>
> A signed loader binds its metadata map(s) through the existing fd_array,
> and an exclusive map is already bound to a program digest (excl_prog_hash).
> So when a signature is present, collect the exclusive maps from fd_array
> and append their frozen contents to the instructions before verification:
> The signature now covers insns || metadata_0 || metadata_1 || [...] in
> the fd_array order, and verification completes in bpf_check(), once the
> fd_array maps are resolved into used_maps, before the LSM admission hook
> and the rest of verification. A program is either BPF_SIG_UNSIGNED or
> BPF_SIG_VERIFIED, with nothing in between. While folding the fd_array
> maps, a non-exclusive map bound to a signed program is rejected, so every
> map folded into the signature is exclusive. A signed loader that fails
> to cover its metadata thus does not load, and BPF_SIG_VERIFIED always
> means the instructions and every exclusive map are authentic. The maps
> must be frozen so the hashed bytes cannot change before the loader runs;
> the map <-> program digest binding is enforced by the verifier for every
> used map. Binding maps through fd_array_cnt makes the verifier resolve
> and excl-check them (excl_prog_sha vs prog->digest) before it would
> otherwise compute the digest, so compute prog->digest up front in
> bpf_check(), over the unmodified instructions the signature covers, for
> a load that folds metadata.
>
> Unsigned programs are not affected by the signature path; for them the
> LSM admission hook merely moves below fd_array resolution, with minimal
> bounded work in between (see the previous commit). Note, signed loaders
> generated by older libbpf/bpftool versions need to be regenerated; some
> of the recent fixes we've had on the signed loader side require the
> latter already to close gaps.
>
> Finally, some remarks around the security_bpf_prog_load() placement
> given there was pushback, demanding either a new hook or a reuse of
> the existing security_bpf_prog() hook [3]: Just for loading a single
> BPF program it would then need to pass through four layers of LSM hooks:
>
>   1) security_bpf (cmd=PROG_LOAD): for gating various bpf subcmds
>   2) security_bpf_prog_load: historical admission hook (CAP/token,
>      prog_type, attach point), pre-verification
>   3) security_bpf_prog_verify_signature: newly asked admission hook,
>      same role as 2), plus the BPF signature verdict
>   4) security_bpf_prog: gate handing the prog fd back to userspace,
>      verification done & signature verified
>
> The use-cases of 2) and 3) conflate, thus BPF community prefers to just
> keep a total of 3 LSM hooks (as-is today): 3) makes 2) incoherent given
> they are the /same class/ of hook, that is, access-control admission on
> the load and split only by _what_ they can see. Worse, with the split,
> for a signed BPF program security_bpf_prog_load 2) admits a program whose
> signature has not been checked, so a policy gating at 2) is structurally
> unable to express "admit only verified" and every such policy is forced
> onto 3) *anyway*. In other words, one doesn't get two complementary hooks,
> but rather, one real admission hook aka 3) plus a now-degraded /legacy/
> hook 2) that can't answer the question operators actually want to ask.
>
> Reusing security_bpf_prog() 4) for admission is no alternative either:
> it fires only after the entire verifier (and JIT) pipeline ran, so
> denying a not-yet-verified program at that point burns exactly the
> work a denial is supposed to avoid, and by then the program has an id
> assigned and the kallsyms/perf/audit load events fired. Policies are
> free to also consume the signature verdict at 4), but admission control
> belongs into security_bpf_prog_load(). Hence the latter remains the only
> admission hook, merely moved past signature verification; with moving
> large allocations further down into the BPF verifier, there is now only
> minimal work between the old and new location.

See my comment on patch 0/9, but if we can combine this patch with the
vzalloc(progam) patch, I'll be happy to drop my NACK, and you can trim
the last half of this patch description ;)

I suspect that squashing the two patches should also quiet sashiko's
comments as it will be able to see the vzalloc() below the
security_bpf_prog_load() hook in this same patch.

> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Nacked-by: Paul Moore <paul@paul-moore.com> # (don't move bpf_prog_load LSM hook)
> Link: https://lore.kernel.org/bpf/2f71d6c03698eb17d51f7247efde777627ee578a.camel@HansenPartnership.com [0]
> Link: https://lore.kernel.org/lkml/ecf0521ed302db672672ebfbc670ecfba36a6e00.camel@HansenPartnership.com [1]
> Link: https://lore.kernel.org/bpf/88703f00d5b7a779728451008626efa45e42db3d.camel@HansenPartnership.com [2]
> Link: https://lore.kernel.org/bpf/DJOFY21DYUI4.19WKQ3NPZ4H5R@gmail.com [3]
> ---
>  include/linux/bpf_verifier.h |   1 +
>  kernel/bpf/syscall.c         |  76 +--------------
>  kernel/bpf/verifier.c        | 175 +++++++++++++++++++++++++++++++++++
>  3 files changed, 178 insertions(+), 74 deletions(-)

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH bpf-next v4 0/9] Verify BPF signed loader at load time
From: Paul Moore @ 2026-07-06 17:13 UTC (permalink / raw)
  To: Daniel Borkmann
  Cc: ast, kpsingh, James.Bottomley, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <20260706135644.326006-1-daniel@iogearbox.net>

On Mon, Jul 6, 2026 at 9:56 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
>
> The BPF signing scheme signs a light skeleton's loader program and lets
> the loader vouch for everything else: bpftool bakes the SHA256 of the
> metadata map into the loader's instructions, signs the instructions, and
> the loader compares the (frozen, exclusive) map against that hash from
> within BPF once it runs. The construction is sound as a trusted hash
> chain, but the kernel itself never attests the metadata, and that split
> has been the recurring objection from the LSM / integrity side since the
> scheme was proposed.
>
> This proposal closes both gaps by having the kernel verify the metadata
> at BPF_PROG_LOAD time, before the LSM admission hook and before the
> verifier, /without/ growing the UAPI. A signed loader binds its metadata
> map(s) through the existing fd_array/fd_array_cnt, and exclusive maps
> are already bound to the loader's digest via excl_prog_hash. When a
> signature is present, the kernel collects the exclusive maps from the
> fd_array and appends their frozen contents to the instructions before
> PKCS#7 verification, so the signature covers ...
>
>     insns || metadata_0 || metadata_1 || [...]
>
> ... in fd_array order. The in-loader hash check is dropped from the
> gen_loader entirely: generated loaders carry no verification logic
> anymore, and signing or verifying a skeleton becomes an ordinary CMS
> operation over bytes that sit verbatim in the skeleton, reproducible
> offline. A signed program is either BPF_SIG_UNSIGNED or BPF_SIG_VERIFIED
> with nothing in between.
>
> There is no new UAPI, we now have a single signature scheme, no LSM
> code reaching into BPF internals, no new LSM hook, and unsigned loads
> are completely unaffected. It is also less complex since the loader
> does not need to deal with BTF, an extra kfunc, etc, as proposed in
> an earlier series [0]. Tested against full BPF CI which came back
> green. For more details and examples, see the documentation patch in
> this series.
>
>   [0] https://lore.kernel.org/bpf/20260522023234.3778588-1-kpsingh@kernel.org/
>
> v3 -> v4:
>   - Fix upper limit in MAX_FD_ARRAY_CNT (Anton)
>   - Reject !fd_array && attr->fd_array_cnt (Anton)
>   - Add bpftool patch wrt ignored return value of EVP_Digest() (sashiko)
>   - Fix setting of gen_loader_fixture_init (sashiko)
>   - Fix unused map_fd cleanup branch in selftest (bot+bpf-ci)
>   - Remove now unused map->excl member and adjust selftests
>   - Added more BPF signed_loader corner case selftest coverage
>   - Added Paul's Nack wrt bpf_prog_load LSM hook dispute
>   - Added patch 2 to move bigger allocations below fd_array
>     resolution (Paul)

If you want to squash patch 2/9 and 3/9 together so that one can't
easily merge 3/9 without the vzalloc(program) relocation I'll gladly
drop my NACK on patch 3/9.

> v2 -> v3:
>   - Added first commit to cache and work on objects in fd_array
>     which was the most recent issue sashiko rightfully complained
>   - Added more BPF signed_loader selftest coverage to cover that
>     usage of sparse fd_array or map fds gets rejected
>   - I left the security_bpf_prog_load as in v2 given preference
>     from BPF side over adding new hook
> v1 -> v2:
>   - Addressed both sashiko complaints, the TOCTOU bug regarding
>     fd_array processing, as well as exclusive map checking to
>     only allow array maps. The validation is now moved into the
>     verifier before the main verification work happens. This also
>     gives the opportunity to utilize the verifier log.
>
> Daniel Borkmann (9):
>   bpf: Resolve and cache fd_array objects at load time
>   bpf: Move bigger allocations below fd_array resolution
>   bpf: Verify signed loader metadata at load time
>   libbpf: Drop in-loader metadata check for load-time verification
>   bpftool: Check EVP_Digest when computing excl_prog_hash
>   bpftool: Cover loader metadata with the program signature
>   selftests/bpf: Adjust bpf_map layout in verifier_map_ptr
>   selftests/bpf: Verify load-time signed loader metadata
>   Documentation/bpf: Add BPF signing and enforcement doc
>
>  Documentation/bpf/index.rst                   |    1 +
>  Documentation/bpf/signing.rst                 |  496 ++++++++
>  include/linux/bpf.h                           |    1 -
>  include/linux/bpf_verifier.h                  |   23 +-
>  kernel/bpf/syscall.c                          |   83 +-
>  kernel/bpf/verifier.c                         |  450 ++++++--
>  tools/bpf/bpftool/gen.c                       |    2 +
>  tools/bpf/bpftool/sign.c                      |   24 +-
>  tools/lib/bpf/bpf_gen_internal.h              |    1 -
>  tools/lib/bpf/gen_loader.c                    |   76 +-
>  tools/lib/bpf/libbpf_internal.h               |    1 -
>  tools/lib/bpf/skel_internal.h                 |   31 +-
>  .../selftests/bpf/prog_tests/signed_loader.c  | 1004 ++++++++++++++---
>  .../selftests/bpf/progs/test_signed_loader.c  |    9 +-
>  .../selftests/bpf/progs/verifier_map_ptr.c    |   23 +-
>  15 files changed, 1786 insertions(+), 439 deletions(-)
>  create mode 100644 Documentation/bpf/signing.rst

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH bpf-next v3 2/6] bpf: Verify signed loader metadata at load time
From: Paul Moore @ 2026-07-06 17:01 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Daniel Borkmann, ast, kpsingh, James.Bottomley, bboscaccy, memxor,
	torvalds, a.s.protopopov, bpf, linux-security-module
In-Reply-To: <DJOFY21DYUI4.19WKQ3NPZ4H5R@gmail.com>

On Thu, Jul 2, 2026 at 6:33 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Thu Jul 2, 2026 at 3:05 PM PDT, Paul Moore wrote:

...

> > With this patch, even in cases where SELinux will prevent the BPF
> > program load operation, it is not able to do so before the process
> > triggers a potential sizable memory allocation, opening the door for a
> > local DoS attack.
>
> Not true either. Worst case is 1M which is 8Mbyte.
> Not even close to anything DoS worthy.

That's 8MB in a single allocation that can be triggered directly by
userspace.  I suspect most systems allow a fair number of processes
per user; that 8MB allocation can quickly multiply into a big number,
especially for more modest, non-server systems/VMs.

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH bpf-next v4 1/9] bpf: Resolve and cache fd_array objects at load time
From: Anton Protopopov @ 2026-07-06 15:42 UTC (permalink / raw)
  To: Daniel Borkmann
  Cc: ast, kpsingh, James.Bottomley, paul, bboscaccy, memxor, torvalds,
	bpf, linux-security-module
In-Reply-To: <20260706135644.326006-2-daniel@iogearbox.net>

On 26/07/06 03:56PM, Daniel Borkmann wrote:
> The fd_array passed to BPF_PROG_LOAD carries the map and module BTF file
> descriptors a program binds. The verifier reads it more than once during
> a load: process_fd_array() walks it to bind the maps and BTFs, and
> check_and_resolve_insns() and the kfunc BTF resolver later read it again
> to resolve the program's BPF_PSEUDO_MAP_IDX* and module kfunc refs.
> 
> For signed BPF, we need these upfront in memory, thus resolve each fd to
> its object once and cache it by fd_array index, then bind that cached
> object for the rest of the load. env->fd_array becomes a small per-slot
> {map, btf} cache rather than a bpfptr_t; every later reference is then
> an in-bounds lookup of an already-resolved object, and an index outside
> the cache is rejected instead of read from user memory:
> 
>   - continuous (fd_array_cnt given): the caller declares the length and
>     every entry is resolved and bound up front (used also by the BPF
>     signed loader)
> 
>   - sparse (no fd_array_cnt): left as the legacy path with no fd_array
>     cache; each reference reads its fd from the caller's fd_array and
>     resolves it on the spot. Deduplication in used_maps and the kfunc BTF
>     table keeps this correct, and only unsigned programs use this shape.
> 
> Split these into separate helpers to make it easier to follow.
> 
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Anton Protopopov <a.s.protopopov@gmail.com>
> ---
>  include/linux/bpf_verifier.h |  22 +++-
>  kernel/bpf/verifier.c        | 223 +++++++++++++++++++++++++++--------
>  2 files changed, 193 insertions(+), 52 deletions(-)

Acked-by: Anton Protopopov <a.s.protopopov@gmail.com>

> [...]
> -- 
> 2.43.0
> 

^ permalink raw reply

* Re: [PATCH bpf-next v4 3/9] bpf: Verify signed loader metadata at load time
From: bot+bpf-ci @ 2026-07-06 15:09 UTC (permalink / raw)
  To: daniel, ast
  Cc: kpsingh, James.Bottomley, paul, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260706135644.326006-4-daniel@iogearbox.net>

[-- Attachment #1: Type: text/plain, Size: 3502 bytes --]

> A signed gen_loader program carries the programs, maps and relocations
> it installs in a metadata array map. The loader instructions are covered
> by the PKCS#7 signature, but the metadata map is not: Today the loader
> compares the map contents from within BPF against a hash baked into its
> (signed) instructions, using the kernel-cached map hash. The kernel
> itself never actually attests that the metadata the loader installs is
> the metadata that was signed.
>
> [ ... ]
>
> A signed loader binds its metadata map(s) through the existing fd_array,
> and an exclusive map is already bound to a program digest (excl_prog_hash).
> So when a signature is present, collect the exclusive maps from fd_array
> and append their frozen contents to the instructions before verification:
> The signature now covers insns || metadata_0 || metadata_1 || [...] in
> the fd_array order, and verification completes in bpf_check(), once the
> fd_array maps are resolved into used_maps, before the LSM admission hook
> and the rest of verification.
>
> [ ... ]
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>

This changes how the existing BPF program signing scheme attests the
loader payload, and reworks the verification that was added when
signature support first landed.

Should it carry a Fixes: tag pointing at the commit that introduced the
signing scheme, for example:

  Fixes: 349271568303 ("bpf: Implement signature verification for BPF programs")

That commit added attr->signature/signature_size/keyring_id and the
original bpf_prog_verify_signature() that only covered prog->insnsi. The
later commit ea1535e28bb3 ("bpf: Limit bpf program signature size")
already uses this same target.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index e227c76a8dd9..65f53128fc33 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c

[ ... ]

> @@ -19909,11 +20068,27 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
>  	ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
>  	if (ret)
>  		goto err_free_env;
> +	if (env->signature) {
> +		ret = bpf_prog_calc_tag(env->prog);
> +		if (ret < 0)
> +			goto err_prep;
> +	}
>
>  	ret = process_fd_array(env, attr, uattr);
>  	if (ret)
>  		goto err_prep;
>
> +	if (env->signature) {
> +		ret = bpf_prog_verify_signature(env, attr, uattr.is_kernel);
> +		if (ret)
> +			goto err_prep;
> +	}
> +
> +	ret = security_bpf_prog_load(env->prog, attr, env->prog->aux->token,
> +				     uattr.is_kernel);
> +	if (ret)
> +		goto err_prep;

This moves the security_bpf_prog_load() admission hook out of
bpf_prog_load() in syscall.c and into bpf_check(), now placed after
process_fd_array().

At this point the program instructions have already been allocated and
copied in, up to the maximum program size. Does relocating the hook here
mean that for a policy which denies a load, the denial now happens only
after that allocation, rather than before it as in the previous
placement?

This placement carries a Nacked-by from the LSM maintainer on the same
grounds, and the code keeps the hook in the new location. Is it worth
resolving that objection before this lands, or is the intent to leave it
for the merge-window decision?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28797884155

^ permalink raw reply

* Re: [PATCH bpf-next v4 4/9] libbpf: Drop in-loader metadata check for load-time verification
From: bot+bpf-ci @ 2026-07-06 14:50 UTC (permalink / raw)
  To: daniel, ast
  Cc: kpsingh, James.Bottomley, paul, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260706135644.326006-5-daniel@iogearbox.net>

[-- Attachment #1: Type: text/plain, Size: 2897 bytes --]

> commit eb80a2d94081929e7d91f275cdeda214433dead7
> Author: Daniel Borkmann <daniel@iogearbox.net>
> Date:   Mon Jul 6 15:56:39 2026 +0200
>
>     libbpf: Drop in-loader metadata check for load-time verification
>
>     The signed gen_loader used to police its own metadata map from within
>     BPF: emit_signature_match() read the kernel-cached map->sha[] back
>     through hardcoded struct bpf_map offsets and compared it against a hash
>     that compute_sha_update_offsets() baked into the signed instructions,
>     after a BPF_OBJ_GET_INFO_BY_FD round-trip to populate map->sha[].
>
>     The kernel now verifies the metadata at BPF_PROG_LOAD time by folding
>     the frozen contents of the loader's exclusive fd_array maps into the
>     signature, so the loader no longer checks anything itself. Generated
>     loaders thus carry no verification logic of their own anymore: Nothing
>     in the signing chain depends on emitted loader bytecode doing the right
>     thing.
>
>     On the loading side, skel_internal.h now sets fd_array_cnt for a signed
>     load so the kernel scans fd_array for the exclusive metadata map -
>     still frozen, as the kernel requires - and the BPF_OBJ_GET_INFO_BY_FD
>     round-trip to populate map->sha[] is gone. The struct bpf_map layout
>     BUILD_BUG_ON()s on the kernel side are removed as well: they only
>     pinned the ABI for the in-BPF read of map->sha[] that is no longer
>     needed. Same for the map->excl member. Note: gen_hash is retained; it
>     still marks a loader as signed so an untrusted host cannot re-dimension
>     maps or override initial values now covered by the signature.
>
>     Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>

In reviewing the patch series discussion on lore, Paul Moore raised a
security concern in v3 about the companion patch that moves the
security_bpf_prog_load() LSM hook into bpf_check() in verifier.c. He
noted that the moved hook placement allows processes to allocate kernel
memory (including bpf_verifier_env via kvzalloc_obj) before SELinux can
deny the BPF program load operation, creating a potential local DoS
attack vector where even denied operations trigger sizable allocations.

Paul explicitly Nacked the change with:

    Nacked-by: Paul Moore (don't move bpf_prog_load LSM hook)
    <paul@paul-moore.com>

Reference:
https://lore.kernel.org/bpf/CAHC9VhQD4RHsGiTMVCCTN+eB71-Ueopke8XghExpNJ2PSNa_jQ@mail.gmail.com/

Paul did not comment on v4, leaving it unclear whether his concern was
addressed or if he accepted the change. Has this security regression
concern been resolved, or does the hook movement remain in the series?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28797884155

^ permalink raw reply

* [PATCH bpf-next v4 6/9] bpftool: Cover loader metadata with the program signature
From: Daniel Borkmann @ 2026-07-06 13:56 UTC (permalink / raw)
  To: ast
  Cc: kpsingh, James.Bottomley, paul, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <20260706135644.326006-1-daniel@iogearbox.net>

bpftool_prog_sign() signed only the loader instructions. The metadata
blob the loader installs was left to an in-loader hash check, which
the kernel now performs at load time over insns || metadata.

Sign that same concatenation: pass the metadata blob (gen_loader_opts
data) through to bpftool_prog_sign() and feed insns || metadata to
CMS_final(). The excl_prog_hash stays a digest of the instructions
alone; it binds the metadata map to the loader and is matched against
prog->digest by the verifier, independent of what the signature covers.

The signed artifact is now plain data: both bytes the signature
covers are embedded verbatim in the generated skeleton, so signing
and verifying an lskel is an ordinary CMS operation that a signer or
auditor can perform (or reproduce) offline, without analyzing loader
bytecode to establish what the signature actually attests to.

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 tools/bpf/bpftool/gen.c  |  2 ++
 tools/bpf/bpftool/sign.c | 17 +++++++++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index 6ae7262ebe0c..a01d06d22d1a 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -793,6 +793,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 	if (sign_progs) {
 		sopts.insns = opts.insns;
 		sopts.insns_sz = opts.insns_sz;
+		sopts.data = opts.data;
+		sopts.data_sz = opts.data_sz;
 		sopts.excl_prog_hash = prog_sha;
 		sopts.excl_prog_hash_sz = sizeof(prog_sha);
 		sopts.signature = sig_buf;
diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index 1257dba8ef2f..88726a6db6d0 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -135,9 +135,21 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	CMS_ContentInfo *cms = NULL;
 	long actual_sig_len = 0;
 	X509 *x509 = NULL;
+	void *data = NULL;
+	size_t data_sz;
 	int err = 0;
 
-	bd_in = BIO_new_mem_buf(opts->insns, opts->insns_sz);
+	data_sz = (size_t)opts->insns_sz + opts->data_sz;
+	data = malloc(data_sz);
+	if (!data) {
+		err = -ENOMEM;
+		goto cleanup;
+	}
+	memcpy(data, opts->insns, opts->insns_sz);
+	if (opts->data_sz)
+		memcpy((char *)data + opts->insns_sz, opts->data, opts->data_sz);
+
+	bd_in = BIO_new_mem_buf(data, data_sz);
 	if (!bd_in) {
 		err = -ENOMEM;
 		goto cleanup;
@@ -181,7 +193,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 		goto cleanup;
 	}
 
-		bd_out = BIO_new(BIO_s_mem());
+	bd_out = BIO_new(BIO_s_mem());
 	if (!bd_out) {
 		err = -ENOMEM;
 		goto cleanup;
@@ -215,6 +227,7 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 	X509_free(x509);
 	EVP_PKEY_free(private_key);
 	BIO_free(bd_in);
+	free(data);
 	DISPLAY_OSSL_ERR(err < 0);
 	return err;
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH bpf-next v4 5/9] bpftool: Check EVP_Digest when computing excl_prog_hash
From: Daniel Borkmann @ 2026-07-06 13:56 UTC (permalink / raw)
  To: ast
  Cc: kpsingh, James.Bottomley, paul, bboscaccy, memxor, torvalds,
	a.s.protopopov, bpf, linux-security-module
In-Reply-To: <20260706135644.326006-1-daniel@iogearbox.net>

bpftool_prog_sign() ignores the return value of EVP_Digest(). If the
digest computation fails (context allocation failure, or a digest
fetch failure under OpenSSL), EVP_Digest() returns 0 and leaves the
output buffer untouched, but the function still reports success.

Fixes: 40863f4d6ef2 ("bpftool: Add support for signing BPF programs")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 tools/bpf/bpftool/sign.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c
index f9b742f4bb10..1257dba8ef2f 100644
--- a/tools/bpf/bpftool/sign.c
+++ b/tools/bpf/bpftool/sign.c
@@ -175,8 +175,11 @@ int bpftool_prog_sign(struct bpf_load_and_run_opts *opts)
 		goto cleanup;
 	}
 
-	EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
-		   &opts->excl_prog_hash_sz, EVP_sha256(), NULL);
+	if (EVP_Digest(opts->insns, opts->insns_sz, opts->excl_prog_hash,
+		       &opts->excl_prog_hash_sz, EVP_sha256(), NULL) != 1) {
+		err = -EIO;
+		goto cleanup;
+	}
 
 		bd_out = BIO_new(BIO_s_mem());
 	if (!bd_out) {
-- 
2.43.0


^ permalink raw reply related


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