Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH v5 4/8] selinux: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 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: <20260528182607.3150386-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 and data 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 | 49 ++++++++++++++++++++++++++++------------
 1 file changed, 35 insertions(+), 14 deletions(-)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0f704380a8c8..c8de175bde04 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2802,19 +2802,37 @@ 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,
@@ -7558,13 +7576,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 v5 3/8] apparmor: Convert from sb_mount to granular mount hooks
From: Song Liu @ 2026-05-28 18:26 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: <20260528182607.3150386-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: reuses apparmor_move_mount() which already handles
  pre-resolved paths.
- 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           | 100 +++++++++++++++++++++++-------
 security/apparmor/mount.c         |  37 ++---------
 3 files changed, 83 insertions(+), 59 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 4415bca5889c..b0de7f316f51 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,34 +699,83 @@ 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;
@@ -1655,8 +1705,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 523570aa1a5a..38b40e16014f 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 v5 2/8] apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
From: Song Liu @ 2026-05-28 18:26 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: <20260528182607.3150386-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 3491e9f60194..4415bca5889c 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 v5 1/8] lsm: Add granular mount hooks
From: Song Liu @ 2026-05-28 18:26 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: <20260528182607.3150386-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 paths)
  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      |  50 +++++++++++++++++
 kernel/bpf/bpf_lsm.c          |   7 +++
 security/security.c           | 101 ++++++++++++++++++++++++++++++++++
 4 files changed, 170 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 2b8dfb35caed..98f0fe382665 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)
