summaryrefslogtreecommitdiff
path: root/include/linux
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2026-04-09 15:04:32 +0200
committerChristian Brauner <brauner@kernel.org>2026-05-11 12:25:31 +0200
commit6f43b87876197e4b2eb7d4564f5dd082c981e92a (patch)
treec4c9a2cd25f5059c422ce394ea8d4fee44040883 /include/linux
parent254f49634ee16a731174d2ae34bc50bd5f45e731 (diff)
parentc5ca9f85d7fe27a8c9c88195bb819e8b569fa930 (diff)
Merge patch series "uaccess/sockptr: copy_struct_ fixes and more helpers"
Stefan Metzmacher <metze@samba.org> says: Here are some patches related to copy_struct_{from,to}_{user,sockptr}() I collected during my work on an IPPROTO_SMBDIRECT implementation wrapping the smbdirect code used by cifs.ko and ksmbd.ko. The first patch fixes copy_struct_to_user() to behave like documented. The 2nd patch fixes the case where copy_struct_from_user() is called by copy_struct_from_sockptr(). The 3rd patch introduces copy_struct_{from,to}_bounce_buffer() as a result of a discussion about the IPPROTO_QUIC driver in order to be future prove when handling msg_control messages in sendmsg and recvmsg. But I'll likely also use them in my IPPROTO_SMBDIRECT driver. The 4th patch makes copy_struct_from_sockptr() a trivial wrapper switching between copy_struct_from_user() and copy_struct_from_bounce_buffer() The 5th patch introduces copy_struct_to_sockptr() which I'll also use in my IPPROTO_SMBDIRECT driver. * patches from https://patch.msgid.link/cover.1775576651.git.metze@samba.org: sockptr: introduce copy_struct_to_sockptr() sockptr: let copy_struct_from_sockptr() use copy_struct_from_bounce_buffer() uaccess: add copy_struct_{from,to}_bounce_buffer() helpers sockptr: fix usize check in copy_struct_from_sockptr() for user pointers uaccess: fix ignored_trailing logic in copy_struct_to_user() Link: https://patch.msgid.link/cover.1775576651.git.metze@samba.org Signed-off-by: Christian Brauner <brauner@kernel.org>
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/sockptr.h28
-rw-r--r--include/linux/uaccess.h65
2 files changed, 76 insertions, 17 deletions
diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h
index 3e6c8e9d67ae..9c2429c1a570 100644
--- a/include/linux/sockptr.h
+++ b/include/linux/sockptr.h
@@ -87,24 +87,10 @@ static inline int copy_safe_from_sockptr(void *dst, size_t ksize,
static inline int copy_struct_from_sockptr(void *dst, size_t ksize,
sockptr_t src, size_t usize)
{
- size_t size = min(ksize, usize);
- size_t rest = max(ksize, usize) - size;
-
if (!sockptr_is_kernel(src))
- return copy_struct_from_user(dst, ksize, src.user, size);
-
- if (usize < ksize) {
- memset(dst + size, 0, rest);
- } else if (usize > ksize) {
- char *p = src.kernel;
+ return copy_struct_from_user(dst, ksize, src.user, usize);
- while (rest--) {
- if (*p++)
- return -E2BIG;
- }
- }
- memcpy(dst, src.kernel, size);
- return 0;
+ return copy_struct_from_bounce_buffer(dst, ksize, src.kernel, usize);
}
static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset,
@@ -121,6 +107,16 @@ static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size)
return copy_to_sockptr_offset(dst, 0, src, size);
}
+static inline int
+copy_struct_to_sockptr(sockptr_t dst, size_t usize, const void *src,
+ size_t ksize, bool *ignored_trailing)
+{
+ if (!sockptr_is_kernel(dst))
+ return copy_struct_to_user(dst.user, usize, src, ksize, ignored_trailing);
+
+ return copy_struct_to_bounce_buffer(dst.kernel, usize, src, ksize, ignored_trailing);
+}
+
static inline void *memdup_sockptr_noprof(sockptr_t src, size_t len)
{
void *p = kmalloc_track_caller_noprof(len, GFP_USER | __GFP_NOWARN);
diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h
index 56328601218c..e4a64976f1c5 100644
--- a/include/linux/uaccess.h
+++ b/include/linux/uaccess.h
@@ -510,7 +510,7 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src,
return -EFAULT;
}
if (ignored_trailing)
- *ignored_trailing = ksize < usize &&
+ *ignored_trailing = usize < ksize &&
memchr_inv(src + size, 0, rest) != NULL;
/* Copy the interoperable parts of the struct. */
if (copy_to_user(dst, src, size))
@@ -518,6 +518,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);