Linux Security Modules development
 help / color / mirror / Atom feed
* Re: [bug report] keys: request_key_auth payload use-after-free in keyctl_instantiate_key_common()
From: Jarkko Sakkinen @ 2026-05-21 23:52 UTC (permalink / raw)
  To: Shaomin Chen
  Cc: keyrings, linux-security-module, linux-kernel, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn
In-Reply-To: <20260519144403.436694-1-eeesssooo020@gmail.com>

On Tue, May 19, 2026 at 10:44:03PM +0800, Shaomin Chen wrote:
> Hi,
> 
> keyctl_instantiate_key_common() can use a stale request_key_auth payload after
> the current request-key authorisation key has been revoked.
> 
> The relevant code pattern is:
> 
> 	rka = instkey->payload.data[0];
> 	...
> 	copy_from_iter_full(payload, plen, from);   /* can fault and sleep */
> 	...
> 	get_instantiation_keyring(ringid, rka, &dest_keyring);
> 	key_instantiate_and_link(rka->target_key, payload, plen,
> 				 dest_keyring, instkey);
> 
> keyctl_instantiate_key_common() does not hold authkey->sem, an RCU read-side
> critical section, or a reference to the request_key_auth payload across the
> sleeping copy and later rka dereferences.
> 
> One race sequence is:
> 
> 	Task A: request-key helper child        Task B: original request_key path
> 	-------------------------------        ---------------------------------
> 	assume request-key authority
> 	enter KEYCTL_INSTANTIATE_IOV
> 	rka = instkey->payload.data[0]
> 	block in copy_from_iter_full()
> 	                                      helper parent instantiates target key
> 	                                      helper returns to kernel
> 	                                      complete_request_key(authkey, 0)
> 	                                      key_revoke(authkey)
> 	                                      request_key_auth_revoke(authkey)
> 	                                      rcu_assign_keypointer(authkey, NULL)
> 	                                      call_rcu(&rka->rcu, ...)
> 	                                      request_key_auth_rcu_disposal()
> 	                                      free_request_key_auth(rka)
> 	resume from copy_from_iter_full()
> 	get_instantiation_keyring(..., rka, ...)
> 	key_instantiate_and_link(rka->target_key, ...)
> 
> I reproduced this on a current upstream v7.1-rc3 based tree,
> HEAD ab5fce87a778c, with KASAN enabled:
> 
> 	BUG: KASAN: slab-use-after-free in keyctl_instantiate_key_common+0x1dc/0x2a0
> 	Read of size 8
> 	Allocated by task:
> 	  request_key_auth_new+0xe0/0x4d0
> 	Freed by task:
> 	  key_revoke+0x62/0xc0
> 	  call_sbin_request_key+0x6cb/0x740
> 
> The reproducer uses a request-key helper that forks a second process with the
> request-key authority.  The second process enters KEYCTL_INSTANTIATE_IOV and
> blocks in copy_from_iter_full() on a user fault after rka has been loaded.  The
> original helper then instantiates the target key and returns, which revokes the
> auth key and queues the request_key_auth payload for RCU freeing.  When the
> blocked instantiate path resumes, it dereferences the stale rka pointer.
> 
> I can provide the reproducer and a candidate patch.
> 
> Regards,
> Shaomin Chen

Please, just send them and we go from there. On surface looks legit.

BR, Jarkko

^ permalink raw reply

* Re: [PATCH 10/11] treewide: Manually convert custom kernel_param_ops .get callbacks
From: Jani Nikula @ 2026-05-21 17:44 UTC (permalink / raw)
  To: Kees Cook, Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Joonas Lahtinen,
	Rodrigo Vivi, Tvrtko Ursulin, David Airlie, Simona Vetter,
	Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133326.2465264-10-kees@kernel.org>

On Thu, 21 May 2026, Kees Cook <kees@kernel.org> wrote:
> diff --git a/drivers/gpu/drm/i915/i915_mitigations.c b/drivers/gpu/drm/i915/i915_mitigations.c
> index 6061eae84e9c..99cb38f355b6 100644
> --- a/drivers/gpu/drm/i915/i915_mitigations.c
> +++ b/drivers/gpu/drm/i915/i915_mitigations.c
> @@ -95,33 +95,37 @@ static int mitigations_set(const char *val, const struct kernel_param *kp)
>  	return 0;
>  }
>  
> -static int mitigations_get(char *buffer, const struct kernel_param *kp)
> +static int mitigations_get(struct seq_buf *buffer,
> +			   const struct kernel_param *kp)
>  {
>  	unsigned long local = READ_ONCE(mitigations);
> -	int count, i;
>  	bool enable;
> +	int i;

I'm fine with what you have, and I can do these as a follow-up later if
it's too much trouble, but I suggest something like this:

	const char *sep = "";

>  
> -	if (!local)
> -		return scnprintf(buffer, PAGE_SIZE, "%s\n", "off");
> +	if (!local) {
> +		seq_buf_printf(buffer, "%s\n", "off");
> +		return 0;
> +	}
>  
>  	if (local & BIT(BITS_PER_LONG - 1)) {
> -		count = scnprintf(buffer, PAGE_SIZE, "%s,", "auto");
> +		seq_buf_printf(buffer, "%s,", "auto");

		seq_buf_printf(buffer, "%s%s", sep, "auto");
		sep = ",";

(In the printf the sep is just for future expansion, though I don't
expect one.)

>  		enable = false;
>  	} else {
>  		enable = true;
> -		count = 0;
>  	}
>  
>  	for (i = 0; i < ARRAY_SIZE(names); i++) {
>  		if ((local & BIT(i)) != enable)
>  			continue;
> -
> -		count += scnprintf(buffer + count, PAGE_SIZE - count,
> -				   "%s%s,", enable ? "" : "!", names[i]);
> +		seq_buf_printf(buffer, "%s%s,", enable ? "" : "!", names[i]);

		seq_buf_printf(buffer, "%s%s%s", sep, enable ? "" : "!", names[i]);
		sep = ",";

>  	}

	seq_buf_puts(buffer, "\n");

>  
> -	buffer[count - 1] = '\n';
> -	return count;
> +	/* Replace the trailing comma with a newline. */
> +	if (!seq_buf_has_overflowed(buffer) && buffer->len > 0 &&
> +	    buffer->buffer[buffer->len - 1] == ',')
> +		buffer->buffer[buffer->len - 1] = '\n';

Drop the above.

I.e. keep track of sep while printing to avoid removing it later.

BR,
Jani.

> +
> +	return 0;
>  }
>  
>  static DEFINE_KERNEL_PARAM_OPS(ops, mitigations_set, mitigations_get);

-- 
Jani Nikula, Intel

^ permalink raw reply

* Re: [PATCH 01/11] params: bound array element output to the caller's page buffer
From: David Laight @ 2026-05-21 16:46 UTC (permalink / raw)
  To: Kees Cook
  Cc: Luis Chamberlain, Pengpeng Hou, stable, Petr Pavlu,
	Richard Weinberger, Anton Ivanov, Johannes Berg,
	Rafael J. Wysocki, Len Brown, Corey Minyard, Gabriel Somlo,
	Michael S. Tsirkin, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
	Tvrtko Ursulin, David Airlie, Simona Vetter, Bart Van Assche,
	Jason Gunthorpe, Leon Romanovsky, Laurent Pinchart, Hans de Goede,
	Mauro Carvalho Chehab, Bjorn Helgaas, Hannes Reinecke,
	James E.J. Bottomley, Martin K. Petersen, Daniel Lezcano,
	Zhang Rui, Lukasz Luba, Greg Kroah-Hartman, Jiri Slaby,
	Alan Stern, Jason Wang, Xuan Zhuo, Eugenio Pérez,
	Jason Baron, Jim Cromie, Tiwei Bie, Benjamin Berg,
	Ilpo Järvinen, David E. Box, Maciej W. Rozycki,
	Srinivas Pandruvada, Peter Zijlstra, Heiko Carstens,
	Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133326.2465264-1-kees@kernel.org>

On Thu, 21 May 2026 06:33:14 -0700
Kees Cook <kees@kernel.org> wrote:

> From: Pengpeng Hou <pengpeng@iscas.ac.cn>
> 
> param_array_get() appends each element's string representation into the
> shared sysfs page buffer by passing buffer + off to the element getter.
> 
> That works for getters that only write a small bounded string, but
> param_get_charp() and similar helpers format against PAGE_SIZE from the
> pointer they receive. Once off is non-zero, an element getter can
> therefore write past the end of the original sysfs page buffer.
> 
> Collect each element into a temporary PAGE_SIZE buffer first and then
> copy only the remaining space into the caller's page buffer.

Should this be using a 4k buffer on all architectures?
Initially perhaps just using a different name for the constant until
all the associated PAGE_SIZE limits have been removed.

-- David

> 
> Cc: stable@vger.kernel.org
> Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
>  kernel/params.c | 26 ++++++++++++++++++++------
>  1 file changed, 20 insertions(+), 6 deletions(-)
> 
> diff --git a/kernel/params.c b/kernel/params.c
> index 74d620bc2521..752721922a15 100644
> --- a/kernel/params.c
> +++ b/kernel/params.c
> @@ -475,22 +475,36 @@ static int param_array_set(const char *val, const struct kernel_param *kp)
>  static int param_array_get(char *buffer, const struct kernel_param *kp)
>  {
>  	int i, off, ret;
> +	char *elem_buf;
>  	const struct kparam_array *arr = kp->arr;
>  	struct kernel_param p = *kp;
>  
> +	elem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
> +	if (!elem_buf)
> +		return -ENOMEM;
> +
>  	for (i = off = 0; i < (arr->num ? *arr->num : arr->max); i++) {
> -		/* Replace \n with comma */
> -		if (i)
> -			buffer[off - 1] = ',';
>  		p.arg = arr->elem + arr->elemsize * i;
>  		check_kparam_locked(p.mod);
> -		ret = arr->ops->get(buffer + off, &p);
> +		ret = arr->ops->get(elem_buf, &p);
>  		if (ret < 0)
> -			return ret;
> +			goto out;
> +		ret = min(ret, (int)(PAGE_SIZE - 1 - off));
> +		if (!ret)
> +			break;
> +		/* Replace the previous element's trailing newline with a comma. */
> +		if (i)
> +			buffer[off - 1] = ',';
> +		memcpy(buffer + off, elem_buf, ret);
>  		off += ret;
> +		if (off == PAGE_SIZE - 1)
> +			break;
>  	}
>  	buffer[off] = '\0';
> -	return off;
> +	ret = off;
> +out:
> +	kfree(elem_buf);
> +	return ret;
>  }
>  
>  static void param_array_free(void *arg)


^ permalink raw reply

* [PATCH 6/6] landlock: Document LANDLOCK_SCOPE_SYSV_MESSAGE_QUEUE
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Document the new SysV message queue scope restriction.  Make clear
that because these queues do not use persistent handles, subsequent
operations on a queue already obtained via msgget (or any other
means) may be restricted once this right is enforced.  Also note
that denials surface as -EACCES rather than -EPERM, since the
generic SysV IPC permission path maps every LSM denial to -EACCES.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 Documentation/admin-guide/LSM/landlock.rst |  1 +
 Documentation/userspace-api/landlock.rst   | 30 +++++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst
index 9923874e2156..e983d903bdf9 100644
--- a/Documentation/admin-guide/LSM/landlock.rst
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -58,6 +58,7 @@ AUDIT_LANDLOCK_ACCESS
     **scope.*** - IPC scoping restrictions (ABI 6+):
         - scope.abstract_unix_socket - Abstract UNIX socket connection denied
         - scope.signal - Signal sending denied
+        - scope.sysv_msg_queue - SysV message queue operation denied (ABI 10+)
 
     Multiple blockers can appear in a single event (comma-separated) when
     multiple access rights are missing. For example, creating a regular file
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 45861fa75685..933b2994fec4 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -84,7 +84,8 @@ to be explicit about the denied-by-default access rights.
             LANDLOCK_ACCESS_NET_CONNECT_TCP,
         .scoped =
             LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
-            LANDLOCK_SCOPE_SIGNAL,
+            LANDLOCK_SCOPE_SIGNAL |
+            LANDLOCK_SCOPE_SYSV_MSG_QUEUE,
     };
 
 Because we may not know which kernel version an application will be executed
@@ -132,6 +133,10 @@ version, and only use the available subset of access rights:
     case 6 ... 8:
         /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
         ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
+        __attribute__((fallthrough));
+    case 9:
+        /* Removes LANDLOCK_SCOPE_SYSV_MSG_QUEUE for ABI < 10 */
+        ruleset_attr.scoped &= ~LANDLOCK_SCOPE_SYSV_MSG_QUEUE;
     }
 
 This enables the creation of an inclusive ruleset that will contain our rules.
@@ -380,6 +385,22 @@ The operations which can be scoped are:
     A :manpage:`sendto(2)` on a socket which was previously connected will not
     be restricted.  This works for both datagram and stream sockets.
 
+``LANDLOCK_SCOPE_SYSV_MSG_QUEUE``
+    This limits the set of System V message queues to which we can perform
+    :manpage:`msgget(2)`, :manpage:`msgrcv(2)`, :manpage:`msgsnd(2)`, and
+    :manpage:`msgctl(2)` calls to only message queues which were created by a
+    process in the same or a nested Landlock domain.
+
+    Since System V message queues are IPC namespace global constructs and do
+    not use file descriptors, enforcement of a ruleset with this scoping may
+    cause subsequent operations on an msqid that were allowed prior to
+    enforcement to be denied.
+
+    Denials are reported as ``EACCES``.  Unlike other Landlock scopes,
+    the check shares the generic SysV IPC permission path
+    (``ipcperms(3)``), which maps every denial to ``EACCES`` before it
+    reaches user space.
+
 IPC scoping does not support exceptions via :manpage:`landlock_add_rule(2)`.
 If an operation is scoped within a domain, no rules can be added to allow access
 to resources or processes outside of the scope.
@@ -722,6 +743,13 @@ Starting with the Landlock ABI version 9, it is possible to restrict
 connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
 the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
 
+System V message queue (ABI < 10)
+---------------------------------
+
+Starting with the Landlock ABI version 10, it is possible to restrict
+operations on System V message queues by setting
+``LANDLOCK_SCOPE_SYSV_MSG_QUEUE`` to the ``scoped`` ruleset attribute.
+
 .. _kernel_support:
 
 Kernel support
-- 
2.53.0


^ permalink raw reply related

* [PATCH 5/6] samples/landlock: Support LANDLOCK_SCOPE_SYSV_MSG_QUEUE in sandboxer
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Add sandboxer support for the new LANDLOCK_SCOPE_SYSV_MSG_QUEUE access
right.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 samples/landlock/sandboxer.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 66e56ae275c6..689628b87f5f 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -235,10 +235,12 @@ static bool check_ruleset_scope(const char *const env_var,
 	bool error = false;
 	bool abstract_scoping = false;
 	bool signal_scoping = false;
+	bool sysv_msg_queue_scoping = false;
 
 	/* Scoping is not supported by Landlock ABI */
 	if (!(ruleset_attr->scoped &
-	      (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))
+	      (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL |
+	       LANDLOCK_SCOPE_SYSV_MSG_QUEUE)))
 		goto out_unset;
 
 	env_type_scope = getenv(env_var);
@@ -255,6 +257,9 @@ static bool check_ruleset_scope(const char *const env_var,
 		} else if (strcmp("s", ipc_scoping_name) == 0 &&
 			   !signal_scoping) {
 			signal_scoping = true;
+		} else if (strcmp("m", ipc_scoping_name) == 0 &&
+			   !sysv_msg_queue_scoping) {
+			sysv_msg_queue_scoping = true;
 		} else {
 			fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
 				ipc_scoping_name);
@@ -271,6 +276,8 @@ static bool check_ruleset_scope(const char *const env_var,
 		ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
 	if (!signal_scoping)
 		ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;
+	if (!sysv_msg_queue_scoping)
+		ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SYSV_MSG_QUEUE;
 
 	unsetenv(env_var);
 	return error;
@@ -301,7 +308,7 @@ static bool check_ruleset_scope(const char *const env_var,
 
 /* clang-format on */
 
-#define LANDLOCK_ABI_LAST 9
+#define LANDLOCK_ABI_LAST 10
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -327,6 +334,7 @@ static const char help[] =
 	"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
 	"  - \"a\" to restrict opening abstract unix sockets\n"
 	"  - \"s\" to restrict sending signals\n"
+	"  - \"m\" to restrict associating with message queues\n"
 	"\n"
 	"A sandboxer should not log denied access requests to avoid spamming logs, "
 	"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
@@ -336,7 +344,7 @@ static const char help[] =
 	ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
 	ENV_TCP_BIND_NAME "=\"9418\" "
 	ENV_TCP_CONNECT_NAME "=\"80:443\" "
-	ENV_SCOPED_NAME "=\"a:s\" "
+	ENV_SCOPED_NAME "=\"a:s:m\" "
 	"%1$s bash -i\n"
 	"\n"
 	"This sandboxer can use Landlock features up to ABI version "
@@ -358,7 +366,7 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
 				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
 		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
-			  LANDLOCK_SCOPE_SIGNAL,
+			  LANDLOCK_SCOPE_SIGNAL | LANDLOCK_SCOPE_SYSV_MSG_QUEUE,
 	};
 	int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
 	int set_restrict_flags = 0;
@@ -444,6 +452,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		/* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
 		ruleset_attr.handled_access_fs &=
 			~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
+		__attribute__((fallthrough));
+	case 9:
+		/* Removes LANDLOCK_SCOPE_SYSV_MSG_QUEUE for ABI < 10 */
+		ruleset_attr.scoped &= ~LANDLOCK_SCOPE_SYSV_MSG_QUEUE;
 		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
-- 
2.53.0


^ permalink raw reply related

* [PATCH 4/6] selftests/landlock: Test LANDLOCK_SCOPE_SYSV_MSG_QUEUE
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Add selftests for SysV message queue scoped right.

Use the existing scoped domain harness for msgget, and another fixture
for testing msgsnd, msgrcv and msgctl.

Pass the msqid around for coverage of non-msgget syscalls, since calling
msgget while already restricted would fail and prevent testing the
operation under test.

Denials are checked against -EACCES rather than -EPERM: msgget,
msgsnd, msgrcv and msgctl(IPC_STAT) all reach the Landlock scope
check via ipcperms(), whose callers map every non-zero return into
-EACCES before propagating it to user space.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 .../landlock/scoped_sysv_msg_queue_test.c     | 256 ++++++++++++++++++
 1 file changed, 256 insertions(+)
 create mode 100644 tools/testing/selftests/landlock/scoped_sysv_msg_queue_test.c

diff --git a/tools/testing/selftests/landlock/scoped_sysv_msg_queue_test.c b/tools/testing/selftests/landlock/scoped_sysv_msg_queue_test.c
new file mode 100644
index 000000000000..41f99803b593
--- /dev/null
+++ b/tools/testing/selftests/landlock/scoped_sysv_msg_queue_test.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - SysV Message Queue Scoping
+ *
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "scoped_common.h"
+
+/*
+ * Removes the message queue identified by @msqid, ignoring any error since
+ * the caller might no longer have permission to operate on it (for example,
+ * after entering a scoped domain).
+ */
+static void cleanup_msg_queue(int msqid)
+{
+	if (msqid >= 0)
+		msgctl(msqid, IPC_RMID, NULL);
+}
+
+/* clang-format off */
+FIXTURE(scoped_domains) {};
+/* clang-format on */
+
+#include "scoped_base_variants.h"
+
+FIXTURE_SETUP(scoped_domains)
+{
+	drop_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(scoped_domains)
+{
+}
+
+/*
+ * Parent creates a SysV message queue, then the child tries to associate
+ * with it via msgget(2).  When the child is in a domain that scopes message
+ * queues and the parent is not in that same scope, the association must be
+ * denied with -EACCES (msgget runs the scope check via ipcperms(), which
+ * masks every denial as -EACCES).
+ */
+TEST_F(scoped_domains, check_access_msg_queue)
+{
+	pid_t child;
+	int status;
+	int msqid = -1;
+	int pipe_parent[2], pipe_child[2];
+	char buf;
+	key_t key;
+	bool can_associate;
+
+	/*
+	 * The child can associate with the parent's queue unless the child
+	 * is in a scoped domain that does not include the parent (i.e. the
+	 * parent is outside the child's domain).
+	 */
+	can_associate = !variant->domain_child;
+
+	/*
+	 * Picks a per-test key derived from PID to avoid collisions.  Stale
+	 * queues from a previous run are unlikely but handled by removing
+	 * any matching entry before applying any scope.
+	 */
+	key = (key_t)(getpid() & 0x7fffffff);
+	cleanup_msg_queue(msgget(key, 0));
+
+	if (variant->domain_both)
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SYSV_MSG_QUEUE);
+
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int ret;
+
+		EXPECT_EQ(0, close(pipe_child[0]));
+		EXPECT_EQ(0, close(pipe_parent[1]));
+
+		if (variant->domain_child)
+			create_scoped_domain(_metadata,
+					     LANDLOCK_SCOPE_SYSV_MSG_QUEUE);
+
+		/* Signals readiness to the parent. */
+		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+		EXPECT_EQ(0, close(pipe_child[1]));
+
+		/* Waits for the parent to have created the queue. */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf, 1));
+		EXPECT_EQ(0, close(pipe_parent[0]));
+
+		ret = msgget(key, 0);
+		if (can_associate) {
+			ASSERT_LE(0, ret);
+		} else {
+			ASSERT_EQ(-1, ret);
+			/*
+			 * msgget uses ipcperms(), which masks every LSM
+			 * denial as -EACCES regardless of the value the
+			 * LSM hook returns.
+			 */
+			ASSERT_EQ(EACCES, errno);
+		}
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+	EXPECT_EQ(0, close(pipe_child[1]));
+	EXPECT_EQ(0, close(pipe_parent[0]));
+
+	if (variant->domain_parent)
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SYSV_MSG_QUEUE);
+
+	/* Waits for the child to be ready. */
+	ASSERT_EQ(1, read(pipe_child[0], &buf, 1));
+	EXPECT_EQ(0, close(pipe_child[0]));
+
+	msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0600);
+	ASSERT_LE(0, msqid);
+
+	/* Releases the child. */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+	EXPECT_EQ(0, close(pipe_parent[1]));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	cleanup_msg_queue(msqid);
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * The msg_queue_associate hook (exercised by msgget(2)) is covered by the
+ * scoped_domains fixture above.  The remaining hooks all funnel through the
+ * same scope check, so it suffices to verify that each operation is denied
+ * when the child is scoped relative to the queue's creator.
+ *
+ * To attribute a denial to the operation under test (and not to a preceding
+ * msgget(2) call), the parent creates the queue and the child inherits the
+ * msqid across fork(2), bypassing msg_queue_associate.
+ */
+enum msg_op {
+	MSG_OP_SND,
+	MSG_OP_RCV,
+	MSG_OP_CTL,
+};
+
+/* clang-format off */
+FIXTURE(scoping_msg_ops) {};
+/* clang-format on */
+
+FIXTURE_VARIANT(scoping_msg_ops)
+{
+	enum msg_op op;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(scoping_msg_ops, msgsnd) {
+	/* clang-format on */
+	.op = MSG_OP_SND,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(scoping_msg_ops, msgrcv) {
+	/* clang-format on */
+	.op = MSG_OP_RCV,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(scoping_msg_ops, msgctl) {
+	/* clang-format on */
+	.op = MSG_OP_CTL,
+};
+
+FIXTURE_SETUP(scoping_msg_ops)
+{
+	drop_caps(_metadata);
+}
+
+FIXTURE_TEARDOWN(scoping_msg_ops)
+{
+}
+
+TEST_F(scoping_msg_ops, deny_op)
+{
+	struct msgbuf {
+		long mtype;
+		char mtext[1];
+	} msg = { .mtype = 1 };
+	struct msqid_ds ds;
+	pid_t child;
+	int status;
+	int msqid, ret = 0;
+	key_t key;
+
+	key = (key_t)(getpid() & 0x7fffffff);
+	cleanup_msg_queue(msgget(key, 0));
+
+	msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0600);
+	ASSERT_LE(0, msqid);
+
+	/* Preloads a message so msgrcv(2) would otherwise succeed. */
+	ASSERT_EQ(0, msgsnd(msqid, &msg, sizeof(msg.mtext), 0));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		create_scoped_domain(_metadata, LANDLOCK_SCOPE_SYSV_MSG_QUEUE);
+
+		switch (variant->op) {
+		case MSG_OP_SND:
+			ret = msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
+			break;
+		case MSG_OP_RCV:
+			ret = msgrcv(msqid, &msg, sizeof(msg.mtext), 0,
+				     IPC_NOWAIT);
+			break;
+		case MSG_OP_CTL:
+			ret = msgctl(msqid, IPC_STAT, &ds);
+			break;
+		}
+		ASSERT_EQ(-1, ret);
+		/*
+		 * msgsnd, msgrcv and msgctl(IPC_STAT) all reach the
+		 * Landlock scope check via ipcperms(), whose callers map
+		 * any non-zero return into -EACCES before propagating it
+		 * to user space.
+		 */
+		ASSERT_EQ(EACCES, errno);
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	cleanup_msg_queue(msqid);
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+TEST_HARNESS_MAIN
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/6] landlock: Bump ABI for LANDLOCK_SCOPE_SYSV_MSG_QUEUE
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Bump the ABI version for Landlock SysV message queue scoping.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 security/landlock/syscalls.c                 | 2 +-
 tools/testing/selftests/landlock/base_test.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index accfd2e5a0cd..d45469d5d464 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
  * If the change involves a fix that requires userspace awareness, also update
  * the errata documentation in Documentation/userspace-api/landlock.rst .
  */
-const int landlock_abi_version = 9;
+const int landlock_abi_version = 10;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 30d37234086c..6c8113c2ded1 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,8 +76,8 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
-					     LANDLOCK_CREATE_RULESET_VERSION));
+	ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
+					      LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
 					      LANDLOCK_CREATE_RULESET_VERSION));
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/6] landlock: Add LANDLOCK_SCOPE_SYSV_MSG_QUEUE
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Add a new scoped access right LANDLOCK_SCOPE_SYSV_MSG_QUEUE for
controlling operations msgget, msgsnd, msgrcv, and msgctl on SysV
message queues.