+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 41d7367cf403..b1b3da51a88d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -386,6 +386,17 @@ 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);
+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 +865,45 @@ 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)
+{
+	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 c5c925f00202..aa228372cfb4 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -382,6 +382,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 4e999f023651..b7ec0ec7af26 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1182,6 +1182,107 @@ 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
+ *
+ * Check permission before a mount is moved.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mount_move(const struct path *from_path,
+			const struct path *to_path)
+{
+	return call_int_hook(mount_move, from_path, to_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 v5 0/8] lsm: Replace security_sb_mount with granular mount hooks
From: Song Liu @ 2026-05-28 18:25 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 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                    |  41 +++++++---
 include/linux/lsm_hook_defs.h     |  14 +++-
 include/linux/security.h          |  56 +++++++++++---
 kernel/bpf/bpf_lsm.c              |   7 +-
 security/apparmor/include/mount.h |   5 +-
 security/apparmor/lsm.c           | 102 ++++++++++++++++++-------
 security/apparmor/mount.c         |  37 ++--------
 security/landlock/fs.c            |  41 ++++++++--
 security/security.c               | 119 +++++++++++++++++++++++-------
 security/selinux/hooks.c          |  49 ++++++++----
 security/tomoyo/common.h          |   2 +-
 security/tomoyo/mount.c           |  31 +++++---
 security/tomoyo/tomoyo.c          | 109 ++++++++++++++++++++++++---
 13 files changed, 457 insertions(+), 156 deletions(-)

--
2.53.0-Meta

^ permalink raw reply

* [PATCH 6.12.y] landlock: Fix TCP handling of short AF_UNSPEC addresses
From: Maximilian Heyne @ 2026-05-28 12:14 UTC (permalink / raw)
  To: stable
  Cc: Maximilian Heyne, Matthieu Buffet, Mickaël Salaün,
	Günther Noack, Paul Moore, James Morris, Serge E. Hallyn,
	Konstantin Meskhidze, linux-security-module, linux-kernel

From: Matthieu Buffet <matthieu@buffet.re>

[ Upstream commit e4d82cbce2258f454634307fdabf33aa46b61ab0 ]

current_check_access_socket() treats AF_UNSPEC addresses as
AF_INET ones, and only later adds special case handling to
allow connect(AF_UNSPEC), and on IPv4 sockets
bind(AF_UNSPEC+INADDR_ANY).
This would be fine except AF_UNSPEC addresses can be as
short as a bare AF_UNSPEC sa_family_t field, and nothing
more. The AF_INET code path incorrectly enforces a length of
sizeof(struct sockaddr_in) instead.

Move AF_UNSPEC edge case handling up inside the switch-case,
before the address is (potentially incorrectly) treated as
AF_INET.

Fixes: fff69fb03dde ("landlock: Support network rules with TCP bind and connect")
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
Link: https://lore.kernel.org/r/20251027190726.626244-4-matthieu@buffet.re
Signed-off-by: Mickaël Salaün <mic@digikod.net>
[ There was a conflict due to missing commit 9f74411a40ce ("landlock:
  Log TCP bind and connect denials") ]
Signed-off-by: Maximilian Heyne <mheyne@amazon.de>
---

Backporting this because landlock/net_test deterministically fails as
the selftest from the patch series "Fix TCP short AF_UNSPEC handling"
(https://lore.kernel.org/all/20251027190726.626244-1-matthieu@buffet.re/)
has been backported to 6.12 but not this patch due to conflicts.

---

 security/landlock/net.c | 118 +++++++++++++++++++++++-----------------
 1 file changed, 67 insertions(+), 51 deletions(-)

diff --git a/security/landlock/net.c b/security/landlock/net.c
index 104b6c01fe503..53d479893475f 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -72,6 +72,61 @@ static int current_check_access_socket(struct socket *const sock,
 
 	switch (address->sa_family) {
 	case AF_UNSPEC:
+		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+			/*
+			 * Connecting to an address with AF_UNSPEC dissolves
+			 * the TCP association, which have the same effect as
+			 * closing the connection while retaining the socket
+			 * object (i.e., the file descriptor).  As for dropping
+			 * privileges, closing connections is always allowed.
+			 *
+			 * For a TCP access control system, this request is
+			 * legitimate. Let the network stack handle potential
+			 * inconsistencies and return -EINVAL if needed.
+			 */
+			return 0;
+		} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+			/*
+			 * Binding to an AF_UNSPEC address is treated
+			 * differently by IPv4 and IPv6 sockets. The socket's
+			 * family may change under our feet due to
+			 * setsockopt(IPV6_ADDRFORM), but that's ok: we either
+			 * reject entirely or require
+			 * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so
+			 * it cannot be used to bypass the policy.
+			 *
+			 * IPv4 sockets map AF_UNSPEC to AF_INET for
+			 * retrocompatibility for bind accesses, only if the
+			 * address is INADDR_ANY (cf. __inet_bind). IPv6
+			 * sockets always reject it.
+			 *
+			 * Checking the address is required to not wrongfully
+			 * return -EACCES instead of -EAFNOSUPPORT or -EINVAL.
+			 * We could return 0 and let the network stack handle
+			 * these checks, but it is safer to return a proper
+			 * error and test consistency thanks to kselftest.
+			 */
+			if (sock->sk->__sk_common.skc_family == AF_INET) {
+				const struct sockaddr_in *const sockaddr =
+					(struct sockaddr_in *)address;
+
+				if (addrlen < sizeof(struct sockaddr_in))
+					return -EINVAL;
+
+				if (sockaddr->sin_addr.s_addr !=
+				    htonl(INADDR_ANY))
+					return -EAFNOSUPPORT;
+			} else {
+				if (addrlen < SIN6_LEN_RFC2133)
+					return -EINVAL;
+				else
+					return -EAFNOSUPPORT;
+			}
+		} else {
+			WARN_ON_ONCE(1);
+		}
+		/* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */
+		fallthrough;
 	case AF_INET:
 		if (addrlen < sizeof(struct sockaddr_in))
 			return -EINVAL;
@@ -90,57 +145,18 @@ static int current_check_access_socket(struct socket *const sock,
 		return 0;
 	}
 
-	/* Specific AF_UNSPEC handling. */
-	if (address->sa_family == AF_UNSPEC) {
-		/*
-		 * Connecting to an address with AF_UNSPEC dissolves the TCP
-		 * association, which have the same effect as closing the
-		 * connection while retaining the socket object (i.e., the file
-		 * descriptor).  As for dropping privileges, closing
-		 * connections is always allowed.
-		 *
-		 * For a TCP access control system, this request is legitimate.
-		 * Let the network stack handle potential inconsistencies and
-		 * return -EINVAL if needed.
-		 */
-		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
-			return 0;
-
-		/*
-		 * For compatibility reason, accept AF_UNSPEC for bind
-		 * accesses (mapped to AF_INET) only if the address is
-		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
-		 * required to not wrongfully return -EACCES instead of
-		 * -EAFNOSUPPORT.
-		 *
-		 * We could return 0 and let the network stack handle these
-		 * checks, but it is safer to return a proper error and test
-		 * consistency thanks to kselftest.
-		 */
-		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
-			/* addrlen has already been checked for AF_UNSPEC. */
-			const struct sockaddr_in *const sockaddr =
-				(struct sockaddr_in *)address;
-
-			if (sock->sk->__sk_common.skc_family != AF_INET)
-				return -EINVAL;
-
-			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
-				return -EAFNOSUPPORT;
-		}
-	} else {
-		/*
-		 * Checks sa_family consistency to not wrongfully return
-		 * -EACCES instead of -EINVAL.  Valid sa_family changes are
-		 * only (from AF_INET or AF_INET6) to AF_UNSPEC.
-		 *
-		 * We could return 0 and let the network stack handle this
-		 * check, but it is safer to return a proper error and test
-		 * consistency thanks to kselftest.
-		 */
-		if (address->sa_family != sock->sk->__sk_common.skc_family)
-			return -EINVAL;
-	}
+	/*
+	 * Checks sa_family consistency to not wrongfully return
+	 * -EACCES instead of -EINVAL.  Valid sa_family changes are
+	 * only (from AF_INET or AF_INET6) to AF_UNSPEC.
+	 *
+	 * We could return 0 and let the network stack handle this
+	 * check, but it is safer to return a proper error and test
+	 * consistency thanks to kselftest.
+	 */
+	if (address->sa_family != sock->sk->__sk_common.skc_family &&
+	    address->sa_family != AF_UNSPEC)
+		return -EINVAL;
 
 	id.key.data = (__force uintptr_t)port;
 	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
-- 
2.50.1




Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597

^ permalink raw reply related

* [PATCH 11/11] hornet: scripts: Improve argument handling and error messages
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

Spaces in file names may have caused invocation errors. Additionally
add helpful error messages if we are unable to extract the requested
payload.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/extract-insn.sh | 14 +++++++++++---
 scripts/hornet/extract-map.sh  | 15 ++++++++++++---
 scripts/hornet/extract-skel.sh | 25 ++++++++++++++++++++++---
 3 files changed, 45 insertions(+), 9 deletions(-)

diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index 3e7bed049acb6..e0c7a4ae967f3 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -21,7 +21,15 @@ EXPECTED_ARGS=1
 
 if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
-else
-    printf $(gcc -E $1 | grep "opts_insn" | \
-		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
+
+HEADER="$1"
+STR=$(gcc -E "$HEADER" | \
+      sed -n 's/.*char opts_insn[[:space:]]*\[\][^=]*=[[:space:]]*"\(.*\)"[[:space:]]*;.*/\1/p')
+
+if [ -z "$STR" ]; then
+    echo "$(basename "$0"): no opts_insn[] declaration found in $HEADER" >&2
+    exit 1
+fi
+
+printf '%b' "$STR"
diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index 1d92ebe1a04b5..2d68bb473d889 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -21,7 +21,16 @@ EXPECTED_ARGS=1
 
 if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
-else
-    printf $(gcc -E $1 | grep "opts_data" | \
-		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
+
+# See extract-insn.sh for the rationale behind the sed/printf '%b' shape.
+HEADER="$1"
+STR=$(gcc -E "$HEADER" | \
+      sed -n 's/.*char opts_data[[:space:]]*\[\][^=]*=[[:space:]]*"\(.*\)"[[:space:]]*;.*/\1/p')
+
+if [ -z "$STR" ]; then
+    echo "$(basename "$0"): no opts_data[] declaration found in $HEADER" >&2
+    exit 1
+fi
+
+printf '%b' "$STR"
diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index e115f4b7fdf74..c980f245f4a39 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -21,7 +21,26 @@ EXPECTED_ARGS=2
 
 if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
-else
-    printf $(gcc -E $1 | grep "static const char opts_$2" | \
-		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
+
+# See extract-insn.sh for the rationale behind the sed/printf '%b' shape.
+HEADER="$1"
+FIELD="$2"
+
+# Reject anything that could escape into the sed pattern below.
+case "$FIELD" in
+    *[!A-Za-z0-9_]*|"")
+        echo "$(basename "$0"): invalid field name '$FIELD'" >&2
+        exit 1
+        ;;
+esac
+
+STR=$(gcc -E "$HEADER" | \
+      sed -n "s/.*char opts_${FIELD}[[:space:]]*\[\][^=]*=[[:space:]]*\"\(.*\)\"[[:space:]]*;.*/\1/p")
+
+if [ -z "$STR" ]; then
+    echo "$(basename "$0"): no opts_${FIELD}[] declaration found in $HEADER" >&2
+    exit 1
+fi
+
+printf '%b' "$STR"
-- 
2.53.0


^ permalink raw reply related

* [PATCH 10/11] hornet: scripts: harden scripts to handle trailing whitespace
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

Trailing whitespace after the semicolon in the header files may have
caused the binary extracted payload to be corrupted due to a missing
anchor.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/extract-insn.sh | 2 +-
 scripts/hornet/extract-map.sh  | 2 +-
 scripts/hornet/extract-skel.sh | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index e136932275aa5..3e7bed049acb6 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
 else
     printf $(gcc -E $1 | grep "opts_insn" | \
-		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index 058ac1b32d743..1d92ebe1a04b5 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
 else
     printf $(gcc -E $1 | grep "opts_data" | \
-		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index abc435e2bcd4e..e115f4b7fdf74 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -23,5 +23,5 @@ if [ $ARGC -ne $EXPECTED_ARGS ] ; then
     usage
 else
     printf $(gcc -E $1 | grep "static const char opts_$2" | \
-		 awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+		 awk -F"=" '{print $2}' | sed 's/[[:space:];]*$//' | sed 's/\"//g')
 fi
-- 
2.53.0


^ permalink raw reply related

* [PATCH 09/11] hornet: scripts: set a non-zero error code for usage
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

It was possible that build scripts may continue if arguments were
missing.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/extract-insn.sh | 10 +++++-----
 scripts/hornet/extract-map.sh  | 10 +++++-----
 scripts/hornet/extract-skel.sh | 10 +++++-----
 scripts/hornet/write-sig.sh    | 10 +++++-----
 4 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
index 52338f057ff6b..e136932275aa5 100755
--- a/scripts/hornet/extract-insn.sh
+++ b/scripts/hornet/extract-insn.sh
@@ -8,11 +8,11 @@
 # License as published by the Free Software Foundation.
 
 function usage() {
-    echo "Sample script for extracting instructions"
-    echo "autogenerated eBPF lskel headers"
-    echo ""
-    echo "USAGE: header_file"
-    exit
+    echo "Sample script for extracting instructions" >&2
+    echo "autogenerated eBPF lskel headers" >&2
+    echo "" >&2
+    echo "USAGE: header_file" >&2
+    exit 1
 }
 
 ARGC=$#
diff --git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
index c309f505c6238..058ac1b32d743 100755
--- a/scripts/hornet/extract-map.sh
+++ b/scripts/hornet/extract-map.sh
@@ -8,11 +8,11 @@
 # License as published by the Free Software Foundation.
 
 function usage() {
-    echo "Sample script for extracting instructions"
-    echo "autogenerated eBPF lskel headers"
-    echo ""
-    echo "USAGE: header_file"
-    exit
+    echo "Sample script for extracting instructions" >&2
+    echo "autogenerated eBPF lskel headers" >&2
+    echo "" >&2
+    echo "USAGE: header_file" >&2
+    exit 1
 }
 
 ARGC=$#
diff --git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
index 6550a86b89917..abc435e2bcd4e 100755
--- a/scripts/hornet/extract-skel.sh
+++ b/scripts/hornet/extract-skel.sh
@@ -8,11 +8,11 @@
 # License as published by the Free Software Foundation.
 
 function usage() {
-    echo "Sample script for extracting instructions and map data out of"
-    echo "autogenerated eBPF lskel headers"
-    echo ""
-    echo "USAGE: header_file field"
-    exit
+    echo "Sample script for extracting instructions and map data out of" >&2
+    echo "autogenerated eBPF lskel headers" >&2
+    echo "" >&2
+    echo "USAGE: header_file field" >&2
+    exit 1
 }
 
 ARGC=$#
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
index 7eaabe3bab9aa..ad2b65761c282 100755
--- a/scripts/hornet/write-sig.sh
+++ b/scripts/hornet/write-sig.sh
@@ -8,11 +8,11 @@
 # License as published by the Free Software Foundation.
 
 function usage() {
-    echo "Sample for rewriting an autogenerated eBPF lskel headers"
-    echo "with a new signature"
-    echo ""
-    echo "USAGE: header_file sig"
-    exit
+    echo "Sample for rewriting an autogenerated eBPF lskel headers" >&2
+    echo "with a new signature" >&2
+    echo "" >&2
+    echo "USAGE: header_file sig" >&2
+    exit 1
 }
 
 ARGC=$#
-- 
2.53.0


^ permalink raw reply related

* [PATCH 08/11] hornet: gen_sig: fix missing command line switches
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

D was missing from the getopt list. Additionally, we were missing the
help option handler.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/gen_sig.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index fb9ae1934206a..19ae9af006853 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -295,7 +295,7 @@ int main(int argc, char **argv)
 	int i;
 	int opt;
 
-	const char *short_opts = "C:K:P:O:A:Sh";
+	const char *short_opts = "C:K:P:O:D:A:Sh";
 
 	static const struct option long_opts[] = {
 		{"cert", required_argument, 0, 'C'},
@@ -332,6 +332,9 @@ int main(int argc, char **argv)
 			}
 			hashes[hash_count++].file = optarg;
 			break;
+		case 'h':
+			usage(argv[0]);
+			return EXIT_SUCCESS;
 		default:
 			usage(argv[0]);
 			return EXIT_FAILURE;
-- 
2.53.0


^ permalink raw reply related

* [PATCH 07/11] hornet: gen_sig: check for bad allocations
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

There were a few sites where gen_sig failed to check for bad return
values after allocations. Error out appropriately as needed.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/gen_sig.c | 40 ++++++++++++++++++++++++++++++++++------
 1 file changed, 34 insertions(+), 6 deletions(-)

diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index 647bc3a257dd0..fb9ae1934206a 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -248,13 +248,25 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
 	return rc;
 }
 
-static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
+static int add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
 {
-	HORNET_MAP *map = NULL;
+	HORNET_MAP *map;
 
 	map = HORNET_MAP_new();
-	ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
-	sk_HORNET_MAP_push(set->maps, map);
+	if (!map)
+		return -1;
+
+	if (ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len) != 1) {
+		HORNET_MAP_free(map);
+		return -1;
+	}
+
+	if (sk_HORNET_MAP_push(set->maps, map) <= 0) {
+		HORNET_MAP_free(map);
+		return -1;
+	}
+
+	return 0;
 }
 
 int main(int argc, char **argv)
@@ -353,13 +365,18 @@ int main(int argc, char **argv)
 	ERR(!si, "add signer failed");
 
 	set = MAP_SET_new();
+	ERR(!set, "alloc MAP_SET failed");
 	set->maps = sk_HORNET_MAP_new_null();
+	ERR(!set->maps, "alloc HORNET_MAP stack failed");
 
 	for (i = 0; i < hash_count; i++) {
 		if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) {
 			DIE("failed to hash input");
 		}
-		add_hash(set, hash_buffer, hash_len);
+		if (add_hash(set, hash_buffer, hash_len) != 0) {
+			ERR_print_errors_fp(stderr);
+			DIE("failed to add hash to map set");
+		}
 	}
 
 	oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
@@ -380,7 +397,18 @@ int main(int argc, char **argv)
 	b_out = bio_open_wr(out_path);
 	ERR(!b_out, "opening output path failed");
 
-	i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+	err = i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+	ERR(!err, "writing CMS signature to %s failed", out_path);
+
+	/*
+	 * File BIOs wrap stdio, which buffers writes; small payloads will
+	 * report success from BIO_write even when the underlying file is
+	 * full or otherwise un-writable. Force a flush and check it before
+	 * the BIO is freed, otherwise gen_sig could exit successfully with
+	 * a truncated or empty signature file (e.g. ENOSPC on /dev/full).
+	 */
+	err = BIO_flush(b_out);
+	ERR(err <= 0, "flushing %s failed", out_path);
 
 	BIO_free(data_in);
 	BIO_free(b_out);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 06/11] hornet: gen_sig: fix error string allocations
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

The sha256 function was allocating/freeing it's own error strings,
which could case further errors to only return their error number.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/gen_sig.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index 4e8caad22f381..647bc3a257dd0 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -200,8 +200,6 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
 		return -2;
 	}
 
-	ERR_load_crypto_strings();
-
 	rc = -3;
 	ctx = EVP_MD_CTX_new();
 	if (!ctx) {
@@ -247,7 +245,6 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
 done:
 	EVP_MD_CTX_free(ctx);
 	fclose(f);
-	ERR_free_strings();
 	return rc;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 05/11] hornet: gen_sig: fix off-by-one check for used maps
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

A logic bug limited the maximum number of used maps to
MAX_USED_MAPS-1.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 scripts/hornet/gen_sig.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index b4f983ab24bcd..4e8caad22f381 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -317,11 +317,11 @@ int main(int argc, char **argv)
 			data_path = optarg;
 			break;
 		case 'A':
-			hashes[hash_count].file = optarg;
-			if (++hash_count >= MAX_HASHES) {
+			if (hash_count >= MAX_HASHES) {
 				usage(argv[0]);
 				return EXIT_FAILURE;
 			}
+			hashes[hash_count++].file = optarg;
 			break;
 		default:
 			usage(argv[0]);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 04/11] selftests: hornet: handle cross compilation and test skipping
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

There were a few spots in the hornet selftest makefile where some host
resources were assumed to be used. Additionally add proper skip
detection for scenarios where the autogenerated signing keys don't
exist.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 tools/testing/selftests/hornet/Makefile | 114 ++++++++++++++++++------
 1 file changed, 89 insertions(+), 25 deletions(-)

diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
index 316364f95f28c..460adab35e238 100644
--- a/tools/testing/selftests/hornet/Makefile
+++ b/tools/testing/selftests/hornet/Makefile
@@ -5,59 +5,123 @@ include ../../../scripts/Makefile.include
 
 CLANG ?= clang
 CFLAGS := -g -O2 -Wall
+TOOLSDIR := $(abspath ../../..)
 BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool
 SCRIPTSDIR := $(abspath ../../../../scripts/hornet)
-TOOLSDIR := $(abspath ../../..)
 LIBDIR := $(TOOLSDIR)/lib
 BPFDIR := $(LIBDIR)/bpf
 TOOLSINCDIR := $(TOOLSDIR)/include
 APIDIR := $(TOOLSINCDIR)/uapi
 CERTDIR := $(abspath ../../../../certs)
-PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+HOSTPKG_CONFIG ?= pkg-config
+
+SIGNING_KEY  := $(CERTDIR)/signing_key.pem
+SIGNING_CERT := $(CERTDIR)/signing_key.x509
+
+VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux)				\
+		     $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)	\
+		     ../../../../vmlinux				\
+		     /sys/kernel/btf/vmlinux				\
+		     /boot/vmlinux-$(shell uname -r)
+VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
+
+# The hornet selftest needs the kernel module signing key/cert (generated when
+# the kernel is built with CONFIG_MODULE_SIG=y), a bpftool binary, and a
+# vmlinux with BTF for trivial.bpf.o. If any of those are missing (cross-build
+# without artifacts, container CI, CONFIG_MODULE_SIG disabled, etc.) skip the
+# targets rather than failing the global selftests build.
+hornet_skip_reason :=
+ifeq ($(wildcard $(SIGNING_KEY)),)
+hornet_skip_reason := module signing key not found at $(SIGNING_KEY) (build the kernel with CONFIG_MODULE_SIG=y first)
+else ifeq ($(wildcard $(SIGNING_CERT)),)
+hornet_skip_reason := module signing cert not found at $(SIGNING_CERT)
+else ifeq ($(wildcard $(BPFTOOL)),)
+hornet_skip_reason := bpftool not found at $(BPFTOOL) (build it under tools/bpf/bpftool first)
+else ifeq ($(VMLINUX_BTF),)
+hornet_skip_reason := no vmlinux with BTF found; tried $(VMLINUX_BTF_PATHS) (build the kernel with CONFIG_DEBUG_INFO_BTF=y or set VMLINUX_BTF=)
+endif
+
+ifneq ($(hornet_skip_reason),)
+$(warning Skipping hornet selftests: $(hornet_skip_reason))
+TEST_GEN_PROGS :=
+TEST_GEN_FILES :=
+
+include ../lib.mk
+
+else
 
 TEST_GEN_PROGS := loader
 TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h
-$(TEST_GEN_PROGS): LDLIBS += -lbpf
-$(TEST_GEN_PROGS): $(TEST_GEN_FILES)
 
 include ../lib.mk
 
-BPF_CFLAGS := -target bpf \
-	-D__TARGET_ARCH_$(ARCH) \
-	-I/usr/include/$(shell uname -m)-linux-gnu \
+define get_sys_includes
+$(shell $(1) $(2) -v -E - </dev/null 2>&1 \
+	| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep '__loongarch_grlen ' | awk '{printf("-D__BITS_PER_LONG=%d", $$3)}') \
+$(shell $(1) $(2) -dM -E - </dev/null | grep -E 'MIPS(EL|EB)|_MIPS_SZ(PTR|LONG) |_MIPS_SIM |_ABI(O32|N32|64) ' | awk '{printf("-D%s=%s ", $$2, $$3)}')
+endef
+
+ifneq ($(CROSS_COMPILE),)
+CLANG_TARGET_ARCH = --target=$(notdir $(CROSS_COMPILE:%-=%))
+endif
+CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH))
+
+IS_LITTLE_ENDIAN := $(shell $(CC) -dM -E - </dev/null | \
+			grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
+BPF_TARGET_ENDIAN := $(if $(IS_LITTLE_ENDIAN),--target=bpfel,--target=bpfeb)
+
+BPF_CFLAGS := $(BPF_TARGET_ENDIAN) \
+	-D__TARGET_ARCH_$(SRCARCH) \
+	$(CLANG_SYS_INCLUDES) \
 	$(KHDR_INCLUDES)
 
-vmlinux.h:
-	$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL)
+	$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
 
