1 /* SPDX-License-Identifier: GPL-2.0-only */ 2 /* 3 * Copyright (c) 2020 Christoph Hellwig. 4 * 5 * Support for "universal" pointers that can point to either kernel or userspace 6 * memory. 7 */ 8 #ifndef _LINUX_SOCKPTR_H 9 #define _LINUX_SOCKPTR_H 10 11 #include <linux/compiler.h> 12 #include <linux/slab.h> 13 #include <linux/uaccess.h> 14 15 #ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE 16 typedef union { 17 void *kernel; 18 void __user *user; 19 } sockptr_t; 20 21 static inline bool sockptr_is_kernel(sockptr_t sockptr) 22 { 23 return (unsigned long)sockptr.kernel >= TASK_SIZE; 24 } 25 26 static inline sockptr_t KERNEL_SOCKPTR(void *p) 27 { 28 return (sockptr_t) { .kernel = p }; 29 } 30 #else /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */ 31 typedef struct { 32 union { 33 void *kernel; 34 void __user *user; 35 }; 36 bool is_kernel : 1; 37 } sockptr_t; 38 39 static inline bool sockptr_is_kernel(sockptr_t sockptr) 40 { 41 return sockptr.is_kernel; 42 } 43 44 static inline sockptr_t KERNEL_SOCKPTR(void *p) 45 { 46 return (sockptr_t) { .kernel = p, .is_kernel = true }; 47 } 48 #endif /* CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE */ 49 50 static inline int __must_check init_user_sockptr(sockptr_t *sp, void __user *p, 51 size_t size) 52 { 53 if (!access_ok(p, size)) 54 return -EFAULT; 55 *sp = (sockptr_t) { .user = p }; 56 return 0; 57 } 58 59 static inline bool sockptr_is_null(sockptr_t sockptr) 60 { 61 if (sockptr_is_kernel(sockptr)) 62 return !sockptr.kernel; 63 return !sockptr.user; 64 } 65 66 static inline int copy_from_sockptr_offset(void *dst, sockptr_t src, 67 size_t offset, size_t size) 68 { 69 if (!sockptr_is_kernel(src)) 70 return copy_from_user(dst, src.user + offset, size); 71 memcpy(dst, src.kernel + offset, size); 72 return 0; 73 } 74 75 static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) 76 { 77 return copy_from_sockptr_offset(dst, src, 0, size); 78 } 79 80 static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, 81 const void *src, size_t size) 82 { 83 if (!sockptr_is_kernel(dst)) 84 return copy_to_user(dst.user + offset, src, size); 85 memcpy(dst.kernel + offset, src, size); 86 return 0; 87 } 88 89 static inline void *memdup_sockptr(sockptr_t src, size_t len) 90 { 91 void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN); 92 93 if (!p) 94 return ERR_PTR(-ENOMEM); 95 if (copy_from_sockptr(p, src, len)) { 96 kfree(p); 97 return ERR_PTR(-EFAULT); 98 } 99 return p; 100 } 101 102 static inline void *memdup_sockptr_nul(sockptr_t src, size_t len) 103 { 104 char *p = kmalloc_track_caller(len + 1, GFP_KERNEL); 105 106 if (!p) 107 return ERR_PTR(-ENOMEM); 108 if (copy_from_sockptr(p, src, len)) { 109 kfree(p); 110 return ERR_PTR(-EFAULT); 111 } 112 p[len] = '\0'; 113 return p; 114 } 115 116 static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count) 117 { 118 if (sockptr_is_kernel(src)) { 119 size_t len = min(strnlen(src.kernel, count - 1) + 1, count); 120 121 memcpy(dst, src.kernel, len); 122 return len; 123 } 124 return strncpy_from_user(dst, src.user, count); 125 } 126 127 #endif /* _LINUX_SOCKPTR_H */ 128