Merely handling msgget is insufficient; SysV message queues do not
use FDs or process local handles, and the msqid associated with a
queue is valid within the IPC namespace.  There is no requirement
to perform a msgget to interact with a SysV message queue.

When a process enforces this scoping, access to SysV message queues
by a restricted process is only allowed if the queue was created by
a process in the same or a nested Landlock domain.

When a SysV message queue is allocated by a process in a Landlock
domain, the security blob for the kern_ipc_perm is updated to
reflect domain provenance and the blob is tagged as a message queue
via the new @kind enum.

The scope is enforced from the generic ipc_permission hook rather
than the per-call msg_queue_* hooks.  ipc_permission is the choke
point for msgget on an existing queue and for msgsnd / msgrcv /
msgctl(IPC_STAT, MSG_STAT, MSG_STAT_ANY).

ipc_permission also fires for semaphores and shared memory, so the
hook bails out when the blob's @kind is not LANDLOCK_SYSV_IPC_MSG_QUEUE.

msgctl_down() (IPC_RMID and IPC_SET) does not go through
ipc_permission, so msg_queue_msgctl is kept to cover those.  It
also guards against the IPC_INFO / MSG_INFO case where @msq is
NULL and there is no specific queue to scope.

Also update the scoped_test ACCESS_LAST sentinel to track the new
last scope so the unknown-scope selftest does not falsely accept
LANDLOCK_SCOPE_SYSV_MSG_QUEUE as unknown.

Audit records are generated for this scope on denials.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 include/uapi/linux/landlock.h                 |   3 +
 security/landlock/audit.c                     |   4 +
 security/landlock/audit.h                     |   1 +
 security/landlock/limits.h                    |   2 +-
 security/landlock/task.c                      | 137 ++++++++++++++++++
 .../testing/selftests/landlock/scoped_test.c  |   2 +-
 6 files changed, 147 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 10a346e55e95..c879b345afa3 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -398,10 +398,13 @@ struct landlock_net_port_attr {
  *   related Landlock domain (e.g., a parent domain or a non-sandboxed process).
  * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal
  *   to another process outside the domain.
+ * - %LANDLOCK_SCOPE_SYSV_MSG_QUEUE: Restrict a sandboxed process from interacting
+ *   with a sysv msg queue created by a process outside the domain.
  */
 /* clang-format off */
 #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
 #define LANDLOCK_SCOPE_SIGNAL		                (1ULL << 1)
+#define LANDLOCK_SCOPE_SYSV_MSG_QUEUE			(1ULL << 2)
 /* clang-format on*/
 
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 8d0edf94037d..174ddf6bd42c 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -79,6 +79,10 @@ get_blocker(const enum landlock_request_type type,
 	case LANDLOCK_REQUEST_SCOPE_SIGNAL:
 		WARN_ON_ONCE(access_bit != -1);
 		return "scope.signal";
+
+	case LANDLOCK_REQUEST_SCOPE_MSG_QUEUE:
+		WARN_ON_ONCE(access_bit != -1);
+		return "scope.sysv_msg_queue";
 	}
 
 	WARN_ON_ONCE(1);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 56778331b58c..cc5700adab5a 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -21,6 +21,7 @@ enum landlock_request_type {
 	LANDLOCK_REQUEST_NET_ACCESS,
 	LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
 	LANDLOCK_REQUEST_SCOPE_SIGNAL,
+	LANDLOCK_REQUEST_SCOPE_MSG_QUEUE,
 };
 
 /*
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index b454ad73b15e..7b74bcd66470 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -27,7 +27,7 @@
 #define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
 #define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
 
-#define LANDLOCK_LAST_SCOPE		LANDLOCK_SCOPE_SIGNAL
+#define LANDLOCK_LAST_SCOPE		LANDLOCK_SCOPE_SYSV_MSG_QUEUE
 #define LANDLOCK_MASK_SCOPE		((LANDLOCK_LAST_SCOPE << 1) - 1)
 #define LANDLOCK_NUM_SCOPE		__const_hweight64(LANDLOCK_MASK_SCOPE)
 
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 6d46042132ce..68ac46baa2a5 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -434,6 +434,138 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
 	return -EPERM;
 }
 
+static const struct access_masks sysv_msg_queue_scope = {
+	.scope = LANDLOCK_SCOPE_SYSV_MSG_QUEUE,
+};
+
+/**
+ * hook_msg_queue_alloc_security - Record the creator's domain on a SysV msg
+ *				   queue
+ *
+ * @perm: IPC permission structure of the newly created message queue.
+ *
+ * Save a reference to the creating task's Landlock domain in the IPC security
+ * blob and tag the blob as belonging to a message queue so that the generic
+ * ipc_permission hook can distinguish msg queues from sem and shm objects.
+ *
+ * Return: 0 (allocation of the blob itself is handled by the LSM core).
+ */
+static int hook_msg_queue_alloc_security(struct kern_ipc_perm *const perm)
+{
+	struct landlock_kern_ipc_perm_security *const ipc_sec =
+		landlock_kern_ipc_perm(perm);
+	const struct landlock_cred_security *const subject =
+		landlock_get_applicable_subject(current_cred(),
+						sysv_msg_queue_scope, NULL);
+
+	ipc_sec->kind = LANDLOCK_SYSV_IPC_MSG_QUEUE;
+
+	/*
+	 * The blob is zero-allocated by the LSM core, so owner_subject.domain
+	 * is already NULL for an unsandboxed creator.
+	 */
+	if (!subject)
+		return 0;
+
+	landlock_get_ruleset(subject->domain);
+	ipc_sec->owner_subject = *subject;
+	return 0;
+}
+
+/**
+ * hook_msg_queue_free_security - Release the creator's domain reference
+ *
+ * @perm: IPC permission structure of the message queue being destroyed.
+ *
+ * The IPC security blob itself is freed by the LSM core.
+ */
+static void hook_msg_queue_free_security(struct kern_ipc_perm *const perm)
+{
+	struct landlock_kern_ipc_perm_security *const ipc_sec =
+		landlock_kern_ipc_perm(perm);
+
+	/* May be called from an RCU callback (msg_rcu_free()). */
+	landlock_put_ruleset_deferred(ipc_sec->owner_subject.domain);
+}
+
+/**
+ * hook_ipc_permission - Enforce SysV msg queue scoping on the current task
+ *
+ * @ipcp: IPC permission structure of the object being accessed.
+ * @flag: Requested mode bits (unused; same value for every msg queue access).
+ *
+ * The ipc_permission hook is the choke point for msgget on an existing queue
+ * and for msgsnd / msgrcv / msgctl(IPC_STAT, MSG_STAT, MSG_STAT_ANY) before
+ * they touch any per-message state. Using the per-message msg_queue_msgrcv hook
+ * instead would not work: find_msg() silently skips messages for which the
+ * hook returns an error and turns the result into -EAGAIN / -ENOMSG.
+ *
+ * The hook fires for sem and shm objects as well; @kind is used to filter
+ * them out.
+ *
+ * Return: 0 if access is allowed, -EPERM if scoped out.
+ */
+static int hook_ipc_permission(struct kern_ipc_perm *const ipcp,
+			       const short flag)
+{
+	const struct landlock_kern_ipc_perm_security *const ipc_sec =
+		landlock_kern_ipc_perm(ipcp);
+	size_t handle_layer;
+	const struct landlock_cred_security *subject;
+
+	/* Don't worry about other IPC objects for now */
+	if (ipc_sec->kind != LANDLOCK_SYSV_IPC_MSG_QUEUE)
+		return 0;
+
+	subject = landlock_get_applicable_subject(current_cred(),
+						  sysv_msg_queue_scope,
+						  &handle_layer);
+	if (!subject)
+		return 0;
+
+	if (!domain_is_scoped(subject->domain, ipc_sec->owner_subject.domain,
+			      sysv_msg_queue_scope.scope))
+		return 0;
+
+	landlock_log_denial(subject, &(struct landlock_request) {
+		.type = LANDLOCK_REQUEST_SCOPE_MSG_QUEUE,
+		.audit = {
+			.type = LSM_AUDIT_DATA_IPC,
+			.u.ipc_id = ipcp->key,
+		},
+		.layer_plus_one = handle_layer + 1,
+	});
+	/*
+	 * What error return here technically doesn't matter; it all gets
+	 * mapped into EACCES when it's non-zero. Return EACCES anyway for
+	 * consistency.
+	 */
+	return -EACCES;
+}
+
+/**
+ * hook_msg_queue_msgctl - Enforce scoping on msgctl(IPC_RMID, IPC_SET)
+ *
+ * @msq: IPC permission structure of the message queue, or NULL for
+ *	 namespace-wide commands (IPC_INFO, MSG_INFO).
+ * @cmd: msgctl command code (unused).
+ *
+ * msgctl_down() does not go through ipc_permission(), so this hook is
+ * needed to cover IPC_RMID and IPC_SET.  IPC_INFO and MSG_INFO are
+ * namespace-wide queries with no specific queue, so they are not in scope
+ * for SysV msg queue scoping.
+ *
+ * Return: 0 if access is allowed, -EPERM if scoped out.
+ */
+static int hook_msg_queue_msgctl(struct kern_ipc_perm *const msq, const int cmd)
+{
+	/* IPC_INFO and MSG_INFO are queue-less; nothing to scope. */
+	if (!msq)
+		return 0;
+
+	return hook_ipc_permission(msq, 0);
+}
+
 static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
 	LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
@@ -443,6 +575,11 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
 
 	LSM_HOOK_INIT(task_kill, hook_task_kill),
 	LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask),
+
+	LSM_HOOK_INIT(msg_queue_alloc_security, hook_msg_queue_alloc_security),
+	LSM_HOOK_INIT(msg_queue_free_security, hook_msg_queue_free_security),
+	LSM_HOOK_INIT(msg_queue_msgctl, hook_msg_queue_msgctl),
+	LSM_HOOK_INIT(ipc_permission, hook_ipc_permission),
 };
 
 __init void landlock_add_task_hooks(void)
diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c
index b90f76ed0d9c..6692ba0573e6 100644
--- a/tools/testing/selftests/landlock/scoped_test.c
+++ b/tools/testing/selftests/landlock/scoped_test.c
@@ -12,7 +12,7 @@
 
 #include "common.h"
 
-#define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL
+#define ACCESS_LAST LANDLOCK_SCOPE_SYSV_MSG_QUEUE
 
 TEST(ruleset_with_unknown_scope)
 {
-- 
2.53.0


^ permalink raw reply related

* [PATCH 1/6] landlock: Add kern_ipc_perm credential blob structs
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess
In-Reply-To: <20260521160640.1716746-1-utilityemal77@gmail.com>

Add landlock_kern_ipc_perm_security, tracking ownership of SysV IPC
objects.

The struct contains the creating task's Landlock credential
(@owner_subject) and a @kind enum identifying which SysV IPC object
this blob describes.  The LSM core allocates the IPC blob for every
kern_ipc_perm regardless of object kind, so the generic
ipc_permission hook needs to be able to tell which objects it should
enforce a given scope on.  An enum makes it straightforward to extend
Landlock to sem and shm scoping later without revisiting the blob
layout.

Define the size of this struct in the lbs_ipc field for the Landlock
blob sizes.

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
---
 security/landlock/setup.c |  1 +
 security/landlock/task.h  | 50 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 47dac1736f10..44aff2d734e9 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -32,6 +32,7 @@ struct lsm_blob_sizes landlock_blob_sizes __ro_after_init = {
 	.lbs_file = sizeof(struct landlock_file_security),
 	.lbs_inode = sizeof(struct landlock_inode_security),
 	.lbs_superblock = sizeof(struct landlock_superblock_security),
+	.lbs_ipc = sizeof(struct landlock_kern_ipc_perm_security),
 };
 
 int landlock_errata __ro_after_init;
diff --git a/security/landlock/task.h b/security/landlock/task.h
index 7c00360219a2..0fb82e5e347c 100644
--- a/security/landlock/task.h
+++ b/security/landlock/task.h
@@ -9,6 +9,56 @@
 #ifndef _SECURITY_LANDLOCK_TASK_H
 #define _SECURITY_LANDLOCK_TASK_H
 
+#include <linux/ipc.h>
+#include <linux/types.h>
+
+#include "cred.h"
+#include "setup.h"
+
+/**
+ * enum landlock_sysv_ipc_kind - Kind of SysV IPC object backed by a blob
+ *
+ * @LANDLOCK_SYSV_IPC_UNSET: Blob has not been tagged by a Landlock IPC
+ *	allocation hook.  This is the zero value used for sem and shm
+ *	objects that Landlock does not currently scope, as well as for
+ *	any future kind that has not yet been wired up.
+ * @LANDLOCK_SYSV_IPC_MSG_QUEUE: Blob belongs to a SysV message queue.
+ */
+enum landlock_sysv_ipc_kind {
+	LANDLOCK_SYSV_IPC_UNSET = 0,
+	LANDLOCK_SYSV_IPC_MSG_QUEUE,
+};
+
+/**
+ * struct landlock_kern_ipc_perm_security - IPC object security blob
+ *
+ * Enable provenance tracking of SysV IPC objects to scope IPC accesses.
+ * The LSM core allocates a blob for every kern_ipc_perm regardless of the
+ * underlying object kind (msg queue, semaphore, shared memory), so callers
+ * that act on a subset of object kinds must consult @kind before
+ * interpreting @owner_subject.
+ */
+struct landlock_kern_ipc_perm_security {
+	/**
+	 * @owner_subject: Landlock credential of the task that created the
+	 * kernel IPC object.  Only meaningful when @kind is not
+	 * %LANDLOCK_SYSV_IPC_UNSET.
+	 */
+	struct landlock_cred_security owner_subject;
+	/**
+	 * @kind: Kind of SysV IPC object this blob describes.  Set by the
+	 * matching alloc hook; %LANDLOCK_SYSV_IPC_UNSET for objects whose
+	 * kind Landlock does not currently track.
+	 */
+	enum landlock_sysv_ipc_kind kind;
+};
+
+static inline struct landlock_kern_ipc_perm_security *
+landlock_kern_ipc_perm(const struct kern_ipc_perm *const perm)
+{
+	return perm->security + landlock_blob_sizes.lbs_ipc;
+}
+
 __init void landlock_add_task_hooks(void);
 
 #endif /* _SECURITY_LANDLOCK_TASK_H */
-- 
2.53.0


^ permalink raw reply related

* [PATCH 0/6] landlock: Add scoped access bit for SysV message queues
From: Justin Suess @ 2026-05-21 16:06 UTC (permalink / raw)
  To: gnoack3000, mic; +Cc: linux-kernel, linux-security-module, Justin Suess

This series extends Landlock with a new scoped access right,
LANDLOCK_SCOPE_SYSV_MSG_QUEUE, allowing a sandboxed process to be
restricted from interacting with SysV message queues created outside
of its Landlock domain (or a nested domain).

While use of SysV message queues is less common than other IPC types,
they are commonly used in older applications which may be vulnerable
to exploitation, so they are a meaningful attack surface to restrict.

Background
==========
SysV message queues differ from the IPC mechanisms Landlock already
scopes (UNIX sockets and signals): they have no FD or process-local
handle.  A msqid is valid IPC-namespace-wide and can be obtained
without calling msgget(), so simply hooking msgget() is insufficient.
Domain provenance has to be tracked on the queue itself and checked on
every operation against it.

Approach
========
A new credential blob is attached to each kern_ipc_perm at creation
time, recording the creating task's Landlock domain and a @kind
tag identifying the IPC object type.  The @kind tag is required
because the LSM core allocates an IPC blob for every kern_ipc_perm
regardless of kind, and the generic ipc_permission hook fires for
semaphores and shared memory as well as message queues.

The enum also leaves room to extend scoping to sem/shm later
without changing the blob layout.

Enforcement is done from security_ipc_permission(), which is the
single choke point for msgget() on an existing queue, msgsnd(),
msgrcv(), and the msgctl() variants that go through ipcperms()
(IPC_STAT, MSG_STAT, MSG_STAT_ANY).  msgctl_down() (IPC_RMID and
IPC_SET) bypasses ipcperms(), so the per-call msg_queue_msgctl
hook is kept for those cases.  msg_queue_msgctl also covers the
IPC_INFO / MSG_INFO case where no specific queue exists.

Quirks
======
- Denials surface as -EACCES rather than -EPERM because the generic
  ipcperms() path maps every LSM denial to -EACCES before returning
  to userspace.  This is documented and the selftests check for
  -EACCES accordingly.
- Because there is no persistent handle, a msqid already obtained
  by a process before it enforces this scope can become unusable
  once the restriction is in place; this is intentional and
  documented.

Patch layout
============
  1. Add the kern_ipc_perm credential blob and @kind enum.
  2. Implement LANDLOCK_SCOPE_SYSV_MSG_QUEUE, the ipc_permission
     hook, and msg_queue_msgctl coverage for IPC_RMID/IPC_SET and
     IPC_INFO/MSG_INFO.
  3. Bump the Landlock ABI.
  4. Selftests covering msgget plus a separate fixture for msgsnd,
     msgrcv, and msgctl using a pre-created msqid.
  5. sandboxer sample support for the new scope.
  6. Documentation updates covering the new scope, the -EACCES
     return code, and the implications of non-persistent handles.

Test coverage
=============
Selftests exercise denial and allow paths for msgget, msgsnd,
msgrcv, and msgctl(IPC_STAT) across domain boundaries, including
nested-domain inheritance.  All existing and added tests are
passing.

Kind Regards,
Justin Suess

Justin Suess (6):
  landlock: Add kern_ipc_perm credential blob structs
  landlock: Add LANDLOCK_SCOPE_SYSV_MSG_QUEUE
  landlock: Bump ABI for LANDLOCK_SCOPE_SYSV_MSG_QUEUE
  selftests/landlock: Test LANDLOCK_SCOPE_SYSV_MSG_QUEUE
  samples/landlock: Support LANDLOCK_SCOPE_SYSV_MSG_QUEUE in sandboxer
  landlock: Document LANDLOCK_SCOPE_SYSV_MESSAGE_QUEUE

 Documentation/admin-guide/LSM/landlock.rst    |   1 +
 Documentation/userspace-api/landlock.rst      |  30 +-
 include/uapi/linux/landlock.h                 |   3 +
 samples/landlock/sandboxer.c                  |  20 +-
 security/landlock/audit.c                     |   4 +
 security/landlock/audit.h                     |   1 +
 security/landlock/limits.h                    |   2 +-
 security/landlock/setup.c                     |   1 +
 security/landlock/syscalls.c                  |   2 +-
 security/landlock/task.c                      | 137 ++++++++++
 security/landlock/task.h                      |  50 ++++
 tools/testing/selftests/landlock/base_test.c  |   4 +-
 .../landlock/scoped_sysv_msg_queue_test.c     | 256 ++++++++++++++++++
 .../testing/selftests/landlock/scoped_test.c  |   2 +-
 14 files changed, 503 insertions(+), 10 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/scoped_sysv_msg_queue_test.c


base-commit: 9c5b83756e7b7eab35335da0d5c02a8854bbf416
-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v5 10/13] ima: Add support for flushing the hash table when staging measurements
From: Mimi Zohar @ 2026-05-21 16:06 UTC (permalink / raw)
  To: Roberto Sassu, corbet, skhan, dmitry.kasatkin, eric.snowberg,
	paul, jmorris, serge
  Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
	gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260429160319.4162918-11-roberto.sassu@huaweicloud.com>

