mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 18:09:57 +00:00
![Christoph Hellwig](/assets/img/avatar_default.png)
This reverts commits6d04fe15f7
anda31edb2059
. It turns out the idea to share a single pointer for both kernel and user space address causes various kinds of problems. So use the slightly less optimal version that uses an extra bit, but which is guaranteed to be safe everywhere. Fixes:6d04fe15f7
("net: optimize the sockptr_t for unified kernel/user address spaces") Reported-by: Eric Dumazet <edumazet@google.com> Reported-by: John Stultz <john.stultz@linaro.org> Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: David S. Miller <davem@davemloft.net>
105 lines
2.2 KiB
C
105 lines
2.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;
|
|
}
|
|
|
|
static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size)
|
|
{
|
|
return copy_from_sockptr_offset(dst, src, 0, size);
|
|
}
|
|
|
|
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 void *memdup_sockptr(sockptr_t src, size_t len)
|
|
{
|
|
void *p = kmalloc_track_caller(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;
|
|
}
|
|
|
|
static inline void *memdup_sockptr_nul(sockptr_t src, size_t len)
|
|
{
|
|
char *p = kmalloc_track_caller(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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#endif /* _LINUX_SOCKPTR_H */
|