[PATCH 3/5] uaccess: add copy_struct_{from,to}_bounce_buffer() helpers

From: Stefan Metzmacher

Date: Tue Apr 07 2026 - 12:09:12 EST


These are similar to copy_struct_{from,to}_user() but operate
on kernel buffers instead of user buffers.

They can be used when there is a temporary bounce buffer used,
e.g. in msg_control or similar places.

It allows us to have the same logic to handle old vs. current
and current vs. new structures in the same compatible way.

copy_struct_from_sockptr() will also be able to
use copy_struct_from_bounce_buffer() for the kernel
case as follow us patch.

I'll use this in my IPPROTO_SMBDIRECT work,
but maybe it will also be useful for others...
IPPROTO_QUIC will likely also use it.

Cc: Dmitry Safonov <0x7f454c46@xxxxxxxxx>
Cc: Dmitry Safonov <dima@xxxxxxxxxx>
Cc: Francesco Ruggeri <fruggeri@xxxxxxxxxx>
Cc: Salam Noureddine <noureddine@xxxxxxxxxx>
Cc: David Ahern <dsahern@xxxxxxxxxx>
Cc: David S. Miller <davem@xxxxxxxxxxxxx>
Cc: Michal Luczaj <mhal@xxxxxxx>
Cc: David Wei <dw@xxxxxxxxxxx>
Cc: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx>
Cc: Luiz Augusto von Dentz <luiz.dentz@xxxxxxxxx>
Cc: Marcel Holtmann <marcel@xxxxxxxxxxxx>
Cc: Xin Long <lucien.xin@xxxxxxxxx>
Cc: Eric Dumazet <edumazet@xxxxxxxxxx>
Cc: Kuniyuki Iwashima <kuniyu@xxxxxxxxxx>
Cc: Paolo Abeni <pabeni@xxxxxxxxxx>
Cc: Willem de Bruijn <willemb@xxxxxxxxxx>
Cc: Neal Cardwell <ncardwell@xxxxxxxxxx>
Cc: Jakub Kicinski <kuba@xxxxxxxxxx>
Cc: Simon Horman <horms@xxxxxxxxxx>
Cc: Aleksa Sarai <cyphar@xxxxxxxxxx>
Cc: Christian Brauner <brauner@xxxxxxxxxx>
CC: Kees Cook <keescook@xxxxxxxxxxxx>
Cc: netdev@xxxxxxxxxxxxxxx
Cc: linux-bluetooth@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Signed-off-by: Stefan Metzmacher <metze@xxxxxxxxx>
---
include/linux/uaccess.h | 63 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)

diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h
index 1234b5fa4761..a6cd4f48bb99 100644
--- a/include/linux/uaccess.h
+++ b/include/linux/uaccess.h
@@ -513,6 +513,69 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src,
return 0;
}

+static __always_inline void
+__copy_struct_generic_bounce_buffer(void *dst, size_t dstsize,
+ const void *src, size_t srcsize,
+ bool *ignored_trailing)
+{
+ size_t size = min(dstsize, srcsize);
+ size_t rest = max(dstsize, srcsize) - size;
+
+ /* Deal with trailing bytes. */
+ if (dstsize > srcsize)
+ memset(dst + size, 0, rest);
+ if (ignored_trailing)
+ *ignored_trailing = dstsize < srcsize &&
+ memchr_inv(src + size, 0, rest) != NULL;
+ /* Copy the interoperable parts of the struct. */
+ memcpy(dst, src, size);
+}
+
+/**
+ * This is like copy_struct_from_user(), but the
+ * src buffer was already copied into a kernel
+ * bounce buffer, so it will never return -EFAULT.
+ */
+static __always_inline __must_check int
+copy_struct_from_bounce_buffer(void *dst, size_t dstsize,
+ const void *src, size_t srcsize)
+{
+ bool ignored_trailing;
+
+ /* Double check if ksize is larger than a known object size. */
+ if (WARN_ON_ONCE(dstsize > __builtin_object_size(dst, 1)))
+ return -E2BIG;
+
+ __copy_struct_generic_bounce_buffer(dst, dstsize,
+ src, srcsize,
+ &ignored_trailing);
+ if (unlikely(ignored_trailing))
+ return -E2BIG;
+
+ return 0;
+}
+
+/**
+ * This is like copy_struct_to_user(), but the
+ * dst buffer is a kernel bounce buffer instead
+ * of a direct userspace buffer, so it will never return -EFAULT.
+ */
+static __always_inline __must_check int
+copy_struct_to_bounce_buffer(void *dst, size_t dstsize,
+ const void *src,
+ size_t srcsize,
+ bool *ignored_trailing)
+{
+ /* Double check if srcsize is larger than a known object size. */
+ if (WARN_ON_ONCE(srcsize > __builtin_object_size(src, 1)))
+ return -E2BIG;
+
+ __copy_struct_generic_bounce_buffer(dst, dstsize,
+ src, srcsize,
+ ignored_trailing);
+ return 0;
+}
+
bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size);

long copy_from_kernel_nofault(void *dst, const void *src, size_t size);
--
2.43.0