On Wed, 2026-04-29 at 18:03 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
> 
> Introduce the new kernel option ima_flush_htable to decide whether or not
> the digests of staged measurement entries are flushed from the hash table,
> when they are deleted.

Unless explicitly requested, the existing hash table is not cleared after
exporting the measurement list. Why is clearing the hash table configurable?  
The boot command line option does not provide enough information to decide why
you would or wouldn't want to clear the hash table.  Please update the patch
description and the boot command line option.

thanks,

Mimi

> 
> When the option is enabled, replace the old hash table with a new one,
> by calling ima_alloc_replace_htable(), and completely delete the
> measurements entries.
> 
> Note: This code derives from the Alt-IMA Huawei project, whose license is
>       GPL-2.0 OR MIT.
> 
> Link: https://github.com/linux-integrity/linux/issues/1
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>

^ permalink raw reply

* Re: [PATCH v2 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: bot+bpf-ci @ 2026-05-21 16:03 UTC (permalink / raw)
  To: kpsingh, bpf, linux-security-module
  Cc: ast, daniel, memxor, kpsingh, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521152217.2345235-3-kpsingh@kernel.org>

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

> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 1b28cacc3075..5f1ed0823892 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -295,6 +295,7 @@ struct bpf_map_owner {
>
>  struct bpf_map {
>  	u8 sha[SHA256_DIGEST_SIZE];
> +	__bpf_md_ptr(char *, excl_prog_sha);
>  	const struct bpf_map_ops *ops;
>  	struct bpf_map *inner_map_meta;
>  #ifdef CONFIG_SECURITY
> @@ -335,7 +336,6 @@ struct bpf_map {
>  	atomic64_t sleepable_refcnt;
>  	s64 __percpu *elem_count;
>  	u64 cookie; /* write-once */
> -	char *excl_prog_sha;
>  };

[ ... ]

> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index 9478b8f78f26..fee35c26deb8 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -600,6 +600,22 @@ static void emit_signature_match(struct bpf_gen *gen)
>  			gen->error = -ERANGE;
>  		}
>  	}
> +
> +	/* Reject if the metadata map is not exclusive. Without exclusivity
> +	 * the cached map->sha[] verified above can be stale: another BPF
> +	 * program with map access could have mutated the contents between
> +	 * BPF_OBJ_GET_INFO_BY_FD and loader execution.
> +	 */

This isn't a bug, but the multi-line comment opening should follow the
BPF subsystem style with /* on its own line.

> +	emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
> +					 0, 0, 0, 0));
> +	emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
> +	off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> +	if (is_simm16(off)) {
> +		emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> +		emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
> +	} else {
> +		gen->error = -ERANGE;
> +	}
>  }

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
> index e2767d27d8aa..baef2e93cb41 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
> @@ -70,13 +70,15 @@ __naked void bpf_map_ptr_write_rejected(void)
>  	: __clobber_all);
>  }
>
> -/* The first element of struct bpf_map is a SHA256 hash of 32 bytes, accessing
> - * into this array is valid. The opts field is now at offset 33.
> +/* The first 32 bytes of struct bpf_map are the SHA256 hash; accessing into
> + * this array is valid. excl_prog_sha follows at offset 32 (8 bytes via
> + * __bpf_md_ptr), then ops at offset 40. Reading at offset 41 crosses into
> + * the ops pointer and must be rejected.
>   */
>  SEC("socket")
>  __description("bpf_map_ptr: read non-existent field rejected")
>  __failure
> -__msg("cannot access ptr member ops with moff 32 in struct bpf_map with off 33 size 4")
> +__msg("cannot access ptr member ops with moff 40 in struct bpf_map with off 41 size 4")
>  __failure_unpriv
>  __msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
>  __flag(BPF_F_ANY_ALIGNMENT)
> @@ -85,7 +87,7 @@ __naked void read_non_existent_field_rejected(void)
>  	asm volatile ("					\
>  	r6 = 0;						\
>  	r1 = %[map_array_48b] ll;			\
> -	r6 = *(u32*)(r1 + 33);				\
> +	r6 = *(u32*)(r1 + 41);				\
>  	r0 = 1;						\
>  	exit;						\
>  "	:


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

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

^ permalink raw reply

* Re: [PATCH v2 1/2] libbpf: fix off-by-one in emit_signature_match jump offset
From: bot+bpf-ci @ 2026-05-21 16:03 UTC (permalink / raw)
  To: kpsingh, bpf, linux-security-module
  Cc: ast, daniel, memxor, kpsingh, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521152217.2345235-2-kpsingh@kernel.org>

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

> commit 4eb2c1ff205ef1d11b97cef9c13092a8b13ba5e1
> Author: KP Singh <kpsingh@kernel.org>
>
>     libbpf: fix off-by-one in emit_signature_match jump offset
>
>     The offset for the cleanup-label jump is computed before the MOV R7
>     instruction is emitted, but the JMP lands after it. Account for the
>     extra insn in the offset calculation (-2 instead of -1). Drop the
>     redundant self-loop in the else branch; gen->error = -ERANGE already
>     marks the generation as failed.
>
>     Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
>     Signed-off-by: KP Singh <kpsingh@kernel.org>

The Fixes: tag points to commit fb2b0e290147 ("libbpf: Update light
skeleton for signing"), which does not exist in this repository. The bug was
actually introduced by commit b3beebbe6154 ("adding ci files"), which added
the gen_loader.c file containing the emit_signature_match function with the
off-by-one error in the jump offset calculation. Should the Fixes: tag be:

  Fixes: b3beebbe6154 ("adding ci files")

> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index cd5c2543f54d..9478b8f78f26 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -592,13 +592,12 @@ static void emit_signature_match(struct bpf_gen *gen)
>  		gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
>  		emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
>
> -		off =  -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
> +		off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
>  		if (is_simm16(off)) {
>  			emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
>  			emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
>  		} else {
>  			gen->error = -ERANGE;
> -			emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, -1));
>  		}
>  	}
>  }

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

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

^ permalink raw reply

* [PATCH v2 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: KP Singh @ 2026-05-21 15:22 UTC (permalink / raw)
  To: bpf, linux-security-module; +Cc: ast, daniel, memxor, KP Singh
In-Reply-To: <20260521152217.2345235-1-kpsingh@kernel.org>

The loader verifies map->sha against the metadata hash in its
instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
on the frozen map.

While the map is frozen, the loader must also ensure the map is
exclusive, as, without exclusivity, another BPF program with map access
can mutate the contents afterwards, so the check passes on stale data.

Place excl_prog_sha right after sha[] in struct bpf_map and have
gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
fixed offset 32. Declare excl_prog_sha with __bpf_md_ptr so the
8-byte BPF_LDX_MEM read works on 32-bit kernels.

Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 include/linux/bpf.h                              |  2 +-
 tools/lib/bpf/gen_loader.c                       | 16 ++++++++++++++++
 .../selftests/bpf/progs/verifier_map_ptr.c       | 10 ++++++----
 3 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index cd191c5fdb0a..ea9bd24f82c0 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -295,6 +295,7 @@ struct bpf_map_owner {
 
 struct bpf_map {
 	u8 sha[SHA256_DIGEST_SIZE];
+	__bpf_md_ptr(char *, excl_prog_sha);
 	const struct bpf_map_ops *ops;
 	struct bpf_map *inner_map_meta;
 #ifdef CONFIG_SECURITY
@@ -335,7 +336,6 @@ struct bpf_map {
 	atomic64_t sleepable_refcnt;
 	s64 __percpu *elem_count;
 	u64 cookie; /* write-once */
-	char *excl_prog_sha;
 };
 
 static inline const char *btf_field_type_name(enum btf_field_type type)
diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index 9478b8f78f26..fee35c26deb8 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -600,6 +600,22 @@ static void emit_signature_match(struct bpf_gen *gen)
 			gen->error = -ERANGE;
 		}
 	}
+
+	/* Reject if the metadata map is not exclusive. Without exclusivity
+	 * the cached map->sha[] verified above can be stale: another BPF
+	 * program with map access could have mutated the contents between
+	 * BPF_OBJ_GET_INFO_BY_FD and loader execution.
+	 */
+	emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
+					 0, 0, 0, 0));
+	emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
+	off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
+	if (is_simm16(off)) {
+		emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
+		emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
+	} else {
+		gen->error = -ERANGE;
+	}
 }
 
 void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
index e2767d27d8aa..baef2e93cb41 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
@@ -70,13 +70,15 @@ __naked void bpf_map_ptr_write_rejected(void)
 	: __clobber_all);
 }
 
-/* The first element of struct bpf_map is a SHA256 hash of 32 bytes, accessing
- * into this array is valid. The opts field is now at offset 33.
+/* The first 32 bytes of struct bpf_map are the SHA256 hash; accessing into
+ * this array is valid. excl_prog_sha follows at offset 32 (8 bytes via
+ * __bpf_md_ptr), then ops at offset 40. Reading at offset 41 crosses into
+ * the ops pointer and must be rejected.
  */
 SEC("socket")
 __description("bpf_map_ptr: read non-existent field rejected")
 __failure
-__msg("cannot access ptr member ops with moff 32 in struct bpf_map with off 33 size 4")
+__msg("cannot access ptr member ops with moff 40 in struct bpf_map with off 41 size 4")
 __failure_unpriv
 __msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
 __flag(BPF_F_ANY_ALIGNMENT)
@@ -85,7 +87,7 @@ __naked void read_non_existent_field_rejected(void)
 	asm volatile ("					\
 	r6 = 0;						\
 	r1 = %[map_array_48b] ll;			\
-	r6 = *(u32*)(r1 + 33);				\
+	r6 = *(u32*)(r1 + 41);				\
 	r0 = 1;						\
 	exit;						\
 "	:
-- 
2.53.0


^ permalink raw reply related

* [PATCH v2 1/2] libbpf: fix off-by-one in emit_signature_match jump offset
From: KP Singh @ 2026-05-21 15:22 UTC (permalink / raw)
  To: bpf, linux-security-module; +Cc: ast, daniel, memxor, KP Singh
In-Reply-To: <20260521152217.2345235-1-kpsingh@kernel.org>

The offset for the cleanup-label jump is computed before the MOV R7
instruction is emitted, but the JMP lands after it. Account for the
extra insn in the offset calculation (-2 instead of -1). Drop the
redundant self-loop in the else branch; gen->error = -ERANGE already
marks the generation as failed.

Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
 tools/lib/bpf/gen_loader.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index cd5c2543f54d..9478b8f78f26 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -592,13 +592,12 @@ static void emit_signature_match(struct bpf_gen *gen)
 		gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
 		emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
 
-		off =  -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
+		off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
 		if (is_simm16(off)) {
 			emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
 			emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
 		} else {
 			gen->error = -ERANGE;
-			emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, -1));
 		}
 	}
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v2 0/2] gen_loader fixes
From: KP Singh @ 2026-05-21 15:22 UTC (permalink / raw)
  To: bpf, linux-security-module; +Cc: ast, daniel, memxor

  Fix off-by-one in the signed loader's cleanup jump and reject
  non-exclusive metadata maps.

  Patch 1 fixes a pre-existing bug: the cleanup-label offset in
  emit_signature_match() does not account for the MOV insn emitted
  before the JMP.

  Patch 2 adds an exclusivity check so the loader bails if the
  metadata map lacks excl_prog_hash, preventing stale-hash attacks
  from concurrent map writers.

KP Singh (2):
  libbpf: fix off-by-one in emit_signature_match jump offset
  bpf, libbpf: reject non-exclusive metadata maps in the signed loader

 include/linux/bpf.h                           |  2 +-
 tools/lib/bpf/gen_loader.c                    | 19 +++++++++++++++++--
 .../selftests/bpf/progs/verifier_map_ptr.c    | 10 ++++++----
 3 files changed, 24 insertions(+), 7 deletions(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v5 09/13] ima: Add support for staging measurements with prompt
From: Mimi Zohar @ 2026-05-21 15:18 UTC (permalink / raw)
  To: Roberto Sassu, corbet, skhan, dmitry.kasatkin, eric.snowberg,
	paul, jmorris, serge
  Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
	gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260429160319.4162918-10-roberto.sassu@huaweicloud.com>

On Wed, 2026-04-29 at 18:03 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
> 
> Introduce the ability of staging the IMA measurement list and deleting them
> with a prompt.
> 
> Staging means moving the current content of the measurement list to a

-> moving the current measurement list records ...

> separate location, and allowing users to read and delete it. This causes
> the measurement list to be atomically truncated before new measurements can
> be added. 

The wording is a bit off - "before new measurements can be added".  One of the
main objectives of staging the measurement list is to allow new measurement
records to continue to be added to the measurement list, while the staged
measurements are exported.

> Staging can be done only once at a time. In the event of kexec(),
> staging is reverted and staged entries will be carried over to the new
> kernel.
> 
> Introduce ascii_runtime_measurements_<algo>_staged and
> binary_runtime_measurements_<algo>_staged interfaces to access and delete
> the measurements. Also, add write permission to the original measurement
> interfaces.

Wondering if adding "write" permission to the original measurement interface
will change based on your 9/13 comment.

The patch, like others in this patch set, are well written.  There are a couple
of inline comments.  I'll defer reviewing the rest of this patch to v6.

> 
> Use 'echo A > <IMA original interface>' and
> 'echo D > <IMA _staged interface>' to respectively stage and delete the
> entire measurements list. Locking of these interfaces is also mediated with
> a call to _ima_measurements_open() and with ima_measurements_release().
> 
> Implement the staging functionality by introducing the new global
> measurements list ima_measurements_staged, and ima_queue_stage() and
> ima_queue_staged_delete_all() to respectively move measurements from the
> current measurements list to the staged one, and to move staged
> measurements to the ima_measurements_trim list for deletion. Introduce
> ima_queue_delete() to delete the measurements.
> 
> Finally, introduce the BINARY_STAGED and BINARY_FULL binary measurements
> list types, to maintain the counters and the binary size of staged
> measurements and the full measurements list (including entries that were
> staged). BINARY still represents the current binary measurements list.
> 
> Use the binary size for the BINARY + BINARY_STAGED types in
> ima_add_kexec_buffer(), since both measurements list types are copied to
> the secondary kernel during kexec. Use BINARY_FULL in
> ima_measure_kexec_event(), to generate a critical data record.
> 
> It should be noted that the BINARY_FULL counter is not passed through
> kexec. Thus, the number of entries included in the kexec critical data
> records refers to the entries since the previous kexec records.
> 
> Note: This code derives from the Alt-IMA Huawei project, whose license is
>       GPL-2.0 OR MIT.
> 
> Link: https://github.com/linux-integrity/linux/issues/1
> Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
>  security/integrity/ima/Kconfig     |  13 +++
>  security/integrity/ima/ima.h       |   8 +-
>  security/integrity/ima/ima_fs.c    | 181 ++++++++++++++++++++++++++---
>  security/integrity/ima/ima_kexec.c |  24 +++-
>  security/integrity/ima/ima_queue.c |  97 +++++++++++++++-
>  5 files changed, 302 insertions(+), 21 deletions(-)
> 
> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> index 862fbee2b174..48c906793efb 100644
> --- a/security/integrity/ima/Kconfig
> +++ b/security/integrity/ima/Kconfig
> @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
>  	  If set to the default value of 0, an extra half page of memory for those
>  	  additional measurements will be allocated.
>  
> +config IMA_STAGING
> +	bool "Support for staging the measurements list"
> +	default y

Exporting and deleting the IMA measurement list carries an inherent security
risk: if the measurements are not durably stored before deletion, they are
permanently lost. Deletion should be treated as experimental until a trusted
service exists to guarantee safe storage.

Please change the default to 'n'.


> +	help
> +	  Add support for staging the measurements list.
> +
> +	  It allows user space to stage the measurements list for deletion and
> +	  to delete the staged measurements after confirmation.
> +
> +	  On kexec, staging is reverted and staged measurements are prepended

-> staging is aborted and any staged measurement records are .copied ..

> +	  to the current measurements list when measurements are copied to the
> +	  secondary kernel.
> +
>  endif

Mimi

^ permalink raw reply

* [PATCH v2] apparmor: Fix inverted comparison in cache_hold_inc()
From: Eduardo Vasconcelos @ 2026-05-21 15:13 UTC (permalink / raw)
  To: john.johansen, paul, jmorris, serge
  Cc: Eduardo Vasconcelos, apparmor, linux-security-module,
	linux-kernel

cache_hold_inc() prevents the per-CPU cache hold counter from
rising above MAX_HOLD_COUNT, but the comparison is inverted
(> MAX_HOLD_COUNT instead of <), so the counter never rises
above 0.

This breaks the cache mechanism because since the hold counter
is always 0, the global pool is always attempted first before
falling back to the local cache. The decrement also never occurs,
thus the hold counter is effectively dead.

Fix by changing > to < in cache_hold_inc().

Fixes: 0b6a6b72b329 ("apparmor: document the buffer hold, add an overflow guard")
Signed-off-by: Eduardo Vasconcelos <eduardo@eduardovasconcelos.com>
---
Changes in v2:
- Add Fixes: tag
- Link fo v1: https://lore.kernel.org/all/20260521065731.6888-1-eduardo@eduardovasconcelos.com/

 security/apparmor/lsm.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3491e9f60194..b7c19805a216 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -2129,7 +2129,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
  */
 static void cache_hold_inc(unsigned int *hold)
 {
-	if (*hold > MAX_HOLD_COUNT)
+	if (*hold < MAX_HOLD_COUNT)
 		(*hold)++;
 }
 
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH 04/11] treewide: Convert struct kernel_param_ops initializers to DEFINE_KERNEL_PARAM_OPS
From: Sean Christopherson @ 2026-05-21 13:59 UTC (permalink / raw)
  To: Kees Cook
  Cc: Luis Chamberlain, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Paolo Bonzini, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
	Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
	Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
	John Johansen, Paul Moore, James Morris, Serge E. Hallyn,
	Andy Shevchenko, Georgia Garcia, kvm, dmaengine, linux-modules,
	kasan-dev, linux-mm, apparmor, linux-security-module, linux-um,
	linux-acpi, openipmi-developer, qemu-devel, intel-gfx, dri-devel,
	linux-rdma, linux-media, linux-pci, linux-scsi, linux-pm,
	linuxppc-dev, linux-serial, linux-usb, usb-storage,
	virtualization, linux-kernel, linux-arch, netdev, linux-fsdevel,
	linux-hardening
In-Reply-To: <20260521133326.2465264-4-kees@kernel.org>

On Thu, May 21, 2026, Kees Cook wrote:
> Using Coccinelle, rewrite every struct kernel_param_ops initializer that
> sets .get into a DEFINE_KERNEL_PARAM_OPS-family macro invocation,
> for example:
> 
> @@
> declarer name DEFINE_KERNEL_PARAM_OPS;
> identifier OPS;
> expression SET, GET;
> @@
> - const struct kernel_param_ops OPS = {
> -       .set = SET,
> -       .get = GET,
> - };
> + DEFINE_KERNEL_PARAM_OPS(OPS, SET, GET);

IMO, "OPS, GET, SET" is more intuitive, especially since that's the order used
by DEFINE_SIMPLE_ATTRIBUTE and DEFINE_DEBUGFS_ATTRIBUTE.

^ permalink raw reply

* Re: [PATCH 09/11] treewide: Convert custom kernel_param_ops .get callbacks to seq_buf via cocci
From: Sean Christopherson @ 2026-05-21 13:45 UTC (permalink / raw)
  To: Kees Cook
  Cc: Luis Chamberlain, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Paolo Bonzini, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
	Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
	Alexander Potapenko, Marco Elver, Dmitry Vyukov, Andrew Morton,
	John Johansen, Paul Moore, James Morris, Serge E. Hallyn,
	Andy Shevchenko, Georgia Garcia, kvm, dmaengine, linux-modules,
	kasan-dev, linux-mm, apparmor, linux-security-module, linux-um,
	linux-acpi, openipmi-developer, qemu-devel, intel-gfx, dri-devel,
	linux-rdma, linux-media, linux-pci, linux-scsi, linux-pm,
	linuxppc-dev, linux-serial, linux-usb, usb-storage,
	virtualization, linux-kernel, linux-arch, netdev, linux-fsdevel,
	linux-hardening
In-Reply-To: <20260521133326.2465264-9-kees@kernel.org>

