-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a uptr_t type that can hold a pointer to either a user or kernel memory region, and simply helpers to copy to and from it. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: David S. Miller <davem@davemloft.net>
- Loading branch information
Christoph Hellwig
authored and
David S. Miller
committed
Jul 24, 2020
1 parent
d200cf6
commit ba423fd
Showing
1 changed file
with
104 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* 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) | ||
{ | ||
return !sockptr.user && !sockptr.kernel; | ||
} | ||
|
||
static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) | ||
{ | ||
if (!sockptr_is_kernel(src)) | ||
return copy_from_user(dst, src.user, size); | ||
memcpy(dst, src.kernel, size); | ||
return 0; | ||
} | ||
|
||
static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size) | ||
{ | ||
if (!sockptr_is_kernel(dst)) | ||
return copy_to_user(dst.user, src, size); | ||
memcpy(dst.kernel, 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 void sockptr_advance(sockptr_t sockptr, size_t len) | ||
{ | ||
if (sockptr_is_kernel(sockptr)) | ||
sockptr.kernel += len; | ||
else | ||
sockptr.user += len; | ||
} | ||
|
||
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 */ |