Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
a3dff71c KC |
2 | /* |
3 | * This is for all the tests related to copy_to_user() and copy_from_user() | |
4 | * hardening. | |
5 | */ | |
6d2e91a6 | 6 | #include "lkdtm.h" |
a3dff71c KC |
7 | #include <linux/slab.h> |
8 | #include <linux/vmalloc.h> | |
68db0cf1 | 9 | #include <linux/sched/task_stack.h> |
a3dff71c KC |
10 | #include <linux/mman.h> |
11 | #include <linux/uaccess.h> | |
12 | #include <asm/cacheflush.h> | |
13 | ||
3c17648c KC |
14 | /* |
15 | * Many of the tests here end up using const sizes, but those would | |
16 | * normally be ignored by hardened usercopy, so force the compiler | |
17 | * into choosing the non-const path to make sure we trigger the | |
18 | * hardened usercopy checks by added "unconst" to all the const copies, | |
19 | * and making sure "cache_size" isn't optimized into a const. | |
20 | */ | |
21 | static volatile size_t unconst = 0; | |
22 | static volatile size_t cache_size = 1024; | |
e47e3118 | 23 | static struct kmem_cache *whitelist_cache; |
a3dff71c KC |
24 | |
25 | static const unsigned char test_text[] = "This is a test.\n"; | |
26 | ||
27 | /* | |
28 | * Instead of adding -Wno-return-local-addr, just pass the stack address | |
29 | * through a function to obfuscate it from the compiler. | |
30 | */ | |
31 | static noinline unsigned char *trick_compiler(unsigned char *stack) | |
32 | { | |
33 | return stack + 0; | |
34 | } | |
35 | ||
36 | static noinline unsigned char *do_usercopy_stack_callee(int value) | |
37 | { | |
38 | unsigned char buf[32]; | |
39 | int i; | |
40 | ||
41 | /* Exercise stack to avoid everything living in registers. */ | |
42 | for (i = 0; i < sizeof(buf); i++) { | |
43 | buf[i] = value & 0xff; | |
44 | } | |
45 | ||
46 | return trick_compiler(buf); | |
47 | } | |
48 | ||
49 | static noinline void do_usercopy_stack(bool to_user, bool bad_frame) | |
50 | { | |
51 | unsigned long user_addr; | |
52 | unsigned char good_stack[32]; | |
53 | unsigned char *bad_stack; | |
54 | int i; | |
55 | ||
56 | /* Exercise stack to avoid everything living in registers. */ | |
57 | for (i = 0; i < sizeof(good_stack); i++) | |
58 | good_stack[i] = test_text[i % sizeof(test_text)]; | |
59 | ||
60 | /* This is a pointer to outside our current stack frame. */ | |
61 | if (bad_frame) { | |
30010874 | 62 | bad_stack = do_usercopy_stack_callee((uintptr_t)&bad_stack); |
a3dff71c KC |
63 | } else { |
64 | /* Put start address just inside stack. */ | |
65 | bad_stack = task_stack_page(current) + THREAD_SIZE; | |
66 | bad_stack -= sizeof(unsigned long); | |
67 | } | |
68 | ||
69 | user_addr = vm_mmap(NULL, 0, PAGE_SIZE, | |
70 | PROT_READ | PROT_WRITE | PROT_EXEC, | |
71 | MAP_ANONYMOUS | MAP_PRIVATE, 0); | |
72 | if (user_addr >= TASK_SIZE) { | |
73 | pr_warn("Failed to allocate user memory\n"); | |
74 | return; | |
75 | } | |
76 | ||
77 | if (to_user) { | |
78 | pr_info("attempting good copy_to_user of local stack\n"); | |
79 | if (copy_to_user((void __user *)user_addr, good_stack, | |
3c17648c | 80 | unconst + sizeof(good_stack))) { |
a3dff71c KC |
81 | pr_warn("copy_to_user failed unexpectedly?!\n"); |
82 | goto free_user; | |
83 | } | |
84 | ||
85 | pr_info("attempting bad copy_to_user of distant stack\n"); | |
86 | if (copy_to_user((void __user *)user_addr, bad_stack, | |
3c17648c | 87 | unconst + sizeof(good_stack))) { |
a3dff71c KC |
88 | pr_warn("copy_to_user failed, but lacked Oops\n"); |
89 | goto free_user; | |
90 | } | |
91 | } else { | |
92 | /* | |
93 | * There isn't a safe way to not be protected by usercopy | |
94 | * if we're going to write to another thread's stack. | |
95 | */ | |
96 | if (!bad_frame) | |
97 | goto free_user; | |
98 | ||
99 | pr_info("attempting good copy_from_user of local stack\n"); | |
100 | if (copy_from_user(good_stack, (void __user *)user_addr, | |
3c17648c | 101 | unconst + sizeof(good_stack))) { |
a3dff71c KC |
102 | pr_warn("copy_from_user failed unexpectedly?!\n"); |
103 | goto free_user; | |
104 | } | |
105 | ||
106 | pr_info("attempting bad copy_from_user of distant stack\n"); | |
107 | if (copy_from_user(bad_stack, (void __user *)user_addr, | |
3c17648c | 108 | unconst + sizeof(good_stack))) { |
a3dff71c KC |
109 | pr_warn("copy_from_user failed, but lacked Oops\n"); |
110 | goto free_user; | |
111 | } | |
112 | } | |
113 | ||
114 | free_user: | |
115 | vm_munmap(user_addr, PAGE_SIZE); | |
116 | } | |
117 | ||
e47e3118 KC |
118 | /* |
119 | * This checks for whole-object size validation with hardened usercopy, | |
120 | * with or without usercopy whitelisting. | |
121 | */ | |
a3dff71c KC |
122 | static void do_usercopy_heap_size(bool to_user) |
123 | { | |
124 | unsigned long user_addr; | |
125 | unsigned char *one, *two; | |
c7588686 KC |
126 | void __user *test_user_addr; |
127 | void *test_kern_addr; | |
3c17648c | 128 | size_t size = unconst + 1024; |
a3dff71c KC |
129 | |
130 | one = kmalloc(size, GFP_KERNEL); | |
131 | two = kmalloc(size, GFP_KERNEL); | |
132 | if (!one || !two) { | |
133 | pr_warn("Failed to allocate kernel memory\n"); | |
134 | goto free_kernel; | |
135 | } | |
136 | ||
137 | user_addr = vm_mmap(NULL, 0, PAGE_SIZE, | |
138 | PROT_READ | PROT_WRITE | PROT_EXEC, | |
139 | MAP_ANONYMOUS | MAP_PRIVATE, 0); | |
140 | if (user_addr >= TASK_SIZE) { | |
141 | pr_warn("Failed to allocate user memory\n"); | |
142 | goto free_kernel; | |
143 | } | |
144 | ||
145 | memset(one, 'A', size); | |
146 | memset(two, 'B', size); | |
147 | ||
c7588686 KC |
148 | test_user_addr = (void __user *)(user_addr + 16); |
149 | test_kern_addr = one + 16; | |
150 | ||
a3dff71c KC |
151 | if (to_user) { |
152 | pr_info("attempting good copy_to_user of correct size\n"); | |
c7588686 | 153 | if (copy_to_user(test_user_addr, test_kern_addr, size / 2)) { |
a3dff71c KC |
154 | pr_warn("copy_to_user failed unexpectedly?!\n"); |
155 | goto free_user; | |
156 | } | |
157 | ||
158 | pr_info("attempting bad copy_to_user of too large size\n"); | |
c7588686 | 159 | if (copy_to_user(test_user_addr, test_kern_addr, size)) { |
a3dff71c KC |
160 | pr_warn("copy_to_user failed, but lacked Oops\n"); |
161 | goto free_user; | |
162 | } | |
163 | } else { | |
164 | pr_info("attempting good copy_from_user of correct size\n"); | |
c7588686 | 165 | if (copy_from_user(test_kern_addr, test_user_addr, size / 2)) { |
a3dff71c KC |
166 | pr_warn("copy_from_user failed unexpectedly?!\n"); |
167 | goto free_user; | |
168 | } | |
169 | ||
170 | pr_info("attempting bad copy_from_user of too large size\n"); | |
c7588686 | 171 | if (copy_from_user(test_kern_addr, test_user_addr, size)) { |
a3dff71c KC |
172 | pr_warn("copy_from_user failed, but lacked Oops\n"); |
173 | goto free_user; | |
174 | } | |
175 | } | |
176 | ||
177 | free_user: | |
178 | vm_munmap(user_addr, PAGE_SIZE); | |
179 | free_kernel: | |
180 | kfree(one); | |
181 | kfree(two); | |
182 | } | |
183 | ||
e47e3118 KC |
184 | /* |
185 | * This checks for the specific whitelist window within an object. If this | |
186 | * test passes, then do_usercopy_heap_size() tests will pass too. | |
187 | */ | |
188 | static void do_usercopy_heap_whitelist(bool to_user) | |
a3dff71c | 189 | { |
e47e3118 KC |
190 | unsigned long user_alloc; |
191 | unsigned char *buf = NULL; | |
192 | unsigned char __user *user_addr; | |
193 | size_t offset, size; | |
a3dff71c KC |
194 | |
195 | /* Make sure cache was prepared. */ | |
e47e3118 | 196 | if (!whitelist_cache) { |
a3dff71c KC |
197 | pr_warn("Failed to allocate kernel cache\n"); |
198 | return; | |
199 | } | |
200 | ||
201 | /* | |
e47e3118 | 202 | * Allocate a buffer with a whitelisted window in the buffer. |
a3dff71c | 203 | */ |
e47e3118 KC |
204 | buf = kmem_cache_alloc(whitelist_cache, GFP_KERNEL); |
205 | if (!buf) { | |
206 | pr_warn("Failed to allocate buffer from whitelist cache\n"); | |
a3dff71c KC |
207 | goto free_alloc; |
208 | } | |
209 | ||
210 | /* Allocate user memory we'll poke at. */ | |
e47e3118 | 211 | user_alloc = vm_mmap(NULL, 0, PAGE_SIZE, |
a3dff71c KC |
212 | PROT_READ | PROT_WRITE | PROT_EXEC, |
213 | MAP_ANONYMOUS | MAP_PRIVATE, 0); | |
e47e3118 | 214 | if (user_alloc >= TASK_SIZE) { |
a3dff71c KC |
215 | pr_warn("Failed to allocate user memory\n"); |
216 | goto free_alloc; | |
217 | } | |
e47e3118 | 218 | user_addr = (void __user *)user_alloc; |
a3dff71c | 219 | |
e47e3118 KC |
220 | memset(buf, 'B', cache_size); |
221 | ||
222 | /* Whitelisted window in buffer, from kmem_cache_create_usercopy. */ | |
223 | offset = (cache_size / 4) + unconst; | |
224 | size = (cache_size / 16) + unconst; | |
a3dff71c KC |
225 | |
226 | if (to_user) { | |
e47e3118 KC |
227 | pr_info("attempting good copy_to_user inside whitelist\n"); |
228 | if (copy_to_user(user_addr, buf + offset, size)) { | |
a3dff71c KC |
229 | pr_warn("copy_to_user failed unexpectedly?!\n"); |
230 | goto free_user; | |
231 | } | |
232 | ||
e47e3118 KC |
233 | pr_info("attempting bad copy_to_user outside whitelist\n"); |
234 | if (copy_to_user(user_addr, buf + offset - 1, size)) { | |
a3dff71c KC |
235 | pr_warn("copy_to_user failed, but lacked Oops\n"); |
236 | goto free_user; | |
237 | } | |
238 | } else { | |
e47e3118 KC |
239 | pr_info("attempting good copy_from_user inside whitelist\n"); |
240 | if (copy_from_user(buf + offset, user_addr, size)) { | |
a3dff71c KC |
241 | pr_warn("copy_from_user failed unexpectedly?!\n"); |
242 | goto free_user; | |
243 | } | |
244 | ||
e47e3118 KC |
245 | pr_info("attempting bad copy_from_user outside whitelist\n"); |
246 | if (copy_from_user(buf + offset - 1, user_addr, size)) { | |
a3dff71c KC |
247 | pr_warn("copy_from_user failed, but lacked Oops\n"); |
248 | goto free_user; | |
249 | } | |
250 | } | |
251 | ||
252 | free_user: | |
e47e3118 | 253 | vm_munmap(user_alloc, PAGE_SIZE); |
a3dff71c | 254 | free_alloc: |
e47e3118 KC |
255 | if (buf) |
256 | kmem_cache_free(whitelist_cache, buf); | |
a3dff71c KC |
257 | } |
258 | ||
259 | /* Callable tests. */ | |
260 | void lkdtm_USERCOPY_HEAP_SIZE_TO(void) | |
261 | { | |
262 | do_usercopy_heap_size(true); | |
263 | } | |
264 | ||
265 | void lkdtm_USERCOPY_HEAP_SIZE_FROM(void) | |
266 | { | |
267 | do_usercopy_heap_size(false); | |
268 | } | |
269 | ||
e47e3118 | 270 | void lkdtm_USERCOPY_HEAP_WHITELIST_TO(void) |
a3dff71c | 271 | { |
e47e3118 | 272 | do_usercopy_heap_whitelist(true); |
a3dff71c KC |
273 | } |
274 | ||
e47e3118 | 275 | void lkdtm_USERCOPY_HEAP_WHITELIST_FROM(void) |
a3dff71c | 276 | { |
e47e3118 | 277 | do_usercopy_heap_whitelist(false); |
a3dff71c KC |
278 | } |
279 | ||
280 | void lkdtm_USERCOPY_STACK_FRAME_TO(void) | |
281 | { | |
282 | do_usercopy_stack(true, true); | |
283 | } | |
284 | ||
285 | void lkdtm_USERCOPY_STACK_FRAME_FROM(void) | |
286 | { | |
287 | do_usercopy_stack(false, true); | |
288 | } | |
289 | ||
290 | void lkdtm_USERCOPY_STACK_BEYOND(void) | |
291 | { | |
292 | do_usercopy_stack(true, false); | |
293 | } | |
294 | ||
295 | void lkdtm_USERCOPY_KERNEL(void) | |
296 | { | |
297 | unsigned long user_addr; | |
298 | ||
299 | user_addr = vm_mmap(NULL, 0, PAGE_SIZE, | |
300 | PROT_READ | PROT_WRITE | PROT_EXEC, | |
301 | MAP_ANONYMOUS | MAP_PRIVATE, 0); | |
302 | if (user_addr >= TASK_SIZE) { | |
303 | pr_warn("Failed to allocate user memory\n"); | |
304 | return; | |
305 | } | |
306 | ||
307 | pr_info("attempting good copy_to_user from kernel rodata\n"); | |
308 | if (copy_to_user((void __user *)user_addr, test_text, | |
3c17648c | 309 | unconst + sizeof(test_text))) { |
a3dff71c KC |
310 | pr_warn("copy_to_user failed unexpectedly?!\n"); |
311 | goto free_user; | |
312 | } | |
313 | ||
314 | pr_info("attempting bad copy_to_user from kernel text\n"); | |
3c17648c KC |
315 | if (copy_to_user((void __user *)user_addr, vm_mmap, |
316 | unconst + PAGE_SIZE)) { | |
a3dff71c KC |
317 | pr_warn("copy_to_user failed, but lacked Oops\n"); |
318 | goto free_user; | |
319 | } | |
320 | ||
321 | free_user: | |
322 | vm_munmap(user_addr, PAGE_SIZE); | |
323 | } | |
324 | ||
325 | void __init lkdtm_usercopy_init(void) | |
326 | { | |
327 | /* Prepare cache that lacks SLAB_USERCOPY flag. */ | |
e47e3118 KC |
328 | whitelist_cache = |
329 | kmem_cache_create_usercopy("lkdtm-usercopy", cache_size, | |
330 | 0, 0, | |
331 | cache_size / 4, | |
332 | cache_size / 16, | |
333 | NULL); | |
a3dff71c KC |
334 | } |
335 | ||
336 | void __exit lkdtm_usercopy_exit(void) | |
337 | { | |
e47e3118 | 338 | kmem_cache_destroy(whitelist_cache); |
a3dff71c | 339 | } |