On Thu, May 21, 2026, Kees Cook wrote:
> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index 07f4c7209ac0..00317774a90b 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -368,12 +368,16 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
>  	return ret;
>  }
>  
> -static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
> +static int vmentry_l1d_flush_get(struct seq_buf *s,
> +				 const struct kernel_param *kp)
>  {
> -	if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param)))
> -		return sysfs_emit(s, "???\n");
> +	if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param))) {
> +		seq_buf_printf(s, "???\n");
> +		return 0;
> +	}
>  
> -	return sysfs_emit(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
> +	seq_buf_printf(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
> +	return 0;

For this one, can you manually change it to this?

	if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param)))
		seq_buf_printf(s, "???\n");
	else
		seq_buf_printf(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
	return 0;

>  }
>  
>  /*
> @@ -459,9 +463,11 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
>  	pr_warn_once("Kernel compiled without mitigations, ignoring vmentry_l1d_flush\n");
>  	return 0;
>  }
> -static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
> +static int vmentry_l1d_flush_get(struct seq_buf *s,
> +				 const struct kernel_param *kp)
>  {
> -	return sysfs_emit(s, "never\n");
> +	seq_buf_printf(s, "never\n");
> +	return 0;
>  }
>  #endif

^ permalink raw reply

* [PATCH 11/11] moduleparam: Drop legacy kernel_param_ops .get_str field and dispatch logic
From: Kees Cook @ 2026-05-21 13:33 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133315.work.845-kees@kernel.org>

All struct kernel_param_ops .get callbacks have been migrated to using
struct seq_buf. Drop the migration scaffolding.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 include/linux/moduleparam.h | 37 ++--------------------
 kernel/params.c             | 62 ++++++++++---------------------------
 2 files changed, 20 insertions(+), 79 deletions(-)

diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
index 38acb5aef56b..e6af6f051c93 100644
--- a/include/linux/moduleparam.h
+++ b/include/linux/moduleparam.h
@@ -66,15 +66,8 @@ struct kernel_param_ops {
 	/*
 	 * Format the parameter's value into @s.  Return 0 on success
 	 * (length derived from seq_buf_used()) or -errno on error.
-	 * Exactly one of .get and .get_str should be set; the dispatcher
-	 * WARNs and prefers .get if both are.
 	 */
 	int (*get)(struct seq_buf *s, const struct kernel_param *kp);
-	/*
-	 * Returns length written or -errno.  Buffer is 4k (ie. be short!).
-	 * Deprecated: callbacks should implement .get instead.
-	 */
-	int (*get_str)(char *buffer, const struct kernel_param *kp);
 	/* Optional function to free kp->arg when module unloaded. */
 	void (*free)(void *arg);
 };
@@ -84,33 +77,11 @@ struct kernel_param_ops {
  * any required visibility qualifiers (typically "static"):
  *
  *   static DEFINE_KERNEL_PARAM_OPS(my_ops, my_set, my_get);
- *
- * @_get may be either of:
- *   int (*)(struct seq_buf *, const struct kernel_param *) (seq_buf)
- *   int (*)(char *, const struct kernel_param *)           (legacy)
- *
- * The macro uses _Generic to route the function pointer to the
- * matching field (.get or .get_str) at compile time, leaving the
- * other field NULL. Each helper matches the wrong prototype signature
- * and returns NULL, falling through to the default branch otherwise;
- * if @_get has neither expected signature the assignment to the
- * fields gets a normal compile-time type-mismatch error.
  */
-#define _KERNEL_PARAM_OPS_GET(_get)					\
-	_Generic((_get),						\
-	    int (*)(char *, const struct kernel_param *): NULL,		\
-	    default: (_get))
-
-#define _KERNEL_PARAM_OPS_GET_STR(_get)					\
-	_Generic((_get),						\
-	    int (*)(struct seq_buf *, const struct kernel_param *): NULL, \
-	    default: (_get))
-
 #define DEFINE_KERNEL_PARAM_OPS(_name, _set, _get)			\
 	const struct kernel_param_ops _name = {				\
 		.set = (_set),						\
-		.get = _KERNEL_PARAM_OPS_GET(_get),			\
-		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
+		.get = (_get),						\
 	}
 
 /* As DEFINE_KERNEL_PARAM_OPS, with KERNEL_PARAM_OPS_FL_NOARG set. */
@@ -118,16 +89,14 @@ struct kernel_param_ops {
 	const struct kernel_param_ops _name = {				\
 		.flags = KERNEL_PARAM_OPS_FL_NOARG,			\
 		.set = (_set),						\
-		.get = _KERNEL_PARAM_OPS_GET(_get),			\
-		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
+		.get = (_get),						\
 	}
 
 /* As DEFINE_KERNEL_PARAM_OPS, with an additional .free callback. */
 #define DEFINE_KERNEL_PARAM_OPS_FREE(_name, _set, _get, _free)		\
 	const struct kernel_param_ops _name = {				\
 		.set = (_set),						\
-		.get = _KERNEL_PARAM_OPS_GET(_get),			\
-		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
+		.get = (_get),						\
 		.free = (_free),					\
 	}
 
diff --git a/kernel/params.c b/kernel/params.c
index 25f0c8d5d19f..6b410189297b 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -461,8 +461,7 @@ static int param_array_get(struct seq_buf *s, const struct kernel_param *kp)
 {
 	const struct kparam_array *arr = kp->arr;
 	struct kernel_param p = *kp;
-	char *elem_buf = NULL;
-	int i, ret = 0;
+	int i, ret;
 
 	for (i = 0; i < (arr->num ? *arr->num : arr->max); i++) {
 		size_t before = s->len;
@@ -470,23 +469,9 @@ static int param_array_get(struct seq_buf *s, const struct kernel_param *kp)
 		p.arg = arr->elem + arr->elemsize * i;
 		check_kparam_locked(p.mod);
 
-		if (arr->ops->get) {
-			ret = arr->ops->get(s, &p);
-			if (ret < 0)
-				goto out;
-		} else {
-			if (!elem_buf) {
-				elem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
-				if (!elem_buf) {
-					ret = -ENOMEM;
-					goto out;
-				}
-			}
-			ret = arr->ops->get_str(elem_buf, &p);
-			if (ret < 0)
-				goto out;
-			seq_buf_putmem(s, elem_buf, ret);
-		}
+		ret = arr->ops->get(s, &p);
+		if (ret < 0)
+			return ret;
 
 		/* Nothing got written (e.g. overflow) — stop. */
 		if (s->len == before)
@@ -496,10 +481,7 @@ static int param_array_get(struct seq_buf *s, const struct kernel_param *kp)
 		if (i && s->buffer[before - 1] == '\n')
 			s->buffer[before - 1] = ',';
 	}
-	ret = 0;
-out:
-	kfree(elem_buf);
-	return ret;
+	return 0;
 }
 
 static void param_array_free(void *arg)
@@ -570,32 +552,22 @@ static ssize_t param_attr_show(const struct module_attribute *mattr,
 	int count;
 	const struct param_attribute *attribute = to_param_attr(mattr);
 	const struct kernel_param_ops *ops = attribute->param->ops;
+	struct seq_buf s;
 
-	if (!ops->get && !ops->get_str)
+	if (!ops->get)
 		return -EPERM;
 
-	WARN_ON_ONCE(ops->get && ops->get_str);
-
 	kernel_param_lock(mk->mod);
-	if (ops->get) {
-		struct seq_buf s;
-
-		seq_buf_init(&s, buf, PAGE_SIZE);
-		count = ops->get(&s, attribute->param);
-		if (count >= 0) {
-			WARN_ON_ONCE(count > 0);
-			count = seq_buf_used(&s);
-			/* Make sure string is terminated. */
-			seq_buf_str(&s);
-			/*
-			 * If overflowed, reduce count by 1 for trailing
-			 * NUL byte.
-			 */
-			if (seq_buf_has_overflowed(&s))
-				count--;
-		}
-	} else {
-		count = ops->get_str(buf, attribute->param);
+	seq_buf_init(&s, buf, PAGE_SIZE);
+	count = ops->get(&s, attribute->param);
+	if (count >= 0) {
+		WARN_ON_ONCE(count > 0);
+		count = seq_buf_used(&s);
+		/* Make sure string is terminated. */
+		seq_buf_str(&s);
+		/* If overflowed, reduce count by 1 for trailing NUL byte. */
+		if (seq_buf_has_overflowed(&s))
+			count--;
 	}
 	kernel_param_unlock(mk->mod);
 	return count;
-- 
2.34.1


^ permalink raw reply related

* [PATCH 09/11] treewide: Convert custom kernel_param_ops .get callbacks to seq_buf via cocci
From: Kees Cook @ 2026-05-21 13:33 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133315.work.845-kees@kernel.org>

Using the following Coccinelle script, convert struct kernel_param_ops
.get callbacks from "char *" to "struct seq_buf *" when the only write
to the buffer is via a final call of scnprintf(), snprintf(), sprintf(),
or sysfs_emit().

Since seq_buf_printf() will return -1 on overflow, and struct
kernel_param_ops .get callbacks are expected to truncate without error,
we must ignore the return value from seq_buf_print() and always return 0
(as the length is calculated in the common dispatcher code).

@@
identifier FN, BUF, KP;
expression FMT;
expression list ARGS;
@@
 int FN(
-               char *BUF
+               struct seq_buf *BUF
                , const struct kernel_param *KP)
 {
        ... when any
(
-       return scnprintf(BUF, PAGE_SIZE, FMT, ARGS);
|
-       return snprintf(BUF, PAGE_SIZE, FMT, ARGS);
|
-       return sprintf(BUF, FMT, ARGS);
|
-       return sysfs_emit(BUF, FMT, ARGS);
)
+       seq_buf_printf(BUF, FMT, ARGS);
+       return 0;
 }

No struct kernel_param_ops initializations need changing since
DEFINE_KERNEL_PARAM_OPS already routes the pointer to .get or .get_str
via _Generic based on the function signature, so converted callbacks
are automatically moved from the .get_str to the .get callback.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 arch/s390/kernel/perf_cpum_sf.c               |  6 ++-
 arch/x86/kernel/msr.c                         |  5 +-
 arch/x86/kvm/vmx/vmx.c                        | 18 ++++---
 arch/x86/platform/uv/uv_nmi.c                 | 12 +++--
 drivers/acpi/ec.c                             | 14 ++++--
 drivers/acpi/sysfs.c                          |  6 ++-
 drivers/block/ublk_drv.c                      |  5 +-
 drivers/char/ipmi/ipmi_msghandler.c           |  6 ++-
 drivers/firmware/qcom/qcom_scm.c              | 12 +++--
 drivers/gpu/drm/drm_panic.c                   |  7 +--
 drivers/infiniband/hw/hfi1/driver.c           |  7 +--
 drivers/infiniband/ulp/srpt/ib_srpt.c         |  5 +-
 drivers/input/misc/ati_remote2.c              | 10 ++--
 drivers/input/mouse/psmouse-base.c            |  9 ++--
 drivers/md/md.c                               |  5 +-
 drivers/media/pci/tw686x/tw686x-core.c        |  6 ++-
 drivers/nvme/host/multipath.c                 |  5 +-
 drivers/power/supply/test_power.c             | 47 +++++++++++--------
 drivers/target/target_core_user.c             | 12 +++--
 .../processor_thermal_soc_slider.c            | 12 +++--
 drivers/ufs/core/ufs-fault-injection.c        |  7 +--
 drivers/vhost/scsi.c                          |  5 +-
 fs/nfs/namespace.c                            |  6 ++-
 fs/ocfs2/dlmfs/dlmfs.c                        |  5 +-
 fs/overlayfs/copy_up.c                        |  5 +-
 kernel/locking/locktorture.c                  |  6 ++-
 kernel/rcu/tree.c                             |  6 ++-
 kernel/workqueue.c                            |  6 ++-
 lib/test_dynamic_debug.c                      |  6 ++-
 mm/damon/lru_sort.c                           | 14 +++---
 mm/damon/reclaim.c                            | 14 +++---
 mm/damon/stat.c                               | 10 ++--
 mm/memory_hotplug.c                           | 18 ++++---
 net/ceph/ceph_common.c                        |  5 +-
 net/sunrpc/auth.c                             |  6 ++-
 net/sunrpc/svc.c                              |  5 +-
 security/apparmor/lsm.c                       | 16 ++++---
 37 files changed, 218 insertions(+), 131 deletions(-)

diff --git a/arch/s390/kernel/perf_cpum_sf.c b/arch/s390/kernel/perf_cpum_sf.c
index 76119542562b..75b0d441d238 100644
--- a/arch/s390/kernel/perf_cpum_sf.c
+++ b/arch/s390/kernel/perf_cpum_sf.c
@@ -1991,11 +1991,13 @@ static int s390_pmu_sf_offline_cpu(unsigned int cpu)
 	return cpusf_pmu_setup(cpu, PMC_RELEASE);
 }
 
-static int param_get_sfb_size(char *buffer, const struct kernel_param *kp)
+static int param_get_sfb_size(struct seq_buf *buffer,
+			      const struct kernel_param *kp)
 {
 	if (!cpum_sf_avail())
 		return -ENODEV;
-	return sprintf(buffer, "%lu,%lu", CPUM_SF_MIN_SDB, CPUM_SF_MAX_SDB);
+	seq_buf_printf(buffer, "%lu,%lu", CPUM_SF_MIN_SDB, CPUM_SF_MAX_SDB);
+	return 0;
 }
 
 static int param_set_sfb_size(const char *val, const struct kernel_param *kp)
diff --git a/arch/x86/kernel/msr.c b/arch/x86/kernel/msr.c
index 5f4e1814dc4d..9f07f66c3cfc 100644
--- a/arch/x86/kernel/msr.c
+++ b/arch/x86/kernel/msr.c
@@ -309,7 +309,7 @@ static int set_allow_writes(const char *val, const struct kernel_param *cp)
 	return 0;
 }
 
-static int get_allow_writes(char *buf, const struct kernel_param *kp)
+static int get_allow_writes(struct seq_buf *buf, const struct kernel_param *kp)
 {
 	const char *res;
 
@@ -319,7 +319,8 @@ static int get_allow_writes(char *buf, const struct kernel_param *kp)
 	default: res = "default"; break;
 	}
 
-	return sprintf(buf, "%s\n", res);
+	seq_buf_printf(buf, "%s\n", res);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(allow_writes_ops, set_allow_writes,
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 07f4c7209ac0..00317774a90b 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -368,12 +368,16 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
 	return ret;
 }
 
-static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
+static int vmentry_l1d_flush_get(struct seq_buf *s,
+				 const struct kernel_param *kp)
 {
-	if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param)))
-		return sysfs_emit(s, "???\n");
+	if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param))) {
+		seq_buf_printf(s, "???\n");
+		return 0;
+	}
 
