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/slab.h> |
12 | #include <linux/uaccess.h> |
13 | |
14 | typedef struct { |
15 | union { |
16 | void *kernel; |
17 | void __user *user; |
18 | }; |
19 | bool is_kernel : 1; |
20 | } sockptr_t; |
21 | |
22 | static inline bool sockptr_is_kernel(sockptr_t sockptr) |
23 | { |
24 | return sockptr.is_kernel; |
25 | } |
26 | |
27 | static inline sockptr_t KERNEL_SOCKPTR(void *p) |
28 | { |
29 | return (sockptr_t) { .kernel = p, .is_kernel = true }; |
30 | } |
31 | |
32 | static inline sockptr_t USER_SOCKPTR(void __user *p) |
33 | { |
34 | return (sockptr_t) { .user = p }; |
35 | } |
36 | |
37 | static inline bool sockptr_is_null(sockptr_t sockptr) |
38 | { |
39 | if (sockptr_is_kernel(sockptr)) |
40 | return !sockptr.kernel; |
41 | return !sockptr.user; |
42 | } |
43 | |
44 | static inline int copy_from_sockptr_offset(void *dst, sockptr_t src, |
45 | size_t offset, size_t size) |
46 | { |
47 | if (!sockptr_is_kernel(sockptr: src)) |
48 | return copy_from_user(to: dst, from: src.user + offset, n: size); |
49 | memcpy(dst, src.kernel + offset, size); |
50 | return 0; |
51 | } |
52 | |
53 | /* Deprecated. |
54 | * This is unsafe, unless caller checked user provided optlen. |
55 | * Prefer copy_safe_from_sockptr() instead. |
56 | */ |
57 | static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) |
58 | { |
59 | return copy_from_sockptr_offset(dst, src, offset: 0, size); |
60 | } |
61 | |
62 | /** |
63 | * copy_safe_from_sockptr: copy a struct from sockptr |
64 | * @dst: Destination address, in kernel space. This buffer must be @ksize |
65 | * bytes long. |
66 | * @ksize: Size of @dst struct. |
67 | * @optval: Source address. (in user or kernel space) |
68 | * @optlen: Size of @optval data. |
69 | * |
70 | * Returns: |
71 | * * -EINVAL: @optlen < @ksize |
72 | * * -EFAULT: access to userspace failed. |
73 | * * 0 : @ksize bytes were copied |
74 | */ |
75 | static inline int copy_safe_from_sockptr(void *dst, size_t ksize, |
76 | sockptr_t optval, unsigned int optlen) |
77 | { |
78 | if (optlen < ksize) |
79 | return -EINVAL; |
80 | return copy_from_sockptr(dst, src: optval, size: ksize); |
81 | } |
82 | |
83 | static inline int copy_struct_from_sockptr(void *dst, size_t ksize, |
84 | sockptr_t src, size_t usize) |
85 | { |
86 | size_t size = min(ksize, usize); |
87 | size_t rest = max(ksize, usize) - size; |
88 | |
89 | if (!sockptr_is_kernel(sockptr: src)) |
90 | return copy_struct_from_user(dst, ksize, src: src.user, usize: size); |
91 | |
92 | if (usize < ksize) { |
93 | memset(dst + size, 0, rest); |
94 | } else if (usize > ksize) { |
95 | char *p = src.kernel; |
96 | |
97 | while (rest--) { |
98 | if (*p++) |
99 | return -E2BIG; |
100 | } |
101 | } |
102 | memcpy(dst, src.kernel, size); |
103 | return 0; |
104 | } |
105 | |
106 | static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, |
107 | const void *src, size_t size) |
108 | { |
109 | if (!sockptr_is_kernel(sockptr: dst)) |
110 | return copy_to_user(to: dst.user + offset, from: src, n: size); |
111 | memcpy(dst.kernel + offset, src, size); |
112 | return 0; |
113 | } |
114 | |
115 | static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size) |
116 | { |
117 | return copy_to_sockptr_offset(dst, offset: 0, src, size); |
118 | } |
119 | |
120 | static inline void *memdup_sockptr(sockptr_t src, size_t len) |
121 | { |
122 | void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN); |
123 | |
124 | if (!p) |
125 | return ERR_PTR(error: -ENOMEM); |
126 | if (copy_from_sockptr(dst: p, src, size: len)) { |
127 | kfree(objp: p); |
128 | return ERR_PTR(error: -EFAULT); |
129 | } |
130 | return p; |
131 | } |
132 | |
133 | static inline void *memdup_sockptr_nul(sockptr_t src, size_t len) |
134 | { |
135 | char *p = kmalloc_track_caller(len + 1, GFP_KERNEL); |
136 | |
137 | if (!p) |
138 | return ERR_PTR(error: -ENOMEM); |
139 | if (copy_from_sockptr(dst: p, src, size: len)) { |
140 | kfree(objp: p); |
141 | return ERR_PTR(error: -EFAULT); |
142 | } |
143 | p[len] = '\0'; |
144 | return p; |
145 | } |
146 | |
147 | static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count) |
148 | { |
149 | if (sockptr_is_kernel(sockptr: src)) { |
150 | size_t len = min(strnlen(src.kernel, count - 1) + 1, count); |
151 | |
152 | memcpy(dst, src.kernel, len); |
153 | return len; |
154 | } |
155 | return strncpy_from_user(dst, src: src.user, count); |
156 | } |
157 | |
158 | static inline int check_zeroed_sockptr(sockptr_t src, size_t offset, |
159 | size_t size) |
160 | { |
161 | if (!sockptr_is_kernel(sockptr: src)) |
162 | return check_zeroed_user(from: src.user + offset, size); |
163 | return memchr_inv(p: src.kernel + offset, c: 0, size) == NULL; |
164 | } |
165 | |
166 | #endif /* _LINUX_SOCKPTR_H */ |
167 | |