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 | static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) |
54 | { |
55 | return copy_from_sockptr_offset(dst, src, offset: 0, size); |
56 | } |
57 | |
58 | static inline int copy_struct_from_sockptr(void *dst, size_t ksize, |
59 | sockptr_t src, size_t usize) |
60 | { |
61 | size_t size = min(ksize, usize); |
62 | size_t rest = max(ksize, usize) - size; |
63 | |
64 | if (!sockptr_is_kernel(sockptr: src)) |
65 | return copy_struct_from_user(dst, ksize, src: src.user, usize: size); |
66 | |
67 | if (usize < ksize) { |
68 | memset(dst + size, 0, rest); |
69 | } else if (usize > ksize) { |
70 | char *p = src.kernel; |
71 | |
72 | while (rest--) { |
73 | if (*p++) |
74 | return -E2BIG; |
75 | } |
76 | } |
77 | memcpy(dst, src.kernel, size); |
78 | return 0; |
79 | } |
80 | |
81 | static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, |
82 | const void *src, size_t size) |
83 | { |
84 | if (!sockptr_is_kernel(sockptr: dst)) |
85 | return copy_to_user(to: dst.user + offset, from: src, n: size); |
86 | memcpy(dst.kernel + offset, src, size); |
87 | return 0; |
88 | } |
89 | |
90 | static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size) |
91 | { |
92 | return copy_to_sockptr_offset(dst, offset: 0, src, size); |
93 | } |
94 | |
95 | static inline void *memdup_sockptr(sockptr_t src, size_t len) |
96 | { |
97 | void *p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN); |
98 | |
99 | if (!p) |
100 | return ERR_PTR(error: -ENOMEM); |
101 | if (copy_from_sockptr(dst: p, src, size: len)) { |
102 | kfree(objp: p); |
103 | return ERR_PTR(error: -EFAULT); |
104 | } |
105 | return p; |
106 | } |
107 | |
108 | static inline void *memdup_sockptr_nul(sockptr_t src, size_t len) |
109 | { |
110 | char *p = kmalloc_track_caller(len + 1, GFP_KERNEL); |
111 | |
112 | if (!p) |
113 | return ERR_PTR(error: -ENOMEM); |
114 | if (copy_from_sockptr(dst: p, src, size: len)) { |
115 | kfree(objp: p); |
116 | return ERR_PTR(error: -EFAULT); |
117 | } |
118 | p[len] = '\0'; |
119 | return p; |
120 | } |
121 | |
122 | static inline long strncpy_from_sockptr(char *dst, sockptr_t src, size_t count) |
123 | { |
124 | if (sockptr_is_kernel(sockptr: src)) { |
125 | size_t len = min(strnlen(src.kernel, count - 1) + 1, count); |
126 | |
127 | memcpy(dst, src.kernel, len); |
128 | return len; |
129 | } |
130 | return strncpy_from_user(dst, src: src.user, count); |
131 | } |
132 | |
133 | static inline int check_zeroed_sockptr(sockptr_t src, size_t offset, |
134 | size_t size) |
135 | { |
136 | if (!sockptr_is_kernel(sockptr: src)) |
137 | return check_zeroed_user(from: src.user + offset, size); |
138 | return memchr_inv(p: src.kernel + offset, c: 0, size) == NULL; |
139 | } |
140 | |
141 | #endif /* _LINUX_SOCKPTR_H */ |
142 | |