-	return sysfs_emit(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
+	seq_buf_printf(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
+	return 0;
 }
 
 /*
@@ -459,9 +463,11 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
 	pr_warn_once("Kernel compiled without mitigations, ignoring vmentry_l1d_flush\n");
 	return 0;
 }
-static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
+static int vmentry_l1d_flush_get(struct seq_buf *s,
+				 const struct kernel_param *kp)
 {
-	return sysfs_emit(s, "never\n");
+	seq_buf_printf(s, "never\n");
+	return 0;
 }
 #endif
 
diff --git a/arch/x86/platform/uv/uv_nmi.c b/arch/x86/platform/uv/uv_nmi.c
index a7ac80b5f8d9..c401369efe22 100644
--- a/arch/x86/platform/uv/uv_nmi.c
+++ b/arch/x86/platform/uv/uv_nmi.c
@@ -111,9 +111,11 @@ module_param_named(dump_loglevel, uv_nmi_loglevel, int, 0644);
  * The following values show statistics on how perf events are affecting
  * this system.
  */
-static int param_get_local64(char *buffer, const struct kernel_param *kp)
+static int param_get_local64(struct seq_buf *buffer,
+			     const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%lu\n", local64_read((local64_t *)kp->arg));
+	seq_buf_printf(buffer, "%lu\n", local64_read((local64_t *)kp->arg));
+	return 0;
 }
 
 static int param_set_local64(const char *val, const struct kernel_param *kp)
@@ -207,9 +209,11 @@ static const char * const actions_desc[nmi_act_max] = {
 
 static enum action_t uv_nmi_action = nmi_act_dump;
 
-static int param_get_action(char *buffer, const struct kernel_param *kp)
+static int param_get_action(struct seq_buf *buffer,
+			    const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n", actions[uv_nmi_action]);
+	seq_buf_printf(buffer, "%s\n", actions[uv_nmi_action]);
+	return 0;
 }
 
 static int param_set_action(const char *val, const struct kernel_param *kp)
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index 45204538ed87..6478e5290faf 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -2236,18 +2236,22 @@ static int param_set_event_clearing(const char *val,
 	return result;
 }
 
-static int param_get_event_clearing(char *buffer,
+static int param_get_event_clearing(struct seq_buf *buffer,
 				    const struct kernel_param *kp)
 {
 	switch (ec_event_clearing) {
 	case ACPI_EC_EVT_TIMING_STATUS:
-		return sprintf(buffer, "status\n");
+		seq_buf_printf(buffer, "status\n");
+		return 0;
 	case ACPI_EC_EVT_TIMING_QUERY:
-		return sprintf(buffer, "query\n");
+		seq_buf_printf(buffer, "query\n");
+		return 0;
 	case ACPI_EC_EVT_TIMING_EVENT:
-		return sprintf(buffer, "event\n");
+		seq_buf_printf(buffer, "event\n");
+		return 0;
 	default:
-		return sprintf(buffer, "invalid\n");
+		seq_buf_printf(buffer, "invalid\n");
+		return 0;
 	}
 	return 0;
 }
diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
index 3d32a5280432..5247ed7e05cc 100644
--- a/drivers/acpi/sysfs.c
+++ b/drivers/acpi/sysfs.c
@@ -192,9 +192,11 @@ static int param_set_trace_method_name(const char *val,
 	return 0;
 }
 
-static int param_get_trace_method_name(char *buffer, const struct kernel_param *kp)
+static int param_get_trace_method_name(struct seq_buf *buffer,
+				       const struct kernel_param *kp)
 {
-	return sysfs_emit(buffer, "%s\n", acpi_gbl_trace_method_name);
+	seq_buf_printf(buffer, "%s\n", acpi_gbl_trace_method_name);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(param_ops_trace_method,
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index f7bf7ea2d088..ea35662381bf 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -5868,10 +5868,11 @@ static int ublk_set_max_unprivileged_ublks(const char *buf,
 	return param_set_uint_minmax(buf, kp, 0, UBLK_MAX_UBLKS);
 }
 
-static int ublk_get_max_unprivileged_ublks(char *buf,
+static int ublk_get_max_unprivileged_ublks(struct seq_buf *buf,
 					   const struct kernel_param *kp)
 {
-	return sysfs_emit(buf, "%u\n", unprivileged_ublks_max);
+	seq_buf_printf(buf, "%u\n", unprivileged_ublks_max);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(ublk_max_unprivileged_ublks_ops,
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
index b5fed11707e8..45941605b88f 100644
--- a/drivers/char/ipmi/ipmi_msghandler.c
+++ b/drivers/char/ipmi/ipmi_msghandler.c
@@ -90,7 +90,8 @@ static int panic_op_write_handler(const char *val,
 	return 0;
 }
 
-static int panic_op_read_handler(char *buffer, const struct kernel_param *kp)
+static int panic_op_read_handler(struct seq_buf *buffer,
+				 const struct kernel_param *kp)
 {
 	const char *event_str;
 
@@ -99,7 +100,8 @@ static int panic_op_read_handler(char *buffer, const struct kernel_param *kp)
 	else
 		event_str = ipmi_panic_event_str[ipmi_send_panic_event];
 
-	return sprintf(buffer, "%s\n", event_str);
+	seq_buf_printf(buffer, "%s\n", event_str);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(panic_op_ops, panic_op_write_handler,
diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index ef57df53e087..1bdb497e354e 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -2694,12 +2694,16 @@ static irqreturn_t qcom_scm_irq_handler(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
-static int get_download_mode(char *buffer, const struct kernel_param *kp)
+static int get_download_mode(struct seq_buf *buffer,
+			     const struct kernel_param *kp)
 {
-	if (download_mode >= ARRAY_SIZE(download_mode_name))
-		return sysfs_emit(buffer, "unknown mode\n");
+	if (download_mode >= ARRAY_SIZE(download_mode_name)) {
+		seq_buf_printf(buffer, "unknown mode\n");
+		return 0;
+	}
 
-	return sysfs_emit(buffer, "%s\n", download_mode_name[download_mode]);
+	seq_buf_printf(buffer, "%s\n", download_mode_name[download_mode]);
+	return 0;
 }
 
 static int set_download_mode(const char *val, const struct kernel_param *kp)
diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
index c35d1adf2ce3..8b3b749284f0 100644
--- a/drivers/gpu/drm/drm_panic.c
+++ b/drivers/gpu/drm/drm_panic.c
@@ -841,10 +841,11 @@ static int drm_panic_type_set(const char *val, const struct kernel_param *kp)
 	return -EINVAL;
 }
 
-static int drm_panic_type_get(char *buffer, const struct kernel_param *kp)
+static int drm_panic_type_get(struct seq_buf *buffer,
+			      const struct kernel_param *kp)
 {
-	return scnprintf(buffer, PAGE_SIZE, "%s\n",
-			 drm_panic_type_map[drm_panic_type]);
+	seq_buf_printf(buffer, "%s\n", drm_panic_type_map[drm_panic_type]);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(drm_panic_ops, drm_panic_type_set,
diff --git a/drivers/infiniband/hw/hfi1/driver.c b/drivers/infiniband/hw/hfi1/driver.c
index 5b9b0b38b419..3c3f8d4db99d 100644
--- a/drivers/infiniband/hw/hfi1/driver.c
+++ b/drivers/infiniband/hw/hfi1/driver.c
@@ -41,7 +41,7 @@ MODULE_PARM_DESC(cu, "Credit return units");
 
 unsigned long hfi1_cap_mask = HFI1_CAP_MASK_DEFAULT;
 static int hfi1_caps_set(const char *val, const struct kernel_param *kp);
-static int hfi1_caps_get(char *buffer, const struct kernel_param *kp);
+static int hfi1_caps_get(struct seq_buf *buffer, const struct kernel_param *kp);
 static DEFINE_KERNEL_PARAM_OPS(cap_ops, hfi1_caps_set, hfi1_caps_get);
 module_param_cb(cap_mask, &cap_ops, &hfi1_cap_mask, S_IWUSR | S_IRUGO);
 MODULE_PARM_DESC(cap_mask, "Bit mask of enabled/disabled HW features");
@@ -101,14 +101,15 @@ static int hfi1_caps_set(const char *val, const struct kernel_param *kp)
 	return ret;
 }
 
-static int hfi1_caps_get(char *buffer, const struct kernel_param *kp)
+static int hfi1_caps_get(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	unsigned long cap_mask = *(unsigned long *)kp->arg;
 
 	cap_mask &= ~HFI1_CAP_LOCKED_SMASK;
 	cap_mask |= ((cap_mask & HFI1_CAP_K2U) << HFI1_CAP_USER_SHIFT);
 
-	return sysfs_emit(buffer, "0x%lx\n", cap_mask);
+	seq_buf_printf(buffer, "0x%lx\n", cap_mask);
+	return 0;
 }
 
 struct pci_dev *get_pci_dev(struct rvt_dev_info *rdi)
diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c
index 9aec5d80117f..97c77d52a86a 100644
--- a/drivers/infiniband/ulp/srpt/ib_srpt.c
+++ b/drivers/infiniband/ulp/srpt/ib_srpt.c
@@ -86,9 +86,10 @@ static int srpt_set_u64_x(const char *buffer, const struct kernel_param *kp)
 {
 	return kstrtou64(buffer, 16, (u64 *)kp->arg);
 }
-static int srpt_get_u64_x(char *buffer, const struct kernel_param *kp)
+static int srpt_get_u64_x(struct seq_buf *buffer, const struct kernel_param *kp)
 {
-	return sprintf(buffer, "0x%016llx\n", *(u64 *)kp->arg);
+	seq_buf_printf(buffer, "0x%016llx\n", *(u64 *)kp->arg);
+	return 0;
 }
 module_param_call(srpt_service_guid, srpt_set_u64_x, srpt_get_u64_x,
 		  &srpt_service_guid, 0444);
diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c
index 8b4ef7e163d3..d101fe1c2c4c 100644
--- a/drivers/input/misc/ati_remote2.c
+++ b/drivers/input/misc/ati_remote2.c
@@ -63,12 +63,13 @@ static int ati_remote2_set_channel_mask(const char *val,
 	return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_CHANNEL_MASK);
 }
 
-static int ati_remote2_get_channel_mask(char *buffer,
+static int ati_remote2_get_channel_mask(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
 	pr_debug("%s()\n", __func__);
 
-	return sprintf(buffer, "0x%04x\n", *(unsigned int *)kp->arg);
+	seq_buf_printf(buffer, "0x%04x\n", *(unsigned int *)kp->arg);
+	return 0;
 }
 
 static int ati_remote2_set_mode_mask(const char *val,
@@ -79,12 +80,13 @@ static int ati_remote2_set_mode_mask(const char *val,
 	return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_MODE_MASK);
 }
 
-static int ati_remote2_get_mode_mask(char *buffer,
+static int ati_remote2_get_mode_mask(struct seq_buf *buffer,
 				     const struct kernel_param *kp)
 {
 	pr_debug("%s()\n", __func__);
 
-	return sprintf(buffer, "0x%02x\n", *(unsigned int *)kp->arg);
+	seq_buf_printf(buffer, "0x%02x\n", *(unsigned int *)kp->arg);
+	return 0;
 }
 
 static unsigned int channel_mask = ATI_REMOTE2_MAX_CHANNEL_MASK;
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index f9ebb1fd0b6f..39a9b87e69d1 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -44,7 +44,8 @@ MODULE_LICENSE("GPL");
 
 static unsigned int psmouse_max_proto = PSMOUSE_AUTO;
 static int psmouse_set_maxproto(const char *val, const struct kernel_param *);
-static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp);
+static int psmouse_get_maxproto(struct seq_buf *buffer,
+				const struct kernel_param *kp);
 static DEFINE_KERNEL_PARAM_OPS(param_ops_proto_abbrev, psmouse_set_maxproto,
 			       psmouse_get_maxproto);
 #define param_check_proto_abbrev(name, p)	__param_check(name, p, unsigned int)
@@ -1994,11 +1995,13 @@ static int psmouse_set_maxproto(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp)
+static int psmouse_get_maxproto(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
 	int type = *((unsigned int *)kp->arg);
 
-	return sprintf(buffer, "%s\n", psmouse_protocol_by_type(type)->name);
+	seq_buf_printf(buffer, "%s\n", psmouse_protocol_by_type(type)->name);
+	return 0;
 }
 
 static int __init psmouse_init(void)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 8b568eee8743..ce3eb1396ad0 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -10989,9 +10989,10 @@ static __exit void md_exit(void)
 subsys_initcall(md_init);
 module_exit(md_exit)
 
-static int get_ro(char *buffer, const struct kernel_param *kp)
+static int get_ro(struct seq_buf *buffer, const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%d\n", start_readonly);
+	seq_buf_printf(buffer, "%d\n", start_readonly);
+	return 0;
 }
 static int set_ro(const char *val, const struct kernel_param *kp)
 {
diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c
index a10e38221817..35a6ff8d77fc 100644
--- a/drivers/media/pci/tw686x/tw686x-core.c
+++ b/drivers/media/pci/tw686x/tw686x-core.c
@@ -69,9 +69,11 @@ static const char *dma_mode_name(unsigned int mode)
 	}
 }
 
-static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp)
+static int tw686x_dma_mode_get(struct seq_buf *buffer,
+			       const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s", dma_mode_name(dma_mode));
+	seq_buf_printf(buffer, "%s", dma_mode_name(dma_mode));
+	return 0;
 }
 
 static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp)
diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
index f7362377e427..e0c87447074d 100644
--- a/drivers/nvme/host/multipath.c
+++ b/drivers/nvme/host/multipath.c
@@ -85,9 +85,10 @@ static int nvme_set_iopolicy(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int nvme_get_iopolicy(char *buf, const struct kernel_param *kp)
+static int nvme_get_iopolicy(struct seq_buf *buf, const struct kernel_param *kp)
 {
-	return sprintf(buf, "%s\n", nvme_iopolicy_names[iopolicy]);
+	seq_buf_printf(buf, "%s\n", nvme_iopolicy_names[iopolicy]);
+	return 0;
 }
 
 module_param_call(iopolicy, nvme_set_iopolicy, nvme_get_iopolicy,
diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c
index 0bf2bef3383a..9dcd588ab5c9 100644
--- a/drivers/power/supply/test_power.c
+++ b/drivers/power/supply/test_power.c
@@ -490,10 +490,12 @@ static int param_set_ac_online(const char *key, const struct kernel_param *kp)
 	return 0;
 }
 
-static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
+static int param_get_ac_online(struct seq_buf *buffer,
+			       const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, ac_online, "unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, ac_online, "unknown"));
+	return 0;
 }
 
 static int param_set_usb_online(const char *key, const struct kernel_param *kp)
@@ -503,10 +505,12 @@ static int param_set_usb_online(const char *key, const struct kernel_param *kp)
 	return 0;
 }
 
-static int param_get_usb_online(char *buffer, const struct kernel_param *kp)
+static int param_get_usb_online(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, usb_online, "unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, usb_online, "unknown"));
+	return 0;
 }
 
 static int param_set_battery_status(const char *key,
@@ -517,10 +521,12 @@ static int param_set_battery_status(const char *key,
 	return 0;
 }
 
-static int param_get_battery_status(char *buffer, const struct kernel_param *kp)
+static int param_get_battery_status(struct seq_buf *buffer,
+				    const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, battery_status, "unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, battery_status, "unknown"));
+	return 0;
 }
 
 static int param_set_battery_health(const char *key,
@@ -531,10 +537,12 @@ static int param_set_battery_health(const char *key,
 	return 0;
 }
 
-static int param_get_battery_health(char *buffer, const struct kernel_param *kp)
+static int param_get_battery_health(struct seq_buf *buffer,
+				    const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, battery_health, "unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, battery_health, "unknown"));
+	return 0;
 }
 
 static int param_set_battery_present(const char *key,
@@ -545,11 +553,12 @@ static int param_set_battery_present(const char *key,
 	return 0;
 }
 
-static int param_get_battery_present(char *buffer,
+static int param_get_battery_present(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, battery_present, "unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, battery_present, "unknown"));
+	return 0;
 }
 
 static int param_set_battery_technology(const char *key,
@@ -561,12 +570,12 @@ static int param_set_battery_technology(const char *key,
 	return 0;
 }
 
-static int param_get_battery_technology(char *buffer,
+static int param_get_battery_technology(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n",
-			map_get_key(map_ac_online, battery_technology,
-					"unknown"));
+	seq_buf_printf(buffer, "%s\n",
+		       map_get_key(map_ac_online, battery_technology, "unknown"));
+	return 0;
 }
 
 static int param_set_battery_capacity(const char *key,
diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
index 676a12b44e88..5e8817a63726 100644
--- a/drivers/target/target_core_user.c
+++ b/drivers/target/target_core_user.c
@@ -249,10 +249,11 @@ static int tcmu_set_global_max_data_area(const char *str,
 	return 0;
 }
 
-static int tcmu_get_global_max_data_area(char *buffer,
+static int tcmu_get_global_max_data_area(struct seq_buf *buffer,
 					 const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%d\n", TCMU_PAGES_TO_MBS(tcmu_global_max_pages));
+	seq_buf_printf(buffer, "%d\n", TCMU_PAGES_TO_MBS(tcmu_global_max_pages));
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(tcmu_global_max_data_area_op,
@@ -265,11 +266,12 @@ MODULE_PARM_DESC(global_max_data_area_mb,
 		 "Max MBs allowed to be allocated to all the tcmu device's "
 		 "data areas.");
 
-static int tcmu_get_block_netlink(char *buffer,
+static int tcmu_get_block_netlink(struct seq_buf *buffer,
 				  const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n", tcmu_netlink_blocked ?
-		       "blocked" : "unblocked");
+	seq_buf_printf(buffer, "%s\n",
+		       tcmu_netlink_blocked ? "blocked" : "unblocked");
+	return 0;
 }
 
 static int tcmu_set_block_netlink(const char *str,
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
index 68275c3f2c9b..1a68721748d9 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
@@ -77,10 +77,12 @@ static int slider_def_balance_set(const char *arg, const struct kernel_param *kp
 	return ret;
 }
 
-static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
+static int slider_def_balance_get(struct seq_buf *buf,
+				  const struct kernel_param *kp)
 {
 	guard(mutex)(&slider_param_lock);
-	return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
+	seq_buf_printf(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(slider_def_balance_ops, slider_def_balance_set,
@@ -109,10 +111,12 @@ static int slider_def_offset_set(const char *arg, const struct kernel_param *kp)
 	return ret;
 }
 
-static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
+static int slider_def_offset_get(struct seq_buf *buf,
+				 const struct kernel_param *kp)
 {
 	guard(mutex)(&slider_param_lock);
-	return sysfs_emit(buf, "%02x\n", slider_offset);
+	seq_buf_printf(buf, "%02x\n", slider_offset);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(slider_offset_ops, slider_def_offset_set,
diff --git a/drivers/ufs/core/ufs-fault-injection.c b/drivers/ufs/core/ufs-fault-injection.c
index 7d2873da7dc5..88f348b41614 100644
--- a/drivers/ufs/core/ufs-fault-injection.c
+++ b/drivers/ufs/core/ufs-fault-injection.c
@@ -8,7 +8,7 @@
 #include <ufs/ufshcd.h>
 #include "ufs-fault-injection.h"
 
-static int ufs_fault_get(char *buffer, const struct kernel_param *kp);
+static int ufs_fault_get(struct seq_buf *buffer, const struct kernel_param *kp);
 static int ufs_fault_set(const char *val, const struct kernel_param *kp);
 
 static DEFINE_KERNEL_PARAM_OPS(ufs_fault_ops, ufs_fault_set, ufs_fault_get);
@@ -31,11 +31,12 @@ MODULE_PARM_DESC(timeout,
 	"Fault injection. timeout=<interval>,<probability>,<space>,<times>");
 static DECLARE_FAULT_ATTR(ufs_timeout_attr);
 
-static int ufs_fault_get(char *buffer, const struct kernel_param *kp)
+static int ufs_fault_get(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	const char *fault_str = kp->arg;
 
-	return sysfs_emit(buffer, "%s\n", fault_str);
+	seq_buf_printf(buffer, "%s\n", fault_str);
+	return 0;
 }
 
 static int ufs_fault_set(const char *val, const struct kernel_param *kp)
diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
index fd52f2213e27..23ca63ebf3d2 100644
--- a/drivers/vhost/scsi.c
+++ b/drivers/vhost/scsi.c
@@ -81,10 +81,11 @@ static int vhost_scsi_set_inline_sg_cnt(const char *buf,
 }
 #endif
 
-static int vhost_scsi_get_inline_sg_cnt(char *buf,
+static int vhost_scsi_get_inline_sg_cnt(struct seq_buf *buf,
 					const struct kernel_param *kp)
 {
-	return sprintf(buf, "%u\n", vhost_scsi_inline_sg_cnt);
+	seq_buf_printf(buf, "%u\n", vhost_scsi_inline_sg_cnt);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(vhost_scsi_inline_sg_cnt_op,
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index f2fba60dc5ed..5b7debe5274b 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -358,7 +358,8 @@ static int param_set_nfs_timeout(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int param_get_nfs_timeout(char *buffer, const struct kernel_param *kp)
+static int param_get_nfs_timeout(struct seq_buf *buffer,
+				 const struct kernel_param *kp)
 {
 	long num = *((int *)kp->arg);
 
@@ -369,7 +370,8 @@ static int param_get_nfs_timeout(char *buffer, const struct kernel_param *kp)
 			num = (num + (HZ - 1)) / HZ;
 	} else
 		num = -1;
-	return sysfs_emit(buffer, "%li\n", num);
+	seq_buf_printf(buffer, "%li\n", num);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(param_ops_nfs_timeout, param_set_nfs_timeout,
diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c
index 5821e33df78f..8fd759d31ff9 100644
--- a/fs/ocfs2/dlmfs/dlmfs.c
+++ b/fs/ocfs2/dlmfs/dlmfs.c
@@ -78,10 +78,11 @@ static int param_set_dlmfs_capabilities(const char *val,
 	printk(KERN_ERR "%s: readonly parameter\n", kp->name);
 	return -EINVAL;
 }
-static int param_get_dlmfs_capabilities(char *buffer,
+static int param_get_dlmfs_capabilities(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
-	return sysfs_emit(buffer, DLMFS_CAPABILITIES);
+	seq_buf_printf(buffer, DLMFS_CAPABILITIES);
+	return 0;
 }
 module_param_call(capabilities, param_set_dlmfs_capabilities,
 		  param_get_dlmfs_capabilities, NULL, 0444);
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 13cb60b52bd6..d9a21b813b4f 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -28,9 +28,10 @@ static int ovl_ccup_set(const char *buf, const struct kernel_param *param)
 	return 0;
 }
 
-static int ovl_ccup_get(char *buf, const struct kernel_param *param)
+static int ovl_ccup_get(struct seq_buf *buf, const struct kernel_param *param)
 {
-	return sprintf(buf, "N\n");
+	seq_buf_printf(buf, "N\n");
+	return 0;
 }
 
 module_param_call(check_copy_up, ovl_ccup_set, ovl_ccup_get, NULL, 0644);
diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c
index 38ae3b596ef2..9c9b6dc25888 100644
--- a/kernel/locking/locktorture.c
+++ b/kernel/locking/locktorture.c
@@ -86,11 +86,13 @@ static int param_set_cpumask(const char *val, const struct kernel_param *kp)
 }
 
 // Output a cpumask kernel parameter.
-static int param_get_cpumask(char *buffer, const struct kernel_param *kp)
+static int param_get_cpumask(struct seq_buf *buffer,
+			     const struct kernel_param *kp)
 {
 	cpumask_var_t *cm_bind = kp->arg;
 
-	return sprintf(buffer, "%*pbl", cpumask_pr_args(*cm_bind));
+	seq_buf_printf(buffer, "%*pbl", cpumask_pr_args(*cm_bind));
+	return 0;
 }
 
 static bool cpumask_nonempty(cpumask_var_t mask)
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index e675d7f1b4ee..ffbbb7d4ff2a 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -3970,9 +3970,11 @@ static int param_set_do_rcu_barrier(const char *val, const struct kernel_param *
 /*
  * Output the number of outstanding rcutree.do_rcu_barrier requests.
  */
-static int param_get_do_rcu_barrier(char *buffer, const struct kernel_param *kp)
+static int param_get_do_rcu_barrier(struct seq_buf *buffer,
+				    const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%d\n", atomic_read((atomic_t *)kp->arg));
+	seq_buf_printf(buffer, "%d\n", atomic_read((atomic_t *)kp->arg));
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(do_rcu_barrier_ops, param_set_do_rcu_barrier,
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 42562b811d94..3fe338d2ca64 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -7157,9 +7157,11 @@ static int wq_affn_dfl_set(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int wq_affn_dfl_get(char *buffer, const struct kernel_param *kp)
+static int wq_affn_dfl_get(struct seq_buf *buffer,
+			   const struct kernel_param *kp)
 {
-	return scnprintf(buffer, PAGE_SIZE, "%s\n", wq_affn_names[wq_affn_dfl]);
+	seq_buf_printf(buffer, "%s\n", wq_affn_names[wq_affn_dfl]);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(wq_affn_dfl_ops, wq_affn_dfl_set,
diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
index 30880b6c726a..70faf8ede76d 100644
--- a/lib/test_dynamic_debug.c
+++ b/lib/test_dynamic_debug.c
@@ -18,10 +18,12 @@ static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
 	do_prints();
 	return 0;
 }
-static int param_get_do_prints(char *buffer, const struct kernel_param *kp)
+static int param_get_do_prints(struct seq_buf *buffer,
+			       const struct kernel_param *kp)
 {
 	do_prints();
-	return scnprintf(buffer, PAGE_SIZE, "did do_prints\n");
+	seq_buf_printf(buffer, "did do_prints\n");
+	return 0;
 }
 static DEFINE_KERNEL_PARAM_OPS(param_ops_do_prints, param_set_do_prints,
 			       param_get_do_prints);
diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c
index 5feb93c5262e..84e607f76126 100644
--- a/mm/damon/lru_sort.c
+++ b/mm/damon/lru_sort.c
@@ -438,10 +438,11 @@ static int damon_lru_sort_enabled_store(const char *val,
 	return damon_lru_sort_turn(enabled);
 }
 
-static int damon_lru_sort_enabled_load(char *buffer,
-		const struct kernel_param *kp)
+static int damon_lru_sort_enabled_load(struct seq_buf *buffer,
+				       const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
+	seq_buf_printf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_lru_sort_enabled_store,
@@ -461,8 +462,8 @@ static int damon_lru_sort_kdamond_pid_store(const char *val,
 	return 0;
 }
 
-static int damon_lru_sort_kdamond_pid_load(char *buffer,
-		const struct kernel_param *kp)
+static int damon_lru_sort_kdamond_pid_load(struct seq_buf *buffer,
+					   const struct kernel_param *kp)
 {
 	int kdamond_pid = -1;
 
@@ -471,7 +472,8 @@ static int damon_lru_sort_kdamond_pid_load(char *buffer,
 		if (kdamond_pid < 0)
 			kdamond_pid = -1;
 	}
-	return sprintf(buffer, "%d\n", kdamond_pid);
+	seq_buf_printf(buffer, "%d\n", kdamond_pid);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c
index 27e772b095fa..546bdf356a40 100644
--- a/mm/damon/reclaim.c
+++ b/mm/damon/reclaim.c
@@ -340,10 +340,11 @@ static int damon_reclaim_enabled_store(const char *val,
 	return damon_reclaim_turn(enabled);
 }
 
-static int damon_reclaim_enabled_load(char *buffer,
-		const struct kernel_param *kp)
+static int damon_reclaim_enabled_load(struct seq_buf *buffer,
+				      const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
+	seq_buf_printf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_reclaim_enabled_store,
@@ -363,8 +364,8 @@ static int damon_reclaim_kdamond_pid_store(const char *val,
 	return 0;
 }
 
-static int damon_reclaim_kdamond_pid_load(char *buffer,
-		const struct kernel_param *kp)
+static int damon_reclaim_kdamond_pid_load(struct seq_buf *buffer,
+					  const struct kernel_param *kp)
 {
 	int kdamond_pid = -1;
 
@@ -373,7 +374,8 @@ static int damon_reclaim_kdamond_pid_load(char *buffer,
 		if (kdamond_pid < 0)
 			kdamond_pid = -1;
 	}
-	return sprintf(buffer, "%d\n", kdamond_pid);
+	seq_buf_printf(buffer, "%d\n", kdamond_pid);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
diff --git a/mm/damon/stat.c b/mm/damon/stat.c
index 6eb548793802..70d6b477fc0b 100644
--- a/mm/damon/stat.c
+++ b/mm/damon/stat.c
@@ -19,8 +19,8 @@
 static int damon_stat_enabled_store(
 		const char *val, const struct kernel_param *kp);
 
-static int damon_stat_enabled_load(char *buffer,
-		const struct kernel_param *kp);
+static int damon_stat_enabled_load(struct seq_buf *buffer,
+				   const struct kernel_param *kp);
 
 static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_stat_enabled_store,
 			       damon_stat_enabled_load);
@@ -306,9 +306,11 @@ static int damon_stat_enabled_store(
 	return 0;
 }
 
-static int damon_stat_enabled_load(char *buffer, const struct kernel_param *kp)
+static int damon_stat_enabled_load(struct seq_buf *buffer,
+				   const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%c\n", damon_stat_enabled() ? 'Y' : 'N');
+	seq_buf_printf(buffer, "%c\n", damon_stat_enabled() ? 'Y' : 'N');
+	return 0;
 }
 
 static int __init damon_stat_init(void)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 42e0cf313281..887c18a193ac 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -100,13 +100,17 @@ static int set_memmap_mode(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int get_memmap_mode(char *buffer, const struct kernel_param *kp)
+static int get_memmap_mode(struct seq_buf *buffer,
+			   const struct kernel_param *kp)
 {
 	int mode = *((int *)kp->arg);
 
-	if (mode == MEMMAP_ON_MEMORY_FORCE)
-		return sprintf(buffer, "force\n");
-	return sprintf(buffer, "%c\n", mode ? 'Y' : 'N');
+	if (mode == MEMMAP_ON_MEMORY_FORCE) {
+		seq_buf_printf(buffer, "force\n");
+		return 0;
+	}
+	seq_buf_printf(buffer, "%c\n", mode ? 'Y' : 'N');
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(memmap_mode_ops, set_memmap_mode,
@@ -147,9 +151,11 @@ static int set_online_policy(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int get_online_policy(char *buffer, const struct kernel_param *kp)
+static int get_online_policy(struct seq_buf *buffer,
+			     const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);
+	seq_buf_printf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);
+	return 0;
 }
 
 /*
diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c
index 633202a99e4a..583b11a2489c 100644
--- a/net/ceph/ceph_common.c
+++ b/net/ceph/ceph_common.c
@@ -47,10 +47,11 @@ bool libceph_compatible(void *data)
 }
 EXPORT_SYMBOL(libceph_compatible);
 
-static int param_get_supported_features(char *buffer,
+static int param_get_supported_features(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
-	return sprintf(buffer, "0x%llx", CEPH_FEATURES_SUPPORTED_DEFAULT);
+	seq_buf_printf(buffer, "0x%llx", CEPH_FEATURES_SUPPORTED_DEFAULT);
+	return 0;
 }
 static DEFINE_KERNEL_PARAM_OPS(param_ops_supported_features, NULL,
 			       param_get_supported_features);
diff --git a/net/sunrpc/auth.c b/net/sunrpc/auth.c
index 64a3e894fd4c..5a2b64dcf9e5 100644
--- a/net/sunrpc/auth.c
+++ b/net/sunrpc/auth.c
@@ -73,12 +73,14 @@ static int param_set_hashtbl_sz(const char *val, const struct kernel_param *kp)
 	return -EINVAL;
 }
 
-static int param_get_hashtbl_sz(char *buffer, const struct kernel_param *kp)
+static int param_get_hashtbl_sz(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
 	unsigned int nbits;
 
 	nbits = *(unsigned int *)kp->arg;
-	return sprintf(buffer, "%u\n", 1U << nbits);
+	seq_buf_printf(buffer, "%u\n", 1U << nbits);
+	return 0;
 }
 
 #define param_check_hashtbl_sz(name, p) __param_check(name, p, unsigned int);
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index 576fa42e7abf..26b85077ecc8 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -148,7 +148,7 @@ sunrpc_get_pool_mode(char *buf, size_t size)
 EXPORT_SYMBOL(sunrpc_get_pool_mode);
 
 static int
-param_get_pool_mode(char *buf, const struct kernel_param *kp)
+param_get_pool_mode(struct seq_buf *buf, const struct kernel_param *kp)
 {
 	char str[16];
 	int len;
@@ -162,7 +162,8 @@ param_get_pool_mode(char *buf, const struct kernel_param *kp)
 	str[len] = '\n';
 	str[len + 1] = '\0';
 
-	return sysfs_emit(buf, "%s", str);
+	seq_buf_printf(buf, "%s", str);
+	return 0;
 }
 
 module_param_call(pool_mode, param_set_pool_mode, param_get_pool_mode,
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index a6815b4bd0da..748d08c57f60 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1797,10 +1797,11 @@ static int param_set_debug(const char *val, const struct kernel_param *kp);
 static int param_get_debug(struct seq_buf *buffer, const struct kernel_param *kp);
 
 static int param_set_audit(const char *val, const struct kernel_param *kp);
-static int param_get_audit(char *buffer, const struct kernel_param *kp);
+static int param_get_audit(struct seq_buf *buffer,
+			   const struct kernel_param *kp);
 
 static int param_set_mode(const char *val, const struct kernel_param *kp);
-static int param_get_mode(char *buffer, const struct kernel_param *kp);
+static int param_get_mode(struct seq_buf *buffer, const struct kernel_param *kp);
 
 /* Flag values, also controllable via /sys/module/apparmor/parameters
  * We define special types as we want to do additional mediation.
@@ -2050,13 +2051,15 @@ static int param_set_debug(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int param_get_audit(char *buffer, const struct kernel_param *kp)
+static int param_get_audit(struct seq_buf *buffer,
+			   const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
 	if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
 		return -EPERM;
-	return sysfs_emit(buffer, "%s\n", audit_mode_names[aa_g_audit]);
+	seq_buf_printf(buffer, "%s\n", audit_mode_names[aa_g_audit]);
+	return 0;
 }
 
 static int param_set_audit(const char *val, const struct kernel_param *kp)
@@ -2078,13 +2081,14 @@ static int param_set_audit(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int param_get_mode(char *buffer, const struct kernel_param *kp)
+static int param_get_mode(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
 	if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
 		return -EPERM;
-	return sysfs_emit(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]);
+	seq_buf_printf(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]);
+	return 0;
 }
 
 static int param_set_mode(const char *val, const struct kernel_param *kp)
-- 
2.34.1


^ permalink raw reply related

* [PATCH 10/11] treewide: Manually convert custom kernel_param_ops .get callbacks
From: Kees Cook @ 2026-05-21 13:33 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133315.work.845-kees@kernel.org>

Convert struct kernel_param_ops .get callbacks from legacy "char *" to
"struct seq_buf *".

Since seq_buf_printf() will return -1 on overflow, and struct
kernel_param_ops .get callbacks are expected to truncate without error,
we must ignore the return value from seq_buf_print() and always return 0
(as the length is calculated in the common dispatcher code).

No struct kernel_param_ops initializations need changing since
DEFINE_KERNEL_PARAM_OPS already routes the pointer to .get or .get_str
via _Generic based on the function signature, so converted callbacks
are automatically moved from the .get_str to the .get callback.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 include/linux/dynamic_debug.h            |  8 ++-
 arch/um/drivers/vfio_kern.c              |  3 +-
 arch/um/drivers/virtio_uml.c             | 12 ++--
 drivers/acpi/button.c                    | 19 ++++--
 drivers/acpi/sysfs.c                     | 83 +++++++++++-------------
 drivers/char/ipmi/ipmi_watchdog.c        | 33 ++++------
 drivers/firmware/qemu_fw_cfg.c           | 34 +++++-----
 drivers/gpu/drm/i915/i915_mitigations.c  | 26 ++++----
 drivers/infiniband/ulp/srp/ib_srp.c      |  7 +-
 drivers/media/usb/uvc/uvc_driver.c       |  8 ++-
 drivers/pci/pcie/aspm.c                  | 17 +++--
 drivers/scsi/fcoe/fcoe_transport.c       | 22 +++----
 drivers/thermal/intel/intel_powerclamp.c | 14 ++--
 drivers/tty/hvc/hvc_iucv.c               | 18 ++---
 drivers/usb/storage/usb.c                | 20 +++---
 drivers/virtio/virtio_mmio.c             | 21 +++---
 lib/dynamic_debug.c                      | 10 ++-
 17 files changed, 178 insertions(+), 177 deletions(-)

diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
index 05743900a116..999a25671b6a 100644
--- a/include/linux/dynamic_debug.h
+++ b/include/linux/dynamic_debug.h
@@ -334,8 +334,10 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
 extern int ddebug_dyndbg_module_param_cb(char *param, char *val,
 					const char *modname);
 struct kernel_param;
+struct seq_buf;
 int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp);
-int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp);
+int param_get_dyndbg_classes(struct seq_buf *buffer,
+			     const struct kernel_param *kp);
 
 #else
 
@@ -352,9 +354,11 @@ static inline int ddebug_dyndbg_module_param_cb(char *param, char *val,
 }
 
 struct kernel_param;
+struct seq_buf;
 static inline int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp)
 { return 0; }
-static inline int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
+static inline int param_get_dyndbg_classes(struct seq_buf *buffer,
+					   const struct kernel_param *kp)
 { return 0; }
 
 #endif
diff --git a/arch/um/drivers/vfio_kern.c b/arch/um/drivers/vfio_kern.c
index fb7988dc5482..7c1119d0d9c1 100644
--- a/arch/um/drivers/vfio_kern.c
+++ b/arch/um/drivers/vfio_kern.c
@@ -623,7 +623,8 @@ static int uml_vfio_cmdline_set(const char *device, const struct kernel_param *k
 	return 0;
 }
 
-static int uml_vfio_cmdline_get(char *buffer, const struct kernel_param *kp)
+static int uml_vfio_cmdline_get(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
 	return 0;
 }
diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c
index f9ae745f4586..cea806540625 100644
--- a/arch/um/drivers/virtio_uml.c
+++ b/arch/um/drivers/virtio_uml.c
@@ -1379,23 +1379,21 @@ static int vu_cmdline_get_device(struct device *dev, void *data)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 	struct virtio_uml_platform_data *pdata = pdev->dev.platform_data;
-	char *buffer = data;
-	unsigned int len = strlen(buffer);
+	struct seq_buf *s = data;
 
-	snprintf(buffer + len, PAGE_SIZE - len, "%s:%d:%d\n",
-		 pdata->socket_path, pdata->virtio_device_id, pdev->id);
+	seq_buf_printf(s, "%s:%d:%d\n",
+		       pdata->socket_path, pdata->virtio_device_id, pdev->id);
 	return 0;
 }
 
-static int vu_cmdline_get(char *buffer, const struct kernel_param *kp)
+static int vu_cmdline_get(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	guard(mutex)(&vu_cmdline_lock);
 
-	buffer[0] = '\0';
 	if (vu_cmdline_parent_registered)
 		device_for_each_child(&vu_cmdline_parent, buffer,
 				      vu_cmdline_get_device);
-	return strlen(buffer) + 1;
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(vu_cmdline_param_ops, vu_cmdline_set,
diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
index dc064a388c23..31c624bebc65 100644
--- a/drivers/acpi/button.c
+++ b/drivers/acpi/button.c
@@ -715,19 +715,24 @@ static int param_set_lid_init_state(const char *val,
 	return 0;
 }
 
-static int param_get_lid_init_state(char *buf, const struct kernel_param *kp)
+static int param_get_lid_init_state(struct seq_buf *buf,
+				    const struct kernel_param *kp)
 {
-	int i, c = 0;
+	int i;
 
-	for (i = 0; i < ARRAY_SIZE(lid_init_state_str); i++)
+	for (i = 0; i < ARRAY_SIZE(lid_init_state_str); i++) {
 		if (i == lid_init_state)
-			c += sprintf(buf + c, "[%s] ", lid_init_state_str[i]);
+			seq_buf_printf(buf, "[%s] ", lid_init_state_str[i]);
 		else
-			c += sprintf(buf + c, "%s ", lid_init_state_str[i]);
+			seq_buf_printf(buf, "%s ", lid_init_state_str[i]);
+	}
 
-	buf[c - 1] = '\n'; /* Replace the final space with a newline */
+	/* Replace the final space with a newline. */
+	if (!seq_buf_has_overflowed(buf) && buf->len > 0 &&
+	    buf->buffer[buf->len - 1] == ' ')
+		buf->buffer[buf->len - 1] = '\n';
 
-	return c;
+	return 0;
 }
 
 module_param_call(lid_init_state,
diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
index 5247ed7e05cc..dff7cc7da8bf 100644
--- a/drivers/acpi/sysfs.c
+++ b/drivers/acpi/sysfs.c
@@ -89,53 +89,49 @@ static const struct acpi_dlevel acpi_debug_levels[] = {
 	ACPI_DEBUG_INIT(ACPI_LV_EVENTS),
 };
 
-static int param_get_debug_layer(char *buffer, const struct kernel_param *kp)
+static int param_get_debug_layer(struct seq_buf *buffer,
+				 const struct kernel_param *kp)
 {
-	int result = 0;
 	int i;
 
-	result = sprintf(buffer, "%-25s\tHex        SET\n", "Description");
+	seq_buf_printf(buffer, "%-25s\tHex        SET\n", "Description");
 
 	for (i = 0; i < ARRAY_SIZE(acpi_debug_layers); i++) {
-		result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",
-				  acpi_debug_layers[i].name,
-				  acpi_debug_layers[i].value,
-				  (acpi_dbg_layer & acpi_debug_layers[i].value)
-				  ? '*' : ' ');
+		seq_buf_printf(buffer, "%-25s\t0x%08lX [%c]\n",
+			       acpi_debug_layers[i].name,
+			       acpi_debug_layers[i].value,
+			       (acpi_dbg_layer & acpi_debug_layers[i].value)
+			       ? '*' : ' ');
 	}
-	result +=
-	    sprintf(buffer + result, "%-25s\t0x%08X [%c]\n", "ACPI_ALL_DRIVERS",
-		    ACPI_ALL_DRIVERS,
-		    (acpi_dbg_layer & ACPI_ALL_DRIVERS) ==
-		    ACPI_ALL_DRIVERS ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS)
-		    == 0 ? ' ' : '-');
-	result +=
-	    sprintf(buffer + result,
-		    "--\ndebug_layer = 0x%08X ( * = enabled)\n",
-		    acpi_dbg_layer);
+	seq_buf_printf(buffer, "%-25s\t0x%08X [%c]\n", "ACPI_ALL_DRIVERS",
+		       ACPI_ALL_DRIVERS,
+		       (acpi_dbg_layer & ACPI_ALL_DRIVERS) == ACPI_ALL_DRIVERS
+		       ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS) == 0
+		       ? ' ' : '-');
+	seq_buf_printf(buffer, "--\ndebug_layer = 0x%08X ( * = enabled)\n",
+		       acpi_dbg_layer);
 
-	return result;
+	return 0;
 }
 
-static int param_get_debug_level(char *buffer, const struct kernel_param *kp)
+static int param_get_debug_level(struct seq_buf *buffer,
+				 const struct kernel_param *kp)
 {
-	int result = 0;
 	int i;
 
-	result = sprintf(buffer, "%-25s\tHex        SET\n", "Description");
+	seq_buf_printf(buffer, "%-25s\tHex        SET\n", "Description");
 
 	for (i = 0; i < ARRAY_SIZE(acpi_debug_levels); i++) {
-		result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",
-				  acpi_debug_levels[i].name,
-				  acpi_debug_levels[i].value,
-				  (acpi_dbg_level & acpi_debug_levels[i].value)
-				  ? '*' : ' ');
+		seq_buf_printf(buffer, "%-25s\t0x%08lX [%c]\n",
+			       acpi_debug_levels[i].name,
+			       acpi_debug_levels[i].value,
+			       (acpi_dbg_level & acpi_debug_levels[i].value)
+			       ? '*' : ' ');
 	}
-	result +=
-	    sprintf(buffer + result, "--\ndebug_level = 0x%08X (* = enabled)\n",
-		    acpi_dbg_level);
+	seq_buf_printf(buffer, "--\ndebug_level = 0x%08X (* = enabled)\n",
+		       acpi_dbg_level);
 
-	return result;
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(param_ops_debug_layer, param_set_uint,
@@ -247,16 +243,18 @@ static int param_set_trace_state(const char *val,
 	return 0;
 }
 
-static int param_get_trace_state(char *buffer, const struct kernel_param *kp)
+static int param_get_trace_state(struct seq_buf *buffer,
+				 const struct kernel_param *kp)
 {
 	if (!(acpi_gbl_trace_flags & ACPI_TRACE_ENABLED))
-		return sprintf(buffer, "disable\n");
-	if (!acpi_gbl_trace_method_name)
-		return sprintf(buffer, "enable\n");
-	if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT)
-		return sprintf(buffer, "method-once\n");
+		seq_buf_printf(buffer, "disable\n");
+	else if (!acpi_gbl_trace_method_name)
+		seq_buf_printf(buffer, "enable\n");
+	else if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT)
+		seq_buf_printf(buffer, "method-once\n");
 	else
-		return sprintf(buffer, "method\n");
+		seq_buf_printf(buffer, "method\n");
+	return 0;
 }
 
 module_param_call(trace_state, param_set_trace_state, param_get_trace_state,
@@ -272,14 +270,11 @@ MODULE_PARM_DESC(aml_debug_output,
 		 "To enable/disable the ACPI Debug Object output.");
 
 /* /sys/module/acpi/parameters/acpica_version */
-static int param_get_acpica_version(char *buffer,
+static int param_get_acpica_version(struct seq_buf *buffer,
 				    const struct kernel_param *kp)
 {
-	int result;
-
-	result = sprintf(buffer, "%x\n", ACPI_CA_VERSION);
-
-	return result;
+	seq_buf_printf(buffer, "%x\n", ACPI_CA_VERSION);
+	return 0;
 }
 
 module_param_call(acpica_version, NULL, param_get_acpica_version, NULL, 0444);
diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
index 91a99417d204..2bfec85ef331 100644
--- a/drivers/char/ipmi/ipmi_watchdog.c
+++ b/drivers/char/ipmi/ipmi_watchdog.c
@@ -197,11 +197,11 @@ static DEFINE_KERNEL_PARAM_OPS(param_ops_timeout, set_param_timeout,
 			       param_get_int);
 #define param_check_timeout param_check_int
 
-typedef int (*action_fn)(const char *intval, char *outval);
+typedef int (*action_fn)(const char *intval, struct seq_buf *outval);
 
-static int action_op(const char *inval, char *outval);
-static int preaction_op(const char *inval, char *outval);
-static int preop_op(const char *inval, char *outval);
+static int action_op(const char *inval, struct seq_buf *outval);
+static int preaction_op(const char *inval, struct seq_buf *outval);
+static int preop_op(const char *inval, struct seq_buf *outval);
 static void check_parms(void);
 
 static int set_param_str(const char *val, const struct kernel_param *kp)
@@ -227,20 +227,11 @@ static int set_param_str(const char *val, const struct kernel_param *kp)
 	return rv;
 }
 
-static int get_param_str(char *buffer, const struct kernel_param *kp)
+static int get_param_str(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	action_fn fn = (action_fn) kp->arg;
-	int rv, len;
 
-	rv = fn(NULL, buffer);
-	if (rv)
-		return rv;
-
-	len = strlen(buffer);
-	buffer[len++] = '\n';
-	buffer[len] = 0;
-
-	return len;
+	return fn(NULL, buffer);
 }
 
 
@@ -1154,12 +1145,12 @@ static int action_op_set_val(const char *inval)
 	return 0;
 }
 
-static int action_op(const char *inval, char *outval)
+static int action_op(const char *inval, struct seq_buf *outval)
 {
 	int rv;
 
 	if (outval)
-		strcpy(outval, action);
+		seq_buf_printf(outval, "%s\n", action);
 
 	if (!inval)
 		return 0;
@@ -1186,12 +1177,12 @@ static int preaction_op_set_val(const char *inval)
 	return 0;
 }
 
-static int preaction_op(const char *inval, char *outval)
+static int preaction_op(const char *inval, struct seq_buf *outval)
 {
 	int rv;
 
 	if (outval)
-		strcpy(outval, preaction);
+		seq_buf_printf(outval, "%s\n", preaction);
 
 	if (!inval)
 		return 0;
@@ -1214,12 +1205,12 @@ static int preop_op_set_val(const char *inval)
 	return 0;
 }
 
-static int preop_op(const char *inval, char *outval)
+static int preop_op(const char *inval, struct seq_buf *outval)
 {
 	int rv;
 
 	if (outval)
-		strcpy(outval, preop);
+		seq_buf_printf(outval, "%s\n", preop);
 
 	if (!inval)
 		return 0;
diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c
index c87a5449ba8c..4ebc1e327849 100644
--- a/drivers/firmware/qemu_fw_cfg.c
+++ b/drivers/firmware/qemu_fw_cfg.c
@@ -860,7 +860,8 @@ static int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
 	return PTR_ERR_OR_ZERO(fw_cfg_cmdline_dev);
 }
 
-static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
+static int fw_cfg_cmdline_get(struct seq_buf *buf,
+			      const struct kernel_param *kp)
 {
 	/* stay silent if device was not configured via the command
 	 * line, or if the parameter name (ioport/mmio) doesn't match
@@ -873,22 +874,25 @@ static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
 
 	switch (fw_cfg_cmdline_dev->num_resources) {
 	case 1:
-		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_1_FMT,
-				resource_size(&fw_cfg_cmdline_dev->resource[0]),
-				fw_cfg_cmdline_dev->resource[0].start);
+		seq_buf_printf(buf, PH_ADDR_PR_1_FMT,
+			       resource_size(&fw_cfg_cmdline_dev->resource[0]),
+			       fw_cfg_cmdline_dev->resource[0].start);
+		return 0;
 	case 3:
-		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_3_FMT,
-				resource_size(&fw_cfg_cmdline_dev->resource[0]),
-				fw_cfg_cmdline_dev->resource[0].start,
-				fw_cfg_cmdline_dev->resource[1].start,
-				fw_cfg_cmdline_dev->resource[2].start);
+		seq_buf_printf(buf, PH_ADDR_PR_3_FMT,
+			       resource_size(&fw_cfg_cmdline_dev->resource[0]),
+			       fw_cfg_cmdline_dev->resource[0].start,
+			       fw_cfg_cmdline_dev->resource[1].start,
+			       fw_cfg_cmdline_dev->resource[2].start);
+		return 0;
 	case 4:
-		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_4_FMT,
-				resource_size(&fw_cfg_cmdline_dev->resource[0]),
-				fw_cfg_cmdline_dev->resource[0].start,
-				fw_cfg_cmdline_dev->resource[1].start,
-				fw_cfg_cmdline_dev->resource[2].start,
-				fw_cfg_cmdline_dev->resource[3].start);
+		seq_buf_printf(buf, PH_ADDR_PR_4_FMT,
+			       resource_size(&fw_cfg_cmdline_dev->resource[0]),
+			       fw_cfg_cmdline_dev->resource[0].start,
+			       fw_cfg_cmdline_dev->resource[1].start,
+			       fw_cfg_cmdline_dev->resource[2].start,
+			       fw_cfg_cmdline_dev->resource[3].start);
+		return 0;
 	}
 
 	/* Should never get here */
diff --git a/drivers/gpu/drm/i915/i915_mitigations.c b/drivers/gpu/drm/i915/i915_mitigations.c
index 6061eae84e9c..99cb38f355b6 100644
--- a/drivers/gpu/drm/i915/i915_mitigations.c
+++ b/drivers/gpu/drm/i915/i915_mitigations.c
@@ -95,33 +95,37 @@ static int mitigations_set(const char *val, const struct kernel_param *kp)
 	return 0;
 }
 
-static int mitigations_get(char *buffer, const struct kernel_param *kp)
+static int mitigations_get(struct seq_buf *buffer,
+			   const struct kernel_param *kp)
 {
 	unsigned long local = READ_ONCE(mitigations);
-	int count, i;
 	bool enable;
+	int i;
 
-	if (!local)
-		return scnprintf(buffer, PAGE_SIZE, "%s\n", "off");
+	if (!local) {
+		seq_buf_printf(buffer, "%s\n", "off");
+		return 0;
+	}
 
 	if (local & BIT(BITS_PER_LONG - 1)) {
-		count = scnprintf(buffer, PAGE_SIZE, "%s,", "auto");
+		seq_buf_printf(buffer, "%s,", "auto");
 		enable = false;
 	} else {
 		enable = true;
-		count = 0;
 	}
 
 	for (i = 0; i < ARRAY_SIZE(names); i++) {
 		if ((local & BIT(i)) != enable)
 			continue;
-
-		count += scnprintf(buffer + count, PAGE_SIZE - count,
-				   "%s%s,", enable ? "" : "!", names[i]);
+		seq_buf_printf(buffer, "%s%s,", enable ? "" : "!", names[i]);
 	}
 
-	buffer[count - 1] = '\n';
-	return count;
+	/* Replace the trailing comma with a newline. */
+	if (!seq_buf_has_overflowed(buffer) && buffer->len > 0 &&
+	    buffer->buffer[buffer->len - 1] == ',')
+		buffer->buffer[buffer->len - 1] = '\n';
+
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(ops, mitigations_set, mitigations_get);
diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c
index a81515f52a4f..4f53e939eec1 100644
--- a/drivers/infiniband/ulp/srp/ib_srp.c
+++ b/drivers/infiniband/ulp/srp/ib_srp.c
@@ -161,14 +161,15 @@ static struct ib_client srp_client = {
 
 static struct ib_sa_client srp_sa_client;
 
-static int srp_tmo_get(char *buffer, const struct kernel_param *kp)
+static int srp_tmo_get(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	int tmo = *(int *)kp->arg;
 
 	if (tmo >= 0)
-		return sysfs_emit(buffer, "%d\n", tmo);
+		seq_buf_printf(buffer, "%d\n", tmo);
 	else
-		return sysfs_emit(buffer, "off\n");
+		seq_buf_printf(buffer, "off\n");
+	return 0;
 }
 
 static int srp_tmo_set(const char *val, const struct kernel_param *kp)
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 2338cab7fef9..1c5c40ce852d 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -2451,12 +2451,14 @@ static int uvc_reset_resume(struct usb_interface *intf)
  * Module parameters
  */
 
-static int uvc_clock_param_get(char *buffer, const struct kernel_param *kp)
+static int uvc_clock_param_get(struct seq_buf *buffer,
+			       const struct kernel_param *kp)
 {
 	if (uvc_clock_param == CLOCK_MONOTONIC)
-		return sprintf(buffer, "CLOCK_MONOTONIC");
+		seq_buf_printf(buffer, "CLOCK_MONOTONIC");
 	else
-		return sprintf(buffer, "CLOCK_REALTIME");
+		seq_buf_printf(buffer, "CLOCK_REALTIME");
+	return 0;
 }
 
 static int uvc_clock_param_set(const char *val, const struct kernel_param *kp)
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 925373b98dff..af2dd668fe4d 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -1572,16 +1572,19 @@ static int pcie_aspm_set_policy(const char *val,
 	return 0;
 }
 
-static int pcie_aspm_get_policy(char *buffer, const struct kernel_param *kp)
+static int pcie_aspm_get_policy(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
-	int i, cnt = 0;
-	for (i = 0; i < ARRAY_SIZE(policy_str); i++)
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(policy_str); i++) {
 		if (i == aspm_policy)
-			cnt += sprintf(buffer + cnt, "[%s] ", policy_str[i]);
+			seq_buf_printf(buffer, "[%s] ", policy_str[i]);
 		else
-			cnt += sprintf(buffer + cnt, "%s ", policy_str[i]);
-	cnt += sprintf(buffer + cnt, "\n");
-	return cnt;
+			seq_buf_printf(buffer, "%s ", policy_str[i]);
+	}
+	seq_buf_putc(buffer, '\n');
+	return 0;
 }
 
 module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy,
diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c
index 88d85fc9a52a..aa10514ec46e 100644
--- a/drivers/scsi/fcoe/fcoe_transport.c
+++ b/drivers/scsi/fcoe/fcoe_transport.c
@@ -23,7 +23,8 @@ MODULE_LICENSE("GPL v2");
 
 static int fcoe_transport_create(const char *, const struct kernel_param *);
 static int fcoe_transport_destroy(const char *, const struct kernel_param *);
-static int fcoe_transport_show(char *buffer, const struct kernel_param *kp);
+static int fcoe_transport_show(struct seq_buf *buffer,
+			       const struct kernel_param *kp);
 static struct fcoe_transport *fcoe_transport_lookup(struct net_device *device);
 static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *device);
 static int fcoe_transport_enable(const char *, const struct kernel_param *);
@@ -595,22 +596,21 @@ int fcoe_transport_detach(struct fcoe_transport *ft)
 }
 EXPORT_SYMBOL(fcoe_transport_detach);
 
-static int fcoe_transport_show(char *buffer, const struct kernel_param *kp)
+static int fcoe_transport_show(struct seq_buf *buffer,
+			       const struct kernel_param *kp)
 {
-	int i, j;
 	struct fcoe_transport *ft = NULL;
 
-	i = j = sprintf(buffer, "Attached FCoE transports:");
+	seq_buf_printf(buffer, "Attached FCoE transports:");
 	mutex_lock(&ft_mutex);
-	list_for_each_entry(ft, &fcoe_transports, list) {
-		if (i >= PAGE_SIZE - IFNAMSIZ)
-			break;
-		i += snprintf(&buffer[i], IFNAMSIZ, "%s ", ft->name);
+	if (list_empty(&fcoe_transports)) {
+		seq_buf_printf(buffer, "none");
+	} else {
+		list_for_each_entry(ft, &fcoe_transports, list)
+			seq_buf_printf(buffer, "%s ", ft->name);
 	}
 	mutex_unlock(&ft_mutex);
-	if (i == j)
-		i += snprintf(&buffer[i], IFNAMSIZ, "none");
-	return i;
+	return 0;
 }
 
 static int __init fcoe_transport_init(void)
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
index 98fbc6892714..50ec1a0ff1ab 100644
--- a/drivers/thermal/intel/intel_powerclamp.c
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -101,15 +101,13 @@ static int duration_set(const char *arg, const struct kernel_param *kp)
 	return ret;
 }
 
-static int duration_get(char *buf, const struct kernel_param *kp)
+static int duration_get(struct seq_buf *buf, const struct kernel_param *kp)
 {
-	int ret;
-
 	mutex_lock(&powerclamp_lock);
-	ret = sysfs_emit(buf, "%d\n", duration / 1000);
+	seq_buf_printf(buf, "%d\n", duration / 1000);
 	mutex_unlock(&powerclamp_lock);
 
-	return ret;
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(duration_ops, duration_set, duration_get);
@@ -192,12 +190,14 @@ static int cpumask_set(const char *arg, const struct kernel_param *kp)
 	return ret;
 }
 
-static int cpumask_get(char *buf, const struct kernel_param *kp)
+static int cpumask_get(struct seq_buf *buf, const struct kernel_param *kp)
 {
 	if (!cpumask_available(idle_injection_cpu_mask))
 		return -ENODEV;
 
-	return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask);
+	seq_buf_printf(buf, "%*pb\n", nr_cpu_ids,
+		       cpumask_bits(idle_injection_cpu_mask));
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(cpumask_ops, cpumask_set, cpumask_get);
diff --git a/drivers/tty/hvc/hvc_iucv.c b/drivers/tty/hvc/hvc_iucv.c
index 29612a4a32cb..b27c1dfbd249 100644
--- a/drivers/tty/hvc/hvc_iucv.c
+++ b/drivers/tty/hvc/hvc_iucv.c
@@ -1256,36 +1256,32 @@ static int param_set_vmidfilter(const char *val, const struct kernel_param *kp)
 
 /**
  * param_get_vmidfilter() - Get z/VM user ID filter
- * @buffer:	Buffer to store z/VM user ID filter,
- *		(buffer size assumption PAGE_SIZE)
+ * @buffer:	seq_buf to store z/VM user ID filter
  * @kp:		Kernel parameter pointing to the hvc_iucv_filter array
  *
  * The function stores the filter as a comma-separated list of z/VM user IDs
  * in @buffer. Typically, sysfs routines call this function for attr show.
  */
-static int param_get_vmidfilter(char *buffer, const struct kernel_param *kp)
+static int param_get_vmidfilter(struct seq_buf *buffer,
+				const struct kernel_param *kp)
 {
-	int rc;
 	size_t index, len;
 	void *start, *end;
 
 	if (!machine_is_vm() || !hvc_iucv_devices)
 		return -ENODEV;
 
-	rc = 0;
 	read_lock_bh(&hvc_iucv_filter_lock);
 	for (index = 0; index < hvc_iucv_filter_size; index++) {
 		start = hvc_iucv_filter + (8 * index);
 		end   = memchr(start, ' ', 8);
 		len   = (end) ? end - start : 8;
-		memcpy(buffer + rc, start, len);
-		rc += len;
-		buffer[rc++] = ',';
+		if (index)
+			seq_buf_putc(buffer, ',');
+		seq_buf_printf(buffer, "%.*s", (int)len, (char *)start);
 	}
 	read_unlock_bh(&hvc_iucv_filter_lock);
-	if (rc)
-		buffer[--rc] = '\0';	/* replace last comma and update rc */
-	return rc;
+	return 0;
 }
 
 #define param_check_vmidfilter(name, p) __param_check(name, p, void)
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
index 71dd623b95c9..637e1b8f622f 100644
--- a/drivers/usb/storage/usb.c
+++ b/drivers/usb/storage/usb.c
@@ -115,27 +115,22 @@ static int parse_delay_str(const char *str, int ndecimals, const char *suffix,
  * @val: The integer value to format, scaled by 10^(@ndecimals).
  * @ndecimals: Number of decimal to scale down.
  * @suffix: Suffix string to format.
- * @str: Where to store the formatted string.
- * @size: The size of buffer for @str.
+ * @s: Where to store the formatted string.
  *
  * Format an integer value in @val scale down by 10^(@ndecimals) without @suffix
  * if @val is divisible by 10^(@ndecimals).
  * Otherwise format a value in @val just as it is with @suffix
- *
- * Returns the number of characters written into @str.
  */
-static int format_delay_ms(unsigned int val, int ndecimals, const char *suffix,
-			char *str, int size)
+static void format_delay_ms(unsigned int val, int ndecimals, const char *suffix,
+			    struct seq_buf *s)
 {
 	u64 delay_ms = val;
 	unsigned int rem = do_div(delay_ms, int_pow(10, ndecimals));
-	int ret;
 
 	if (rem)
-		ret = scnprintf(str, size, "%u%s\n", val, suffix);
+		seq_buf_printf(s, "%u%s\n", val, suffix);
 	else
-		ret = scnprintf(str, size, "%u\n", (unsigned int)delay_ms);
-	return ret;
+		seq_buf_printf(s, "%u\n", (unsigned int)delay_ms);
 }
 
 static int delay_use_set(const char *s, const struct kernel_param *kp)
@@ -151,11 +146,12 @@ static int delay_use_set(const char *s, const struct kernel_param *kp)
 	return 0;
 }
 
-static int delay_use_get(char *s, const struct kernel_param *kp)
+static int delay_use_get(struct seq_buf *s, const struct kernel_param *kp)
 {
 	unsigned int delay_ms = *((unsigned int *)kp->arg);
 
-	return format_delay_ms(delay_ms, 3, "ms", s, PAGE_SIZE);
+	format_delay_ms(delay_ms, 3, "ms", s);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(delay_use_ops, delay_use_set, delay_use_get);
diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c
index f6df9c76ee81..81a7455e4643 100644
--- a/drivers/virtio/virtio_mmio.c
+++ b/drivers/virtio/virtio_mmio.c
@@ -728,24 +728,21 @@ static int vm_cmdline_set(const char *device,
 
 static int vm_cmdline_get_device(struct device *dev, void *data)
 {
-	char *buffer = data;
-	unsigned int len = strlen(buffer);
+	struct seq_buf *s = data;
 	struct platform_device *pdev = to_platform_device(dev);
 
-	snprintf(buffer + len, PAGE_SIZE - len, "0x%llx@0x%llx:%llu:%d\n",
-			pdev->resource[0].end - pdev->resource[0].start + 1ULL,
-			(unsigned long long)pdev->resource[0].start,
-			(unsigned long long)pdev->resource[1].start,
-			pdev->id);
+	seq_buf_printf(s, "0x%llx@0x%llx:%llu:%d\n",
+		       pdev->resource[0].end - pdev->resource[0].start + 1ULL,
+		       (unsigned long long)pdev->resource[0].start,
+		       (unsigned long long)pdev->resource[1].start,
+		       pdev->id);
 	return 0;
 }
 
-static int vm_cmdline_get(char *buffer, const struct kernel_param *kp)
+static int vm_cmdline_get(struct seq_buf *s, const struct kernel_param *kp)
 {
-	buffer[0] = '\0';
-	device_for_each_child(&vm_cmdline_parent, buffer,
-			vm_cmdline_get_device);
-	return strlen(buffer) + 1;
+	device_for_each_child(&vm_cmdline_parent, s, vm_cmdline_get_device);
+	return 0;
 }
 
 static DEFINE_KERNEL_PARAM_OPS(vm_cmdline_param_ops, vm_cmdline_set,
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index cf0405ba0dbd..123f061c2fb2 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -17,6 +17,7 @@
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/kallsyms.h>
+#include <linux/seq_buf.h>
 #include <linux/types.h>
 #include <linux/mutex.h>
 #include <linux/proc_fs.h>
@@ -787,7 +788,8 @@ EXPORT_SYMBOL(param_set_dyndbg_classes);
  * altered by direct >control.  Displays 0x for DISJOINT, 0-N for
  * LEVEL Returns: #chars written or <0 on error
  */
-int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
+int param_get_dyndbg_classes(struct seq_buf *buffer,
+			     const struct kernel_param *kp)
 {
 	const struct ddebug_class_param *dcp = kp->arg;
 	const struct ddebug_class_map *map = dcp->map;
@@ -796,11 +798,13 @@ int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
 
 	case DD_CLASS_TYPE_DISJOINT_NAMES:
 	case DD_CLASS_TYPE_DISJOINT_BITS:
-		return scnprintf(buffer, PAGE_SIZE, "0x%lx\n", *dcp->bits);
+		seq_buf_printf(buffer, "0x%lx\n", *dcp->bits);
+		return 0;
 
 	case DD_CLASS_TYPE_LEVEL_NAMES:
 	case DD_CLASS_TYPE_LEVEL_NUM:
-		return scnprintf(buffer, PAGE_SIZE, "%d\n", *dcp->lvl);
+		seq_buf_printf(buffer, "%d\n", *dcp->lvl);
+		return 0;
 	default:
 		return -1;
 	}
-- 
2.34.1


^ permalink raw reply related

* [PATCH 08/11] params: Convert generic kernel_param_ops .get helpers to seq_buf
From: Kees Cook @ 2026-05-21 13:33 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133315.work.845-kees@kernel.org>

Convert the generic struct kernel_param_ops .get helpers in
kernel/params.c directly to the seq_buf signature, drop their legacy
"char *" form, and refresh prototypes in <linux/moduleparam.h>:

  param_get_byte/short/ushort/int/uint/long/ulong/ullong/hexint
  param_get_charp/bool/invbool/string
  param_array_get

The STANDARD_PARAM_DEF() macro expands to a seq_buf body for every
numeric helper. param_array_get() now writes element output directly
into the parent seq_buf when the element ops provide .get; it only
allocates the per-call PAGE_SIZE bounce buffer when the element ops
still use the legacy .get_str path. The common "rewrite the prior
element's trailing newline as a comma" step lives outside both
branches so the two paths share it.

The non-core changes in this commit (arch/x86/kvm, mm/kfence,
drivers/dma/dmatest, security/apparmor) are the small set of callers that
directly invoke one of the converted generic helpers from their own .get
callback (e.g. an apparmor wrapper that adds a capability check and then
delegates to param_get_bool()). Because the helpers' signature changes
here, these wrappers must move in lockstep. Each of them is updated
to take "struct seq_buf *" and pass it through; param_get_debug() in
apparmor also pulls aa_print_debug_params() (and its val_mask_to_str()
helper, in security/apparmor/lib.c) over to seq_buf, since that is the
only consumer. No other behavioural change is intended.

Custom .get callbacks that do not delegate to a generic helper (and
therefore still match the .get_str signature) are routed automatically
to the .get_str field by the DEFINE_KERNEL_PARAM_OPS _Generic dispatcher
and are deliberately left alone here, to be changed separately within
their respective subsystems.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 include/linux/moduleparam.h     | 26 +++++------
 security/apparmor/include/lib.h |  3 +-
 mm/kfence/core.c                |  8 ++--
 arch/x86/kvm/mmu/mmu.c          | 16 ++++---
 arch/x86/kvm/svm/avic.c         |  8 ++--
 drivers/dma/dmatest.c           | 14 +++---
 kernel/params.c                 | 80 ++++++++++++++++++++-------------
 security/apparmor/lib.c         | 27 +++++------
 security/apparmor/lsm.c         | 25 ++++++-----
 9 files changed, 114 insertions(+), 93 deletions(-)

diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
index 795bc7c654ef..38acb5aef56b 100644
--- a/include/linux/moduleparam.h
+++ b/include/linux/moduleparam.h
@@ -500,61 +500,61 @@ void module_destroy_params(const struct kernel_param *params, unsigned int num);
 
 extern const struct kernel_param_ops param_ops_byte;
 int param_set_byte(const char *val, const struct kernel_param *kp);
-int param_get_byte(char *buffer, const struct kernel_param *kp);
+int param_get_byte(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_byte(name, p) __param_check(name, p, unsigned char)
 
 extern const struct kernel_param_ops param_ops_short;
 int param_set_short(const char *val, const struct kernel_param *kp);
-int param_get_short(char *buffer, const struct kernel_param *kp);
+int param_get_short(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_short(name, p) __param_check(name, p, short)
 
 extern const struct kernel_param_ops param_ops_ushort;
 int param_set_ushort(const char *val, const struct kernel_param *kp);
-int param_get_ushort(char *buffer, const struct kernel_param *kp);
+int param_get_ushort(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_ushort(name, p) __param_check(name, p, unsigned short)
 
 extern const struct kernel_param_ops param_ops_int;
 int param_set_int(const char *val, const struct kernel_param *kp);
-int param_get_int(char *buffer, const struct kernel_param *kp);
+int param_get_int(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_int(name, p) __param_check(name, p, int)
 
 extern const struct kernel_param_ops param_ops_uint;
 int param_set_uint(const char *val, const struct kernel_param *kp);
-int param_get_uint(char *buffer, const struct kernel_param *kp);
+int param_get_uint(struct seq_buf *s, const struct kernel_param *kp);
 int param_set_uint_minmax(const char *val, const struct kernel_param *kp,
 		unsigned int min, unsigned int max);
 #define param_check_uint(name, p) __param_check(name, p, unsigned int)
 
 extern const struct kernel_param_ops param_ops_long;
 int param_set_long(const char *val, const struct kernel_param *kp);
-int param_get_long(char *buffer, const struct kernel_param *kp);
+int param_get_long(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_long(name, p) __param_check(name, p, long)
 
 extern const struct kernel_param_ops param_ops_ulong;
 int param_set_ulong(const char *val, const struct kernel_param *kp);
-int param_get_ulong(char *buffer, const struct kernel_param *kp);
+int param_get_ulong(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_ulong(name, p) __param_check(name, p, unsigned long)
 
 extern const struct kernel_param_ops param_ops_ullong;
 int param_set_ullong(const char *val, const struct kernel_param *kp);
-int param_get_ullong(char *buffer, const struct kernel_param *kp);
+int param_get_ullong(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_ullong(name, p) __param_check(name, p, unsigned long long)
 
 extern const struct kernel_param_ops param_ops_hexint;
 int param_set_hexint(const char *val, const struct kernel_param *kp);
-int param_get_hexint(char *buffer, const struct kernel_param *kp);
+int param_get_hexint(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_hexint(name, p) param_check_uint(name, p)
 
 extern const struct kernel_param_ops param_ops_charp;
 int param_set_charp(const char *val, const struct kernel_param *kp);
-int param_get_charp(char *buffer, const struct kernel_param *kp);
+int param_get_charp(struct seq_buf *s, const struct kernel_param *kp);
 void param_free_charp(void *arg);
 #define param_check_charp(name, p) __param_check(name, p, char *)
 
 /* We used to allow int as well as bool.  We're taking that away! */
 extern const struct kernel_param_ops param_ops_bool;
 int param_set_bool(const char *val, const struct kernel_param *kp);
-int param_get_bool(char *buffer, const struct kernel_param *kp);
+int param_get_bool(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_bool(name, p) __param_check(name, p, bool)
 
 extern const struct kernel_param_ops param_ops_bool_enable_only;
@@ -564,7 +564,7 @@ int param_set_bool_enable_only(const char *val, const struct kernel_param *kp);
 
 extern const struct kernel_param_ops param_ops_invbool;
 int param_set_invbool(const char *val, const struct kernel_param *kp);
-int param_get_invbool(char *buffer, const struct kernel_param *kp);
+int param_get_invbool(struct seq_buf *s, const struct kernel_param *kp);
 #define param_check_invbool(name, p) __param_check(name, p, bool)
 
 /* An int, which can only be set like a bool (though it shows as an int). */
@@ -677,7 +677,7 @@ extern const struct kernel_param_ops param_array_ops;
 
 extern const struct kernel_param_ops param_ops_string;
 int param_set_copystring(const char *val, const struct kernel_param *kp);
-int param_get_string(char *buffer, const struct kernel_param *kp);
+int param_get_string(struct seq_buf *s, const struct kernel_param *kp);
 
 /* for exporting parameters in /sys/module/.../parameters */
 
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h
index 8c6ce8484552..966082075e61 100644
--- a/security/apparmor/include/lib.h
+++ b/security/apparmor/include/lib.h
@@ -13,6 +13,7 @@
 #include <linux/slab.h>
 #include <linux/fs.h>
 #include <linux/lsm_hooks.h>
+#include <linux/seq_buf.h>
 
 #include "match.h"
 
@@ -72,7 +73,7 @@ do {									\
 #endif
 
 int aa_parse_debug_params(const char *str);
-int aa_print_debug_params(char *buffer);
+int aa_print_debug_params(struct seq_buf *s);
 
 #define AA_ERROR(fmt, args...)						\
 	pr_err_ratelimited("AppArmor: " fmt, ##args)
diff --git a/mm/kfence/core.c b/mm/kfence/core.c
index e14102c01520..bfa936f09978 100644
--- a/mm/kfence/core.c
+++ b/mm/kfence/core.c
@@ -84,10 +84,12 @@ static int param_set_sample_interval(const char *val, const struct kernel_param
 	return 0;
 }
 
-static int param_get_sample_interval(char *buffer, const struct kernel_param *kp)
+static int param_get_sample_interval(struct seq_buf *buffer, const struct kernel_param *kp)
 {
-	if (!READ_ONCE(kfence_enabled))
-		return sprintf(buffer, "0\n");
+	if (!READ_ONCE(kfence_enabled)) {
+		seq_buf_puts(buffer, "0\n");
+		return 0;
+	}
 
 	return param_get_ulong(buffer, kp);
 }
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 996818ee9b09..5e9a2690d335 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -70,7 +70,7 @@ static uint __read_mostly nx_huge_pages_recovery_ratio = 0;
 static uint __read_mostly nx_huge_pages_recovery_ratio = 60;
 #endif
 
-static int get_nx_huge_pages(char *buffer, const struct kernel_param *kp);
+static int get_nx_huge_pages(struct seq_buf *buffer, const struct kernel_param *kp);
 static int set_nx_huge_pages(const char *val, const struct kernel_param *kp);
 static int set_nx_huge_pages_recovery_param(const char *val, const struct kernel_param *kp);
 
@@ -7493,15 +7493,19 @@ static void kvm_wake_nx_recovery_thread(struct kvm *kvm)
 		vhost_task_wake(nx_thread);
 }
 
-static int get_nx_huge_pages(char *buffer, const struct kernel_param *kp)
+static int get_nx_huge_pages(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	int val = *(int *)kp->arg;
 
-	if (nx_hugepage_mitigation_hard_disabled)
-		return sysfs_emit(buffer, "never\n");
+	if (nx_hugepage_mitigation_hard_disabled) {
+		seq_buf_puts(buffer, "never\n");
+		return 0;
+	}
 
-	if (val == -1)
-		return sysfs_emit(buffer, "auto\n");
+	if (val == -1) {
+		seq_buf_puts(buffer, "auto\n");
+		return 0;
+	}
 
 	return param_get_bool(buffer, kp);
 }
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 7907f9addff9..6c3b4626c5c1 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -77,12 +77,14 @@ static int avic_param_set(const char *val, const struct kernel_param *kp)
 	return param_set_bint(val, kp);
 }
 
-static int avic_param_get(char *buffer, const struct kernel_param *kp)
+static int avic_param_get(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	int val = *(int *)kp->arg;
 
-	if (val == AVIC_AUTO_MODE)
-		return sysfs_emit(buffer, "N\n");
+	if (val == AVIC_AUTO_MODE) {
+		seq_buf_puts(buffer, "N\n");
+		return 0;
+	}
 
 	return param_get_bool(buffer, kp);
 }
diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
index a7bddadcc52d..828298faca16 100644
--- a/drivers/dma/dmatest.c
+++ b/drivers/dma/dmatest.c
@@ -153,14 +153,14 @@ static struct dmatest_info {
 };
 
 static int dmatest_run_set(const char *val, const struct kernel_param *kp);
-static int dmatest_run_get(char *val, const struct kernel_param *kp);
+static int dmatest_run_get(struct seq_buf *val, const struct kernel_param *kp);
 static DEFINE_KERNEL_PARAM_OPS(run_ops, dmatest_run_set, dmatest_run_get);
 static bool dmatest_run;
 module_param_cb(run, &run_ops, &dmatest_run, 0644);
 MODULE_PARM_DESC(run, "Run the test (default: false)");
 
 static int dmatest_chan_set(const char *val, const struct kernel_param *kp);
-static int dmatest_chan_get(char *val, const struct kernel_param *kp);
+static int dmatest_chan_get(struct seq_buf *val, const struct kernel_param *kp);
 static DEFINE_KERNEL_PARAM_OPS(multi_chan_ops, dmatest_chan_set,
 			       dmatest_chan_get);
 
@@ -172,7 +172,7 @@ static struct kparam_string newchan_kps = {
 module_param_cb(channel, &multi_chan_ops, &newchan_kps, 0644);
 MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)");
 
-static int dmatest_test_list_get(char *val, const struct kernel_param *kp);
+static int dmatest_test_list_get(struct seq_buf *val, const struct kernel_param *kp);
 static DEFINE_KERNEL_PARAM_OPS(test_list_ops, NULL, dmatest_test_list_get);
 module_param_cb(test_list, &test_list_ops, NULL, 0444);
 MODULE_PARM_DESC(test_list, "Print current test list");
@@ -274,7 +274,7 @@ static bool is_threaded_test_pending(struct dmatest_info *info)
 	return false;
 }
 
-static int dmatest_wait_get(char *val, const struct kernel_param *kp)
+static int dmatest_wait_get(struct seq_buf *val, const struct kernel_param *kp)
 {
 	struct dmatest_info *info = &test_info;
 	struct dmatest_params *params = &info->params;
@@ -1164,7 +1164,7 @@ static void start_threaded_tests(struct dmatest_info *info)
 	run_pending_tests(info);
 }
 
-static int dmatest_run_get(char *val, const struct kernel_param *kp)
+static int dmatest_run_get(struct seq_buf *val, const struct kernel_param *kp)
 {
 	struct dmatest_info *info = &test_info;
 
@@ -1292,7 +1292,7 @@ static int dmatest_chan_set(const char *val, const struct kernel_param *kp)
 	return ret;
 }
 
-static int dmatest_chan_get(char *val, const struct kernel_param *kp)
+static int dmatest_chan_get(struct seq_buf *val, const struct kernel_param *kp)
 {
 	struct dmatest_info *info = &test_info;
 
@@ -1306,7 +1306,7 @@ static int dmatest_chan_get(char *val, const struct kernel_param *kp)
 	return param_get_string(val, kp);
 }
 
-static int dmatest_test_list_get(char *val, const struct kernel_param *kp)
+static int dmatest_test_list_get(struct seq_buf *val, const struct kernel_param *kp)
 {
 	struct dmatest_info *info = &test_info;
 	struct dmatest_chan *dtc;
diff --git a/kernel/params.c b/kernel/params.c
index 4eda2d23ddf2..25f0c8d5d19f 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -212,15 +212,16 @@ char *parse_args(const char *doing,
 }
 
 /* Lazy bastard, eh? */
-#define STANDARD_PARAM_DEF(name, type, format, strtolfn)      		\
+#define STANDARD_PARAM_DEF(name, type, format, strtolfn)		\
 	int param_set_##name(const char *val, const struct kernel_param *kp) \
 	{								\
 		return strtolfn(val, 0, (type *)kp->arg);		\
 	}								\
-	int param_get_##name(char *buffer, const struct kernel_param *kp) \
+	int param_get_##name(struct seq_buf *s,				\
+			     const struct kernel_param *kp)		\
 	{								\
-		return scnprintf(buffer, PAGE_SIZE, format "\n",	\
-				*((type *)kp->arg));			\
+		seq_buf_printf(s, format "\n", *((type *)kp->arg));	\
+		return 0;						\
 	}								\
 	DEFINE_KERNEL_PARAM_OPS(param_ops_##name,			\
 				param_set_##name, param_get_##name);	\
@@ -285,9 +286,10 @@ int param_set_charp(const char *val, const struct kernel_param *kp)
 }
 EXPORT_SYMBOL(param_set_charp);
 
-int param_get_charp(char *buffer, const struct kernel_param *kp)
+int param_get_charp(struct seq_buf *s, const struct kernel_param *kp)
 {
-	return scnprintf(buffer, PAGE_SIZE, "%s\n", *((char **)kp->arg));
+	seq_buf_printf(s, "%s\n", *((char **)kp->arg));
+	return 0;
 }
 EXPORT_SYMBOL(param_get_charp);
 
@@ -312,10 +314,11 @@ int param_set_bool(const char *val, const struct kernel_param *kp)
 }
 EXPORT_SYMBOL(param_set_bool);
 
-int param_get_bool(char *buffer, const struct kernel_param *kp)
+int param_get_bool(struct seq_buf *s, const struct kernel_param *kp)
 {
 	/* Y and N chosen as being relatively non-coder friendly */
-	return sprintf(buffer, "%c\n", *(bool *)kp->arg ? 'Y' : 'N');
+	seq_buf_printf(s, "%c\n", *(bool *)kp->arg ? 'Y' : 'N');
+	return 0;
 }
 EXPORT_SYMBOL(param_get_bool);
 
@@ -365,9 +368,10 @@ int param_set_invbool(const char *val, const struct kernel_param *kp)
 }
 EXPORT_SYMBOL(param_set_invbool);
 
-int param_get_invbool(char *buffer, const struct kernel_param *kp)
+int param_get_invbool(struct seq_buf *s, const struct kernel_param *kp)
 {
-	return sprintf(buffer, "%c\n", (*(bool *)kp->arg) ? 'N' : 'Y');
+	seq_buf_printf(s, "%c\n", (*(bool *)kp->arg) ? 'N' : 'Y');
+	return 0;
 }
 EXPORT_SYMBOL(param_get_invbool);
 
@@ -453,36 +457,46 @@ static int param_array_set(const char *val, const struct kernel_param *kp)
 			   arr->num ?: &temp_num);
 }
 
-static int param_array_get(char *buffer, const struct kernel_param *kp)
+static int param_array_get(struct seq_buf *s, const struct kernel_param *kp)
 {
-	int i, off, ret;
-	char *elem_buf;
 	const struct kparam_array *arr = kp->arr;
 	struct kernel_param p = *kp;
+	char *elem_buf = NULL;
+	int i, ret = 0;
 
-	elem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!elem_buf)
-		return -ENOMEM;
+	for (i = 0; i < (arr->num ? *arr->num : arr->max); i++) {
+		size_t before = s->len;
 
-	for (i = off = 0; i < (arr->num ? *arr->num : arr->max); i++) {
 		p.arg = arr->elem + arr->elemsize * i;
 		check_kparam_locked(p.mod);
-		ret = arr->ops->get_str(elem_buf, &p);
-		if (ret < 0)
-			goto out;
-		ret = min(ret, (int)(PAGE_SIZE - 1 - off));
-		if (!ret)
+
+		if (arr->ops->get) {
+			ret = arr->ops->get(s, &p);
+			if (ret < 0)
+				goto out;
+		} else {
+			if (!elem_buf) {
+				elem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+				if (!elem_buf) {
+					ret = -ENOMEM;
+					goto out;
+				}
+			}
+			ret = arr->ops->get_str(elem_buf, &p);
+			if (ret < 0)
+				goto out;
+			seq_buf_putmem(s, elem_buf, ret);
+		}
+
+		/* Nothing got written (e.g. overflow) — stop. */
+		if (s->len == before)
 			break;
+
 		/* Replace the previous element's trailing newline with a comma. */
-		if (i)
-			buffer[off - 1] = ',';
-		memcpy(buffer + off, elem_buf, ret);
-		off += ret;
-		if (off == PAGE_SIZE - 1)
-			break;
+		if (i && s->buffer[before - 1] == '\n')
+			s->buffer[before - 1] = ',';
 	}
-	buffer[off] = '\0';
-	ret = off;
+	ret = 0;
 out:
 	kfree(elem_buf);
 	return ret;
@@ -517,10 +531,12 @@ int param_set_copystring(const char *val, const struct kernel_param *kp)
 }
 EXPORT_SYMBOL(param_set_copystring);
 
-int param_get_string(char *buffer, const struct kernel_param *kp)
+int param_get_string(struct seq_buf *s, const struct kernel_param *kp)
 {
 	const struct kparam_string *kps = kp->str;
-	return scnprintf(buffer, PAGE_SIZE, "%s\n", kps->string);
+
+	seq_buf_printf(s, "%s\n", kps->string);
+	return 0;
 }
 EXPORT_SYMBOL(param_get_string);
 
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index e41ff57798b2..eef136add5b4 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -85,37 +85,32 @@ int aa_parse_debug_params(const char *str)
 
 /**
  * val_mask_to_str - convert a perm mask to its short string
- * @str: character buffer to store string in (at least 10 characters)
- * @size: size of the @str buffer
+ * @s: seq_buf to store string in
  * @table: NUL-terminated character buffer of permission characters (NOT NULL)
  * @mask: permission mask to convert
  */
-static int val_mask_to_str(char *str, size_t size,
-			   const struct val_table_ent *table, u32 mask)
+static void val_mask_to_str(struct seq_buf *s,
+			    const struct val_table_ent *table, u32 mask)
 {
 	const struct val_table_ent *ent;
-	int total = 0;
+	bool first = true;
 
 	for (ent = table; ent->str; ent++) {
 		if (ent->value && (ent->value & mask) == ent->value) {
-			int len = scnprintf(str, size, "%s%s", total ? "," : "",
-					    ent->str);
-			size -= len;
-			str += len;
-			total += len;
+			seq_buf_printf(s, "%s%s", first ? "" : ",", ent->str);
+			first = false;
 			mask &= ~ent->value;
 		}
 	}
-
-	return total;
 }
 
-int aa_print_debug_params(char *buffer)
+int aa_print_debug_params(struct seq_buf *s)
 {
 	if (!aa_g_debug)
-		return sprintf(buffer, "N");
-	return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table,
-			       aa_g_debug);
+		seq_buf_puts(s, "N");
+	else
+		val_mask_to_str(s, debug_values_table, aa_g_debug);
+	return 0;
 }
 
 bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 8a253c743363..a6815b4bd0da 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -16,6 +16,7 @@
 #include <linux/namei.h>
 #include <linux/ptrace.h>
 #include <linux/ctype.h>
+#include <linux/seq_buf.h>
 #include <linux/sysctl.h>
 #include <linux/sysfs.h>
 #include <linux/audit.h>
@@ -1765,20 +1766,20 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
  */
 
 static int param_set_aabool(const char *val, const struct kernel_param *kp);
-static int param_get_aabool(char *buffer, const struct kernel_param *kp);
+static int param_get_aabool(struct seq_buf *buffer, const struct kernel_param *kp);
 #define param_check_aabool param_check_bool
 static DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_aabool, param_set_aabool,
 				     param_get_aabool);
 
 static int param_set_aauint(const char *val, const struct kernel_param *kp);
-static int param_get_aauint(char *buffer, const struct kernel_param *kp);
+static int param_get_aauint(struct seq_buf *buffer, const struct kernel_param *kp);
 #define param_check_aauint param_check_uint
 static DEFINE_KERNEL_PARAM_OPS(param_ops_aauint, param_set_aauint,
 			       param_get_aauint);
 
 static int param_set_aacompressionlevel(const char *val,
 					const struct kernel_param *kp);
-static int param_get_aacompressionlevel(char *buffer,
+static int param_get_aacompressionlevel(struct seq_buf *buffer,
 					const struct kernel_param *kp);
 #define param_check_aacompressionlevel param_check_int
 static DEFINE_KERNEL_PARAM_OPS(param_ops_aacompressionlevel,
@@ -1786,14 +1787,14 @@ static DEFINE_KERNEL_PARAM_OPS(param_ops_aacompressionlevel,
 			       param_get_aacompressionlevel);
 
 static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp);
-static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
+static int param_get_aalockpolicy(struct seq_buf *buffer, const struct kernel_param *kp);
 #define param_check_aalockpolicy param_check_bool
 static DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_aalockpolicy,
 				     param_set_aalockpolicy,
 				     param_get_aalockpolicy);
 
 static int param_set_debug(const char *val, const struct kernel_param *kp);
-static int param_get_debug(char *buffer, const struct kernel_param *kp);
+static int param_get_debug(struct seq_buf *buffer, const struct kernel_param *kp);
 
 static int param_set_audit(const char *val, const struct kernel_param *kp);
 static int param_get_audit(char *buffer, const struct kernel_param *kp);
@@ -1868,7 +1869,7 @@ module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR);
 bool aa_g_paranoid_load = IS_ENABLED(CONFIG_SECURITY_APPARMOR_PARANOID_LOAD);
 module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO);
 
-static int param_get_aaintbool(char *buffer, const struct kernel_param *kp);
+static int param_get_aaintbool(struct seq_buf *buffer, const struct kernel_param *kp);
 static int param_set_aaintbool(const char *val, const struct kernel_param *kp);
 #define param_check_aaintbool param_check_int
 static DEFINE_KERNEL_PARAM_OPS(param_ops_aaintbool, param_set_aaintbool,
@@ -1898,7 +1899,7 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp
 	return param_set_bool(val, kp);
 }
 
-static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp)
+static int param_get_aalockpolicy(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
@@ -1916,7 +1917,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp)
 	return param_set_bool(val, kp);
 }
 
-static int param_get_aabool(char *buffer, const struct kernel_param *kp)
+static int param_get_aabool(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
@@ -1942,7 +1943,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp)
 	return error;
 }
 
-static int param_get_aauint(char *buffer, const struct kernel_param *kp)
+static int param_get_aauint(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
@@ -1978,7 +1979,7 @@ static int param_set_aaintbool(const char *val, const struct kernel_param *kp)
  * display in the /sys filesystem, while keeping it "int" for the LSM
  * infrastructure.
  */
-static int param_get_aaintbool(char *buffer, const struct kernel_param *kp)
+static int param_get_aaintbool(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	struct kernel_param kp_local;
 	bool value;
@@ -2011,7 +2012,7 @@ static int param_set_aacompressionlevel(const char *val,
 	return error;
 }
 
-static int param_get_aacompressionlevel(char *buffer,
+static int param_get_aacompressionlevel(struct seq_buf *buffer,
 					const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
@@ -2021,7 +2022,7 @@ static int param_get_aacompressionlevel(char *buffer,
 	return param_get_int(buffer, kp);
 }
 
-static int param_get_debug(char *buffer, const struct kernel_param *kp)
+static int param_get_debug(struct seq_buf *buffer, const struct kernel_param *kp)
 {
 	if (!apparmor_enabled)
 		return -EINVAL;
-- 
2.34.1


^ permalink raw reply related

* [PATCH 07/11] moduleparam: Route DEFINE_KERNEL_PARAM_OPS get pointer via _Generic
From: Kees Cook @ 2026-05-21 13:33 UTC (permalink / raw)
  To: Luis Chamberlain
  Cc: Kees Cook, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
	Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
	Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
	Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
	Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
	Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
	Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
	Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
	Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
	Benjamin Berg, Ilpo Järvinen, David E. Box,
	Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
	Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
	Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
	Andrew Morton, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
	linux-modules, kasan-dev, linux-mm, apparmor,
	linux-security-module, linux-um, linux-acpi, openipmi-developer,
	qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
	linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
	linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
	netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133315.work.845-kees@kernel.org>

Make the DEFINE_KERNEL_PARAM_OPS family route their _get argument to
either .get (struct seq_buf *) or .get_str (char *) at compile time
based on the pointer's actual function signature. Two helper macros
do the routing:

  _KERNEL_PARAM_OPS_GET     - return the pointer if it has the seq_buf
                              signature, otherwise NULL of that type
  _KERNEL_PARAM_OPS_GET_STR - mirror image for the char * signature

Both use _Generic; only the two valid function-pointer types are
listed, so any third-party type is a compile error rather than
silently falling through.

Now a callback whose body has been migrated from char * to struct
seq_buf * needs no change at its kernel_param_ops initialization site,
because the macro picks up the new type automatically and assigns to
the correct field.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 include/linux/moduleparam.h | 33 ++++++++++++++++++++++++++-------
 1 file changed, 26 insertions(+), 7 deletions(-)

diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
index c52120f6ac28..795bc7c654ef 100644
--- a/include/linux/moduleparam.h
+++ b/include/linux/moduleparam.h
@@ -85,15 +85,32 @@ struct kernel_param_ops {
  *
  *   static DEFINE_KERNEL_PARAM_OPS(my_ops, my_set, my_get);
  *
- * Routing the @_set and @_get function pointers through the macro
- * (rather than naming the struct fields at every call site) lets the
- * field layout change in one place when callbacks are migrated to a
- * new signature.
+ * @_get may be either of:
+ *   int (*)(struct seq_buf *, const struct kernel_param *) (seq_buf)
+ *   int (*)(char *, const struct kernel_param *)           (legacy)
+ *
+ * The macro uses _Generic to route the function pointer to the
+ * matching field (.get or .get_str) at compile time, leaving the
+ * other field NULL. Each helper matches the wrong prototype signature
+ * and returns NULL, falling through to the default branch otherwise;
+ * if @_get has neither expected signature the assignment to the
+ * fields gets a normal compile-time type-mismatch error.
  */
+#define _KERNEL_PARAM_OPS_GET(_get)					\
+	_Generic((_get),						\
+	    int (*)(char *, const struct kernel_param *): NULL,		\
+	    default: (_get))
+
+#define _KERNEL_PARAM_OPS_GET_STR(_get)					\
+	_Generic((_get),						\
+	    int (*)(struct seq_buf *, const struct kernel_param *): NULL, \
+	    default: (_get))
+
 #define DEFINE_KERNEL_PARAM_OPS(_name, _set, _get)			\
 	const struct kernel_param_ops _name = {				\
 		.set = (_set),						\
-		.get_str = (_get),					\
+		.get = _KERNEL_PARAM_OPS_GET(_get),			\
+		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
 	}
 
 /* As DEFINE_KERNEL_PARAM_OPS, with KERNEL_PARAM_OPS_FL_NOARG set. */
@@ -101,14 +118,16 @@ struct kernel_param_ops {
 	const struct kernel_param_ops _name = {				\
 		.flags = KERNEL_PARAM_OPS_FL_NOARG,			\
 		.set = (_set),						\
-		.get_str = (_get),					\
+		.get = _KERNEL_PARAM_OPS_GET(_get),			\
+		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
 	}
 
 /* As DEFINE_KERNEL_PARAM_OPS, with an additional .free callback. */
 #define DEFINE_KERNEL_PARAM_OPS_FREE(_name, _set, _get, _free)		\
 	const struct kernel_param_ops _name = {				\
 		.set = (_set),						\
-		.get_str = (_get),					\
+		.get = _KERNEL_PARAM_OPS_GET(_get),			\
+		.get_str = _KERNEL_PARAM_OPS_GET_STR(_get),		\
 		.free = (_free),					\
 	}
 
-- 
2.34.1


^ permalink raw reply related


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