-trivial.bpf.o: trivial.bpf.c vmlinux.h
-	$(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@
+$(OUTPUT)/trivial.bpf.o: trivial.bpf.c $(OUTPUT)/vmlinux.h
+	$(CLANG) $(CFLAGS) $(BPF_CFLAGS) -I$(OUTPUT) -c $< -o $@
 
-loader.h: trivial.bpf.o
-	$(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \
+$(OUTPUT)/loader.h: $(OUTPUT)/trivial.bpf.o
+	$(BPFTOOL) gen skeleton -S -k $(SIGNING_KEY) -i $(SIGNING_CERT) \
 		-L $< name trivial > $@
 
-insn.bin: loader.h
+$(OUTPUT)/insn.bin: $(OUTPUT)/loader.h
 	$(SCRIPTSDIR)/extract-insn.sh $< > $@
 
-map.bin: loader.h
+$(OUTPUT)/map.bin: $(OUTPUT)/loader.h
 	$(SCRIPTSDIR)/extract-map.sh $< > $@
 
 $(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
 	$(call msg,GEN_SIG,,$@)
-	$(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \
+	$(Q)$(HOSTCC) $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) \
 		  $< -o $@ \
-		  $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+		  $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+
+$(OUTPUT)/sig.bin: $(OUTPUT)/insn.bin $(OUTPUT)/map.bin $(OUTPUT)/gen_sig
+	$(OUTPUT)/gen_sig --key $(SIGNING_KEY) --cert $(SIGNING_CERT) \
+		--data $(OUTPUT)/insn.bin --add $(OUTPUT)/map.bin --out $@
+
+$(OUTPUT)/signed_loader.h: $(OUTPUT)/sig.bin $(OUTPUT)/loader.h
+	$(SCRIPTSDIR)/write-sig.sh $(OUTPUT)/loader.h $(OUTPUT)/sig.bin > $@
+
+BPFOBJ := $(OUTPUT)/libbpf/libbpf.a
+
+$(OUTPUT)/libbpf:
+	$(Q)mkdir -p $@
 
-sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
-	$(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \
-		--data insn.bin --add map.bin --out sig.bin
+$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
+	   $(APIDIR)/linux/bpf.h | $(OUTPUT)/libbpf
+	$(Q)$(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/libbpf/ \
+		    DESTDIR=$(OUTPUT) prefix= \
+		    $(BPFOBJ) install_headers
 
-signed_loader.h: sig.bin
-	$(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
+$(OUTPUT)/loader: loader.c $(OUTPUT)/signed_loader.h $(BPFOBJ)
+	$(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) -I$(OUTPUT) \
+		$< $(BPFOBJ) -o $@ -lelf -lz
 
-loader: loader.c signed_loader.h
-	$(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf
 
+EXTRA_CLEAN = $(OUTPUT)/gen_sig $(OUTPUT)/libbpf
 
-EXTRA_CLEAN = $(OUTPUT)/gen_sig
+endif
-- 
2.53.0


^ permalink raw reply related

* [PATCH 03/11] hornet: fix off-by-one bug in max used maps check
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

Sashiko correctly reported an off-by-one logic error checking against
the maximum number of used maps.  Removing the index constraint allows
us to simplify the check logic.

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 security/hornet/hornet_lsm.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
index 35d9522d6bc72..eeb422db1092d 100644
--- a/security/hornet/hornet_lsm.c
+++ b/security/hornet/hornet_lsm.c
@@ -49,8 +49,7 @@ int hornet_next_map(void *context, size_t hdrlen,
 {
 	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
 
-	if (++ctx->security->signed_hash_count >= MAX_USED_MAPS)
-		return -EINVAL;
+	ctx->security->signed_hash_count++;
 	return 0;
 }
 
@@ -63,6 +62,8 @@ int hornet_map_hash(void *context, size_t hdrlen,
 
 	if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
 		return -EINVAL;
+	if (ctx->security->signed_hash_count >= MAX_USED_MAPS)
+		return -EINVAL;
 
 	memcpy(&ctx->security->signed_hashes[ctx->security->signed_hash_count * SHA256_DIGEST_SIZE],
 	       value, vlen);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 02/11] hornet: invert map set check logic
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

In a multi-map hash verification scenario, a logic bug may have
allowed an attacker to provide duplicate maps to satisfy the hash
check count. Instead, invert the logic to verify each map discretely

Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 security/hornet/hornet_lsm.c | 24 +++++++++---------------
 1 file changed, 9 insertions(+), 15 deletions(-)

diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
index 516038413f321..35d9522d6bc72 100644
--- a/security/hornet/hornet_lsm.c
+++ b/security/hornet/hornet_lsm.c
@@ -191,7 +191,6 @@ static int hornet_check_prog_maps(struct bpf_prog *prog)
 	struct bpf_map *map;
 	int i, j;
 	bool found;
-	int covered_count = 0;
 
 	security = hornet_bpf_prog_security(prog);
 
@@ -200,18 +199,18 @@ static int hornet_check_prog_maps(struct bpf_prog *prog)
 
 	mutex_lock(&prog->aux->used_maps_mutex);
 
-	/* Verify every used_map has a matching signed hash */
-	for (j = 0; j < prog->aux->used_map_cnt; j++) {
-		map = prog->aux->used_maps[j];
+	/* Verify every signed map exists in used_maps */
+	for (i = 0; i < security->signed_hash_count; i++) {
+		found = false;
+		for (j = 0; j < prog->aux->used_map_cnt; j++) {
+			map = prog->aux->used_maps[j];
 
-		if (!READ_ONCE(map->frozen) || !map->ops->map_get_hash)
-			continue;
+			if (!READ_ONCE(map->frozen) || !map->ops->map_get_hash)
+				continue;
 
-		if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
-			continue;
+			if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
+				continue;
 
-		found = false;
-		for (i = 0; i < security->signed_hash_count; i++) {
 			if (memcmp(hash,
 				   &security->signed_hashes[i * SHA256_DIGEST_SIZE],
 				   SHA256_DIGEST_SIZE) == 0) {
@@ -223,15 +222,10 @@ static int hornet_check_prog_maps(struct bpf_prog *prog)
 			mutex_unlock(&prog->aux->used_maps_mutex);
 			return -EPERM;
 		}
-		covered_count++;
 	}
 
 	mutex_unlock(&prog->aux->used_maps_mutex);
 
-	/* Ensure all signed hashes were accounted for */
-	if (covered_count != security->signed_hash_count)
-		return -EPERM;
-
 	return 0;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 01/11] hornet: fix TOCTOU in signed program verification
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module
In-Reply-To: <20260528030915.2654994-1-bboscaccy@linux.microsoft.com>

The signature verification path was vulnerable to a time-of-check vs
time-of-use race at both the program load and program run hook sites:
between the moment a map's contents were hashed for signature
verification and the moment the program run hook re-verified them, an
attacker with sufficient privileges could swap or mutate the map
contents.

Close the race by snapshotting the map hashes during program load,
attaching them to the program, and re-verifying them from the
security_bpf_prog hook against prog->aux->used_maps. Because used_maps
is the same map set the verifier and runtime resolve against, there is
no longer a window in which the verified set and the executed set can
diverge.

Since we are no longer targeting the fd_array passed in, drop the map
index data entirely and check for whether or not the set of requested
map hashes is a subset of prog->aux->used_maps.

Reported-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
 Documentation/admin-guide/LSM/Hornet.rst |  39 +++-----
 scripts/hornet/gen_sig.c                 |  17 +---
 security/hornet/hornet.asn1              |   1 -
 security/hornet/hornet_lsm.c             | 121 +++--------------------
 tools/testing/selftests/hornet/Makefile  |   2 +-
 5 files changed, 35 insertions(+), 145 deletions(-)

diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
index 0ade4c17374c6..a369bc11408f4 100644
--- a/Documentation/admin-guide/LSM/Hornet.rst
+++ b/Documentation/admin-guide/LSM/Hornet.rst
@@ -86,15 +86,14 @@ Hornet protects against the following threats:
 
 - **Tampering with map data**: When map hashes are included in the
   signature, Hornet verifies that frozen BPF maps match their expected
-  SHA-256 hashes at load time. Maps are also re-verified before program
-  execution via ``BPF_PROG_RUN``.
+  SHA-256 hashes at load time after the program is publically exposed.
 
 Hornet does **not** protect against:
 
 - Compromise of the signing key itself.
 - Attacks that occur after a program has been loaded and verified.
 - Programs loaded by the kernel itself (kernel-internal loads bypass
-  the ``BPF_PROG_RUN`` map check).
+  the map check).
 
 Known Limitations
 =================
@@ -117,6 +116,10 @@ Known Limitations
   data. It does not guarantee positional binding of maps to specific
   fd_array slots.
 
+- Map hash verification does not enforce any ordering. It simply asserts
+  that the set of map hashes requested to be verified exist in the used
+  array.
+
 - BPF_MAP_TYPE_PROG_ARRAY maps must be frozen for Hornet to verify
   them. Unfrozen prog array maps are not covered by verification.
 
@@ -159,24 +162,19 @@ The following describes what happens when a userspace program calls
 5. Hornet extracts the authenticated attribute identified by
    ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``)
    from the PKCS#7 message. This attribute contains an ASN.1-encoded set
-   of map index/hash pairs.
+   of map hash hashes
 
-6. For each map hash entry, Hornet retrieves the corresponding BPF map
-   via its file descriptor, confirms it is frozen, computes its SHA-256
-   hash, and compares it against the signed hash.
+6. For each map hash entry, Hornet retrieves stores the target map hash in
+   the program's LSM blob.
 
 7. The resulting integrity verdict is passed to the
    ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can
    enforce policy.
 
-Runtime Map Verification
-------------------------
-
-When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet
-re-verifies the hashes of all maps associated with the program. This
-ensures that map contents have not been modified between program load
-and execution. If any map hash no longer matches, the ``BPF_PROG_RUN``
-command is denied.
+8. After the verifier processes the program, once it's ready to be published,
+   Hornet intercepts the ``bpf_prog`` hook, and verifies that the set of
+   required hashes exist in the programs used maps. If the map hashes are
+   unable to be found, the command is denied.
 
 Userspace Interface
 -------------------
@@ -199,14 +197,10 @@ the following ASN.1 schema::
   HornetData ::= SET OF Map
 
   Map ::= SEQUENCE {
-      index   INTEGER,
       sha     OCTET STRING
   }
 
-Each ``Map`` entry contains the index of the map in the program's
-``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field
-indicates that the map at that index should be skipped during
-verification.
+Each ``Map`` entry contains an expected SHA-256 hash.
 
 Tooling
 =======
@@ -229,7 +223,7 @@ Usage::
           --key <signer.key> \
           [--pass <passphrase>] \
           --out <signature.p7b> \
-          [--add <mapfile.bin>:<index> ...]
+          [--add <mapfile.bin> ...]
 
 ``--data``
   Path to the binary file containing eBPF program instructions to sign.
@@ -248,8 +242,7 @@ Usage::
 
 ``--add``
   Attach a map hash as a signed attribute. The argument is a path to a
-  binary map file followed by a colon and the map's index in the
-  ``fd_array``. This option may be specified multiple times.
+  binary map file. This option may be specified multiple times.
 
 extract-skel.sh
 ---------------
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
index 8dd9ed66346a2..b4f983ab24bcd 100644
--- a/scripts/hornet/gen_sig.c
+++ b/scripts/hornet/gen_sig.c
@@ -55,7 +55,6 @@
 
 struct hash_spec {
 	char *file;
-	int index;
 };
 
 typedef struct {
@@ -66,7 +65,6 @@ typedef struct {
 
 DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
 ASN1_SEQUENCE(HORNET_MAP) = {
-	ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
 	ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
 } ASN1_SEQUENCE_END(HORNET_MAP);
 
@@ -253,12 +251,11 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
 	return rc;
 }
 
-static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
+static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
 {
 	HORNET_MAP *map = NULL;
 
 	map = HORNET_MAP_new();
-	ASN1_INTEGER_set(map->index, index);
 	ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
 	sk_HORNET_MAP_push(set->maps, map);
 }
@@ -320,14 +317,8 @@ int main(int argc, char **argv)
 			data_path = optarg;
 			break;
 		case 'A':
-			if (strchr(optarg, ':')) {
-				hashes[hash_count].file = strsep(&optarg, ":");
-				hashes[hash_count].index = atoi(optarg);
-				if (++hash_count >= MAX_HASHES) {
-					usage(argv[0]);
-					return EXIT_FAILURE;
-				}
-			} else {
+			hashes[hash_count].file = optarg;
+			if (++hash_count >= MAX_HASHES) {
 				usage(argv[0]);
 				return EXIT_FAILURE;
 			}
@@ -371,7 +362,7 @@ int main(int argc, char **argv)
 		if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) {
 			DIE("failed to hash input");
 		}
-		add_hash(set, hash_buffer, hash_len, hashes[i].index);
+		add_hash(set, hash_buffer, hash_len);
 	}
 
 	oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
index e60abf451ae23..3cf50379f5e7c 100644
--- a/security/hornet/hornet.asn1
+++ b/security/hornet/hornet.asn1
@@ -7,6 +7,5 @@
 HornetData ::= SET OF Map
 
 Map ::= SEQUENCE {
-	index			INTEGER ({ hornet_map_index }),
 	sha			OCTET STRING ({ hornet_map_hash })
 } ({ hornet_next_map })
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
index a4d11fa5b0889..516038413f321 100644
--- a/security/hornet/hornet_lsm.c
+++ b/security/hornet/hornet_lsm.c
@@ -21,26 +21,18 @@
 
 #define MAX_USED_MAPS 64
 
-struct hornet_maps {
-	bpfptr_t fd_array;
-};
-
 /* The only hashing algorithm available is SHA256 due to it be hardcoded
  * in the bpf subsystem.
  */
-
-struct hornet_parse_context {
-	int indexes[MAX_USED_MAPS];
-	bool skips[MAX_USED_MAPS];
-	unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
-	int hash_count;
-};
-
 struct hornet_prog_security_struct {
 	int signed_hash_count;
 	unsigned char signed_hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
 };
 
+struct hornet_parse_context {
+	struct hornet_prog_security_struct *security;
+};
+
 struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
 	.lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
 };
@@ -51,79 +43,17 @@ hornet_bpf_prog_security(struct bpf_prog *prog)
 	return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
 }
 
-static int hornet_verify_hashes(struct hornet_maps *maps,
-				struct hornet_parse_context *ctx,
-				struct bpf_prog *prog)
-{
-	int map_fd;
-	u32 i;
-	struct bpf_map *map;
-	int err = 0;
-	unsigned char hash[SHA256_DIGEST_SIZE];
-	struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
-
-	for (i = 0; i < ctx->hash_count; i++) {
-		if (ctx->skips[i])
-			continue;
-
-		err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
-					      ctx->indexes[i] * sizeof(map_fd),
-					      sizeof(map_fd));
-		if (err != 0)
-			return LSM_INT_VERDICT_FAULT;
-
-		CLASS(fd, f)(map_fd);
-		if (fd_empty(f))
-			return LSM_INT_VERDICT_FAULT;
-		if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
-			return LSM_INT_VERDICT_FAULT;
-
-		map = fd_file(f)->private_data;
-		if (!READ_ONCE(map->frozen))
-			return LSM_INT_VERDICT_FAULT;
-
-		if (!map->ops->map_get_hash)
-			return LSM_INT_VERDICT_FAULT;
-
-		if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
-			return LSM_INT_VERDICT_FAULT;
-
-		err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
-			      SHA256_DIGEST_SIZE);
-		if (err)
-			return LSM_INT_VERDICT_UNEXPECTED;
-
-		memcpy(&security->signed_hashes[security->signed_hash_count * SHA256_DIGEST_SIZE],
-		       &ctx->hashes[i * SHA256_DIGEST_SIZE], SHA256_DIGEST_SIZE);
-		security->signed_hash_count++;
-	}
-	return LSM_INT_VERDICT_OK;
-}
-
 int hornet_next_map(void *context, size_t hdrlen,
 		     unsigned char tag,
 		     const void *value, size_t vlen)
 {
 	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
 
-	if (++ctx->hash_count >= MAX_USED_MAPS)
+	if (++ctx->security->signed_hash_count >= MAX_USED_MAPS)
 		return -EINVAL;
 	return 0;
 }
 
-int hornet_map_index(void *context, size_t hdrlen,
-		     unsigned char tag,
-		     const void *value, size_t vlen)
-{
-	struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
-
-	if (vlen != 1)
-		return -EINVAL;
-
-	ctx->indexes[ctx->hash_count] = *(u8 *)value;
-	return 0;
-}
-
 int hornet_map_hash(void *context, size_t hdrlen,
 		    unsigned char tag,
 		    const void *value, size_t vlen)
@@ -134,11 +64,8 @@ int hornet_map_hash(void *context, size_t hdrlen,
 	if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
 		return -EINVAL;
 
-	if (vlen) {
-		ctx->skips[ctx->hash_count] = false;
-		memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
-	} else
-		ctx->skips[ctx->hash_count] = true;
+	memcpy(&ctx->security->signed_hashes[ctx->security->signed_hash_count * SHA256_DIGEST_SIZE],
+	       value, vlen);
 
 	return 0;
 }
@@ -147,7 +74,6 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
 				struct bpf_token *token, bool is_kernel,
 				enum lsm_integrity_verdict *verdict)
 {
-	struct hornet_maps maps = {0};
 	bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
 	struct pkcs7_message *msg;
 	struct hornet_parse_context *ctx;
@@ -172,7 +98,8 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
 	if (!ctx)
 		return -ENOMEM;
 
-	maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
+	ctx->security = hornet_bpf_prog_security(prog);
+
 	sig = kzalloc(attr->signature_size, GFP_KERNEL);
 	if (!sig) {
 		err = -ENOMEM;
@@ -225,7 +152,7 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
 		goto cleanup_msg;
 	}
 
-	*verdict = hornet_verify_hashes(&maps, ctx, prog);
+	*verdict = LSM_INT_VERDICT_OK;
 	err = 0;
 
 cleanup_msg:
@@ -257,10 +184,8 @@ static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr
 						     &hornet_lsmid, verdict);
 }
 
-static int hornet_check_prog_maps(u32 ufd)
+static int hornet_check_prog_maps(struct bpf_prog *prog)
 {
-	CLASS(fd, f)(ufd);
-	struct bpf_prog *prog;
 	struct hornet_prog_security_struct *security;
 	unsigned char hash[SHA256_DIGEST_SIZE];
 	struct bpf_map *map;
@@ -268,12 +193,6 @@ static int hornet_check_prog_maps(u32 ufd)
 	bool found;
 	int covered_count = 0;
 
-	if (fd_empty(f))
-		return -EBADF;
-	if (fd_file(f)->f_op != &bpf_prog_fops)
-		return -EINVAL;
-
-	prog = fd_file(f)->private_data;
 	security = hornet_bpf_prog_security(prog);
 
 	if (!security->signed_hash_count)
@@ -316,26 +235,14 @@ static int hornet_check_prog_maps(u32 ufd)
 	return 0;
 }
 
-static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
+static int hornet_bpf_prog(struct bpf_prog *prog)
 {
-	/* in horent_bpf(), anything that had originated from kernel space we assume
-	 * has already been checked, in some form or another, so we don't bother
-	 * checking the intergity of any maps. In hornet_bpf_prog_load_integrity(),
-	 * hornet doesn't make any opinion on that and delegates that to the downstream
-	 * policy enforcement.
-	 */
-
-	if (cmd != BPF_PROG_RUN)
-		return 0;
-	if (kernel)
-		return 0;
-
-	return hornet_check_prog_maps(attr->test.prog_fd);
+	return hornet_check_prog_maps(prog);
 }
 
 static struct security_hook_list hornet_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
-	LSM_HOOK_INIT(bpf, hornet_bpf),
+	LSM_HOOK_INIT(bpf_prog, hornet_bpf_prog),
 };
 
 static int __init hornet_init(void)
diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
index 432bce59f54e7..316364f95f28c 100644
--- a/tools/testing/selftests/hornet/Makefile
+++ b/tools/testing/selftests/hornet/Makefile
@@ -51,7 +51,7 @@ $(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
 
 sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
 	$(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \
-		--data insn.bin --add map.bin:0 --out sig.bin
+		--data insn.bin --add map.bin --out sig.bin
 
 signed_loader.h: sig.bin
 	$(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
-- 
2.53.0


^ permalink raw reply related

* [PATCH 00/11] hornet: security, tooling and selftest fixes
From: Blaise Boscaccy @ 2026-05-28  3:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
	Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
	Blaise Boscaccy, linux-security-module

Patch 1 closes a TOCTOU race in signature verification. Map
contents were hashed at the program-load hook and re-hashed at
the program-run hook, leaving a window in which a sufficiently
privileged attacker could mutate a map between the two checks
and run a program whose maps no longer matched what was signed.
The fix records the verified hashes on the prog at load time
and, in security_bpf_prog, checks them against
prog->aux->used_maps — the same map set the verifier and
runtime resolve against — so the verified and executed sets
cannot diverge. The per-map index in the signature format is no
longer needed and is dropped; the check becomes a subset test.
Reported by Eric Biggers.

Patches 2-3 fix two counting bugs in the same area: duplicate maps
could satisfy the required hash count, and an off-by-one capped
accepted maps at MAX_USED_MAPS.

Patches 4-11 are in response to sashiko feedback found here:
https://sashiko.dev/#/patchset/20260507191416.2984054-1-bboscaccy%40linux.microsoft.com

They provide some correctness fixes in the hornet tooling along with
making the selftest behave under cross-compilation and skip cleanly
when signing keys / bpftool / vmlinux BTF are unavailable, instead of
breaking the global selftest build.


Blaise Boscaccy (11):
  hornet: fix TOCTOU in signed program verification
  hornet: invert map set check logic
  hornet: fix off-by-one bug in max used maps check
  selftests: hornet: handle cross compilation and test skipping
  hornet: gen_sig: fix off-by-one check for used maps
  hornet: gen_sig: fix error string allocations
  hornet: gen_sig: check for bad allocations
  hornet: gen_sig: fix missing command line switches
  hornet: scripts: set a non-zero error code for usage
  hornet: scripts: harden scripts to handle trailing whitespace
  hornet: scripts: Improve argument handling and error messages

 Documentation/admin-guide/LSM/Hornet.rst |  39 +++---
 scripts/hornet/extract-insn.sh           |  24 ++--
 scripts/hornet/extract-map.sh            |  25 ++--
 scripts/hornet/extract-skel.sh           |  35 ++++--
 scripts/hornet/gen_sig.c                 |  61 ++++++----
 scripts/hornet/write-sig.sh              |  10 +-
 security/hornet/hornet.asn1              |   1 -
 security/hornet/hornet_lsm.c             | 148 ++++-------------------
 tools/testing/selftests/hornet/Makefile  | 114 +++++++++++++----
 9 files changed, 235 insertions(+), 222 deletions(-)

-- 
2.53.0


^ permalink raw reply

* [PATCH net v2] netlabel: validate unlabeled mask attribute length
From: Chenguang Zhao @ 2026-05-28  1:59 UTC (permalink / raw)
  To: Paul Moore, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Chenguang Zhao, Simon Horman, netdev, linux-security-module

netlbl_unlabel_addrinfo_get() checked the address length
but allowed shorter mask attributes to pass through to
fixed-size address reads.

netlbl_unlabel_addrinfo_get() only rejected a mask
length mismatch when the address attribute length
was also invalid.  A crafted Generic Netlink request
could therefore provide a valid IPv4/IPv6 address
attribute with a shorter mask attribute.

NLA_BINARY policy lengths are maximum lengths,
not exact lengths, so the short mask can pass
policy validation.  The mask is later read as
a full struct in_addr or struct in6_addr.
Require both address and mask attributes to
have the exact expected size.

Fixes: 8cc44579d1bd ("NetLabel: Introduce static network labels for unlabeled connections")
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
v2:
 - Adjust commit message 
 - Add Fixes and 'net' subject prefix.
v1:
 https://lore.kernel.org/all/20260522054521.1169755-1-zhaochenguang@kylinos.cn/
---
 net/netlabel/netlabel_unlabeled.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/net/netlabel/netlabel_unlabeled.c b/net/netlabel/netlabel_unlabeled.c
index ca7a9e2a3de7..c1b7e0061886 100644
--- a/net/netlabel/netlabel_unlabeled.c
+++ b/net/netlabel/netlabel_unlabeled.c
@@ -762,8 +762,9 @@ static int netlbl_unlabel_addrinfo_get(struct genl_info *info,
 	if (info->attrs[NLBL_UNLABEL_A_IPV4ADDR] &&
 	    info->attrs[NLBL_UNLABEL_A_IPV4MASK]) {
 		addr_len = nla_len(info->attrs[NLBL_UNLABEL_A_IPV4ADDR]);
-		if (addr_len != sizeof(struct in_addr) &&
-		    addr_len != nla_len(info->attrs[NLBL_UNLABEL_A_IPV4MASK]))
+		if (addr_len != sizeof(struct in_addr) ||
+		    nla_len(info->attrs[NLBL_UNLABEL_A_IPV4MASK]) !=
+		    sizeof(struct in_addr))
 			return -EINVAL;
 		*len = addr_len;
 		*addr = nla_data(info->attrs[NLBL_UNLABEL_A_IPV4ADDR]);
@@ -771,8 +772,9 @@ static int netlbl_unlabel_addrinfo_get(struct genl_info *info,
 		return 0;
 	} else if (info->attrs[NLBL_UNLABEL_A_IPV6ADDR]) {
 		addr_len = nla_len(info->attrs[NLBL_UNLABEL_A_IPV6ADDR]);
-		if (addr_len != sizeof(struct in6_addr) &&
-		    addr_len != nla_len(info->attrs[NLBL_UNLABEL_A_IPV6MASK]))
+		if (addr_len != sizeof(struct in6_addr) ||
+		    nla_len(info->attrs[NLBL_UNLABEL_A_IPV6MASK]) !=
+		    sizeof(struct in6_addr))
 			return -EINVAL;
 		*len = addr_len;
 		*addr = nla_data(info->attrs[NLBL_UNLABEL_A_IPV6ADDR]);
-- 
2.25.1


^ permalink raw reply related

* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Aaron Tomlin @ 2026-05-28  1:19 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
	vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
	mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
	steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
	linux-security-module, selinux, linux-kernel
In-Reply-To: <20260527195858.GC3493090@noisy.programming.kicks-ass.net>

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

On Wed, May 27, 2026 at 09:58:58PM +0200, Peter Zijlstra wrote:
> On Wed, May 27, 2026 at 01:41:52PM -0400, Aaron Tomlin wrote:
> 
> > > > The actual use case here is multi-tenant workload isolation and visibility.
> > > > Passing the evaluated cpumask to the BPF LSM allows operators to write a
> > > > simple eBPF program to detect spatial boundary overlaps (e.g., logging an
> > > > event if a requested mask intersects with platform-reserved cores).
> 
> Why isn't cgroups good enough to enforce this? If you create a cgroup
> hierarchy per tenant, and constrain them using the cpuset controller,
> they should not be able to escape, rendering this event impossible.

Hi Peter,

You raise a very fair point. The cpuset cgroup controller is indeed the
kernel's primary vehicle for spatial enforcement, and under normal
circumstances, it successfully prevents a tenant from escaping their
designated cores.

The cpuset controller does govern resource limits, but does not audit
intent. When __sched_setaffinity() is invoked, the kernel compares the
requested in_mask against the task's allowed cpuset. If there is only a
partial intersection, the kernel silently truncates the requested mask to
fit the cpuset, without raising any alarm.

The BPF LSM hook, conversely, receives the raw, untruncated in_mask,
affording operators the visibility to detect, audit, and even reject these
violations of intent before the kernel silently sanitises the input.

This patch does not seek to replace the cpuset controller, but rather to
complement it by providing auditing capabilities.

> > We are not creating a bespoke BPF hook here; rather, we are rectifying a
> > historical blind spot within the API. The existing LSM hook is invoked
> > during sched_setaffinity(), yet it presently receives only the task_struct
> > pointer. Consequently, the security module is essentially asked, "Should
> > Process A be permitted to alter Process B's affinity?" without being
> > informed of the proposed affinity itself. Providing in_mask simply
> > furnishes the existing hook with the requisite payload to make an informed
> > decision.
> 
> It occurs to me that this same argument would require to also pass in
> the new sched_attr, no? That way the LSM can inspect the new policy
> before it becomes effective.

I agree, the underlying logic does indeed extend perfectly to sched_attr.

Presently, the LSM is equally oblivious as to whether a process is
requesting a benign transition to SCHED_BATCH, or attempting to escalate
its privileges by requesting a real-time policy such as SCHED_FIFO with
maximum priority. Just as with the CPU mask, providing the sched_attr
payload would rectify this parallel blind spot, allowing BPF policies to
inspect and mediate scheduling attributes before they become effective.

If you are amenable, I should be more than happy to expand the scope of the
forthcoming patch to include this. Alternatively, we could address the
sched_attr expansion in a separate, subsequent patch. Personally, I would
favour the latter approach, but please do let me know your preference.

I very much look forward to hearing Paul's thoughts on whether this aligns
with the broader LSM vision.

Thank you.


Kind regards,
-- 
Aaron Tomlin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH 3/3] apparmor: replace get_zeroed_page() with kzalloc()
From: Paul Moore @ 2026-05-27 23:42 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft), James Morris, John Johansen,
	Ondrej Mosnacek, Serge E. Hallyn, Stephen Smalley
  Cc: Mike Rapoport, apparmor, selinux, linux-kernel, linux-mm,
	linux-security-module
In-Reply-To: <20260520-security-v1-3-831bd8e21dd0@kernel.org>

On May 20, 2026 "Mike Rapoport (Microsoft)" <rppt@kernel.org> wrote:
> 
> multi_transaction_new() allocates memory with get_zeroed_page() and uses
> it as struct multi_transaction.
> 
> The usage of that structure does not require struct page access and it is
> better to allocate multi_transaction objects with kzalloc() that provides
> better scalability and more debugging possibilities.
> 
> Replace use of get_zeroed_page() with kzalloc().
> 
> Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>  security/apparmor/apparmorfs.c | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)

I'll leave this for John and/or Georgia to review and ultimately decide
to merge, but it looks okay to me.

Reviewed-by: Paul Moore <paul@paul-moore.com>

--
paul-moore.com

^ permalink raw reply

* Re: [PATCH 2/3] selinux: hooks: use __getname() to allocate path  buffer
From: Paul Moore @ 2026-05-27 23:42 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft), James Morris, John Johansen,
	Ondrej Mosnacek, Serge E. Hallyn, Stephen Smalley
  Cc: Mike Rapoport, apparmor, selinux, linux-kernel, linux-mm,
	linux-security-module
In-Reply-To: <20260520-security-v1-2-831bd8e21dd0@kernel.org>

On May 20, 2026 "Mike Rapoport (Microsoft)" <rppt@kernel.org> wrote:
> 
> selinux_genfs_get_sid() allocates memory for a path with __get_free_page()
> although there is a dedicated helper for allocation of file paths:
> __getname().
> 
> Replace __get_free_page() for allocation of a path buffer with __getname().
> 
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>  security/selinux/hooks.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)

Merged into selinux/dev, thanks.

--
paul-moore.com

^ permalink raw reply

* Re: [PATCH 1/3] selinux: use k[mz]alloc() to allocate temporary  buffers
From: Paul Moore @ 2026-05-27 23:42 UTC (permalink / raw)
  To: Mike Rapoport (Microsoft), James Morris, John Johansen,
	Ondrej Mosnacek, Serge E. Hallyn, Stephen Smalley
  Cc: Mike Rapoport, apparmor, selinux, linux-kernel, linux-mm,
	linux-security-module
In-Reply-To: <20260520-security-v1-1-831bd8e21dd0@kernel.org>

On May 20, 2026 "Mike Rapoport (Microsoft)" <rppt@kernel.org> wrote:
> 
> Several functions in selinuxfs.c allocate temporary buffers using
> __get_free_page() or get_zeroed_page().
> 
> These buffers are used either to store a string generated by snprintf() (in
> sel_make_bools()) or to copy data from user (sel_read_avc_hash_stats() and
> sel_read_sidtab_hash_stats()).
> 
> Such usage does not require struct page access and it is better to allocate
> these buffers with kzalloc()/kmalloc() that provide better scalability and
> more debugging possibilities.
> 
> Replace use of get_zeroed_page() with kzalloc() and usage of
> __get_free_page() with kmalloc().
> 
> Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>  security/selinux/selinuxfs.c | 12 ++++++------
>  1 file changed, 6 insertions(+), 6 deletions(-)

I suspect if we look closer we can probably also trim some of those
allocations to less then a page, but that can be work for another day.

Merged into selinux/dev, thanks Mike.

--
paul-moore.com

^ permalink raw reply

* Re: [PATCH v4 1/7] lsm: Add granular mount hooks to replace security_sb_mount
From: Song Liu @ 2026-05-27 21:08 UTC (permalink / raw)
  To: Christian Brauner, paul
  Cc: linux-security-module, linux-fsdevel, selinux, apparmor, jmorris,
	serge, viro, jack, john.johansen, stephen.smalley.work, omosnace,
	mic, gnoack, takedakn, penguin-kernel, herton, kernel-team
In-Reply-To: <20260527-landen-bahnfahren-eckpfeiler-c1e1e9cb73aa@brauner>

On Wed, May 27, 2026 at 5:17 AM Christian Brauner <brauner@kernel.org> wrote:
[...]
> > 1/7 adds new hooks:
> >   lsm: Add granular mount hooks to replace security_sb_mount
> > 2/7 through 6/7 migrate LSMs from old hooks to new 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
> > 7/7 removes old hooks:
> >   lsm: Remove security_sb_mount and security_move_mount
> >
> > Some ideas to change this:
>
> My thought had been:
>
> * Add the new hooks to security/.
> * add the individual lsm implementations.
> * Now replace the old hooks with the new hooks in fs/namespace.c
> * Delete the old hooks in security/
>
> IOW, why the migration step? It is a full replacement anyway.

I think having a migration like this doesn't really make
review more difficult. But I am OK refactoring the patches
as requested.

Paul, do you have a strong preference either way?

Thanks,
Song

^ permalink raw reply

* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Peter Zijlstra @ 2026-05-27 19:58 UTC (permalink / raw)
  To: Aaron Tomlin
  Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
	vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
	mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
	steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
	linux-security-module, selinux, linux-kernel
In-Reply-To: <ov33cu2wosubbfufcmfyoinfatecskjgmkvqyit33komlcla2d@2qgj45724bql>

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

On Wed, May 27, 2026 at 01:41:52PM -0400, Aaron Tomlin wrote:

> > > The actual use case here is multi-tenant workload isolation and visibility.
> > > Passing the evaluated cpumask to the BPF LSM allows operators to write a
> > > simple eBPF program to detect spatial boundary overlaps (e.g., logging an
> > > event if a requested mask intersects with platform-reserved cores).

Why isn't cgroups good enough to enforce this? If you create a cgroup
hierarchy per tenant, and constrain them using the cpuset controller,
they should not be able to escape, rendering this event impossible.

> > > If this justification makes more sense, I will focus strictly on the
> > > seccomp pointer limitations and multi-tenant workload isolation.
> > 
> > I suppose it does, my only remaining question is if that is indeed
> > proper use of LSM -- I really don't know much about that.
> > 
> 
> We are not creating a bespoke BPF hook here; rather, we are rectifying a
> historical blind spot within the API. The existing LSM hook is invoked
> during sched_setaffinity(), yet it presently receives only the task_struct
> pointer. Consequently, the security module is essentially asked, "Should
> Process A be permitted to alter Process B's affinity?" without being
> informed of the proposed affinity itself. Providing in_mask simply
> furnishes the existing hook with the requisite payload to make an informed
> decision.

It occurs to me that this same argument would require to also pass in
the new sched_attr, no? That way the LSM can inspect the new policy
before it becomes effective.

> Were the objective solely one of observability, a tracepoint would indeed
> be the most suitable mechanism. However, if the aim within multi-tenant
> environments is active enforcement (namely, safely returning -EPERM to deny
> the pinning request before the scheduler applies it), the LSM layer remains
> the standard, architecturally supported gateway for returning syscall
> errors in accordance with administrative policy.

Indeed; but being constrained in a cpuset cgroup would result in the
same, no?

> I shall defer to Paul Moore and the LSM maintainers for their final
> blessing on the LSM API semantics.

Yes, I think that this is an interesting test-case of the LSM purpose.

You seem to be mostly aiming at resource control, something that is
traditionally done elsewhere.

> Thank you once again for the thorough review and for keeping the
> architectural boundaries honest.

No problem, just trying to understand myself ;-)

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply


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