Files
linux-stable-mirror/include/linux/sockptr.h
T
Stefan Metzmacher c5ca9f85d7 sockptr: introduce copy_struct_to_sockptr()
We already have copy_struct_from_sockptr() as wrapper to
copy_struct_from_user() or copy_struct_from_bounce_buffer(),
so it's good to have copy_struct_to_sockptr()
as well matching the behavior of copy_struct_to_user()
or copy_struct_to_bounce_buffer().

The world would be better without sockptr_t, but having
copy_struct_to_sockptr() is better than open code it
in various places.

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@gmail.com>
Cc: Dmitry Safonov <dima@arista.com>
Cc: Francesco Ruggeri <fruggeri@arista.com>
Cc: Salam Noureddine <noureddine@arista.com>
Cc: David Ahern <dsahern@kernel.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Michal Luczaj <mhal@rbox.co>
Cc: David Wei <dw@davidwei.uk>
Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: Marcel Holtmann <marcel@holtmann.org>
Cc: Xin Long <lucien.xin@gmail.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: Neal Cardwell <ncardwell@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Simon Horman <horms@kernel.org>
Cc: Aleksa Sarai <cyphar@cyphar.com>
Cc: Christian Brauner <brauner@kernel.org>
CC: Kees Cook <keescook@chromium.org>
Cc: netdev@vger.kernel.org
Cc: linux-bluetooth@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Link: https://patch.msgid.link/c950ee1578cb93b4411c3731010def9c1cd82f0d.1775576651.git.metze@samba.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-05-11 12:25:31 +02:00

169 lines
4.2 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020 Christoph Hellwig.
*
* Support for "universal" pointers that can point to either kernel or userspace
* memory.
*/
#ifndef _LINUX_SOCKPTR_H
#define _LINUX_SOCKPTR_H
#include <linux/slab.h>
#include <linux/uaccess.h>
typedef struct {
union {
void *kernel;
void __user *user;
};
bool is_kernel : 1;
} sockptr_t;
static inline bool sockptr_is_kernel(sockptr_t sockptr)
{
return sockptr.is_kernel;
}
static inline sockptr_t KERNEL_SOCKPTR(void *p)
{
return (sockptr_t) { .kernel = p, .is_kernel = true };
}
static inline sockptr_t USER_SOCKPTR(void __user *p)
{
return (sockptr_t) { .user = p };
}
static inline bool sockptr_is_null(sockptr_t sockptr)
{
if (sockptr_is_kernel(sockptr))
return !sockptr.kernel;
return !sockptr.user;
}
static inline int copy_from_sockptr_offset(void *dst, sockptr_t src,
size_t offset, size_t size)
{
if (!sockptr_is_kernel(src))
return copy_from_user(dst, src.user + offset, size);
memcpy(dst, src.kernel + offset, size);
return 0;
}
/* Deprecated.
* This is unsafe, unless caller checked user provided optlen.
* Prefer copy_safe_from_sockptr() instead.
*
* Returns 0 for success, or number of bytes not copied on error.
*/
static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size)
{
return copy_from_sockptr_offset(dst, src, 0, size);
}
/**
* copy_safe_from_sockptr: copy a struct from sockptr
* @dst: Destination address, in kernel space. This buffer must be @ksize
* bytes long.
* @ksize: Size of @dst struct.
* @optval: Source address. (in user or kernel space)
* @optlen: Size of @optval data.
*
* Returns:
* * -EINVAL: @optlen < @ksize
* * -EFAULT: access to userspace failed.
* * 0 : @ksize bytes were copied
*/
static inline int copy_safe_from_sockptr(void *dst, size_t ksize,
sockptr_t optval, unsigned int optlen)
{
if (optlen < ksize)
return -EINVAL;
if (copy_from_sockptr(dst, optval, ksize))
return -EFAULT;
return 0;
}
static inline int copy_struct_from_sockptr(void *dst, size_t ksize,
sockptr_t src, size_t usize)
{
if (!sockptr_is_kernel(src))
return copy_struct_from_user(dst, ksize, src.user, usize);
return copy_struct_from_bounce_buffer(dst, ksize, src.kernel, usize);
}
static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset,
const void *src, size_t size)
{
if (!sockptr_is_kernel(dst))
return copy_to_user(dst.user + offset, src, size);
memcpy(dst.kernel + offset, src, size);
return 0;
}
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);
if (!p)
return ERR_PTR(-ENOMEM);
if (copy_from_sockptr(p, src, len)) {
kfree(p);
return ERR_PTR(-EFAULT);
}
return p;
}
#define memdup_sockptr(...) alloc_hooks(memdup_sockptr_noprof(__VA_ARGS__))
static inline void *memdup_sockptr_nul_noprof(sockptr_t src, size_t len)
{
char *p = kmalloc_track_caller_noprof(len + 1, GFP_KERNEL);
if (!p)
return ERR_PTR(-ENOMEM);
if (copy_from_sockptr(p, src, len)) {
kfree(p);
return ERR_PTR(-EFAULT);
}
p[len] = '\0';
return p;
}
#define memdup_sockptr_nul(...) alloc_hooks(memdup_sockptr_nul_noprof(__VA_ARGS__))
static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count)
{
if (sockptr_is_kernel(src)) {
size_t len = min(strnlen(src.kernel, count - 1) + 1, count);
memcpy(dst, src.kernel, len);
return len;
}
return strncpy_from_user(dst, src.user, count);
}
static inline int check_zeroed_sockptr(sockptr_t src, size_t offset,
size_t size)
{
if (!sockptr_is_kernel(src))
return check_zeroed_user(src.user + offset, size);
return memchr_inv(src.kernel + offset, 0, size) == NULL;
}
#endif /* _LINUX_SOCKPTR_H */