Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
00f496c4 KC |
2 | /* |
3 | * This is for all the tests related to logic bugs (e.g. bad dereferences, | |
4 | * bad alignment, bad loops, bad locking, bad scheduling, deep stacks, and | |
5 | * lockups) along with other things that don't fit well into existing LKDTM | |
6 | * test source files. | |
7 | */ | |
00f496c4 | 8 | #include "lkdtm.h" |
6819d101 | 9 | #include <linux/list.h> |
6d2e91a6 | 10 | #include <linux/sched.h> |
e22aa9d7 | 11 | #include <linux/sched/signal.h> |
7b25a85c | 12 | #include <linux/sched/task_stack.h> |
e22aa9d7 | 13 | #include <linux/uaccess.h> |
ae2e1aad | 14 | #include <linux/slab.h> |
00f496c4 | 15 | |
b09511c2 AL |
16 | #ifdef CONFIG_X86_32 |
17 | #include <asm/desc.h> | |
18 | #endif | |
19 | ||
6819d101 KC |
20 | struct lkdtm_list { |
21 | struct list_head node; | |
22 | }; | |
23 | ||
00f496c4 KC |
24 | /* |
25 | * Make sure our attempts to over run the kernel stack doesn't trigger | |
26 | * a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we | |
27 | * recurse past the end of THREAD_SIZE by default. | |
28 | */ | |
29 | #if defined(CONFIG_FRAME_WARN) && (CONFIG_FRAME_WARN > 0) | |
b9bc7b8b | 30 | #define REC_STACK_SIZE (_AC(CONFIG_FRAME_WARN, UL) / 2) |
00f496c4 KC |
31 | #else |
32 | #define REC_STACK_SIZE (THREAD_SIZE / 8) | |
33 | #endif | |
34 | #define REC_NUM_DEFAULT ((THREAD_SIZE / REC_STACK_SIZE) * 2) | |
35 | ||
36 | static int recur_count = REC_NUM_DEFAULT; | |
37 | ||
38 | static DEFINE_SPINLOCK(lock_me_up); | |
39 | ||
24cccab4 KC |
40 | /* |
41 | * Make sure compiler does not optimize this function or stack frame away: | |
42 | * - function marked noinline | |
43 | * - stack variables are marked volatile | |
44 | * - stack variables are written (memset()) and read (pr_info()) | |
45 | * - function has external effects (pr_info()) | |
46 | * */ | |
47 | static int noinline recursive_loop(int remaining) | |
00f496c4 | 48 | { |
24cccab4 | 49 | volatile char buf[REC_STACK_SIZE]; |
00f496c4 | 50 | |
24cccab4 KC |
51 | memset((void *)buf, remaining & 0xFF, sizeof(buf)); |
52 | pr_info("loop %d/%d ...\n", (int)buf[remaining % sizeof(buf)], | |
53 | recur_count); | |
00f496c4 KC |
54 | if (!remaining) |
55 | return 0; | |
56 | else | |
57 | return recursive_loop(remaining - 1); | |
58 | } | |
59 | ||
60 | /* If the depth is negative, use the default, otherwise keep parameter. */ | |
61 | void __init lkdtm_bugs_init(int *recur_param) | |
62 | { | |
63 | if (*recur_param < 0) | |
64 | *recur_param = recur_count; | |
65 | else | |
66 | recur_count = *recur_param; | |
67 | } | |
68 | ||
69 | void lkdtm_PANIC(void) | |
70 | { | |
71 | panic("dumptest"); | |
72 | } | |
73 | ||
74 | void lkdtm_BUG(void) | |
75 | { | |
76 | BUG(); | |
77 | } | |
78 | ||
d32f11ba KC |
79 | static int warn_counter; |
80 | ||
00f496c4 KC |
81 | void lkdtm_WARNING(void) |
82 | { | |
1ee170ea KC |
83 | WARN_ON(++warn_counter); |
84 | } | |
85 | ||
86 | void lkdtm_WARNING_MESSAGE(void) | |
87 | { | |
88 | WARN(1, "Warning message trigger count: %d\n", ++warn_counter); | |
00f496c4 KC |
89 | } |
90 | ||
91 | void lkdtm_EXCEPTION(void) | |
92 | { | |
9e18308a | 93 | *((volatile int *) 0) = 0; |
00f496c4 KC |
94 | } |
95 | ||
96 | void lkdtm_LOOP(void) | |
97 | { | |
98 | for (;;) | |
99 | ; | |
100 | } | |
101 | ||
24cccab4 | 102 | void lkdtm_EXHAUST_STACK(void) |
00f496c4 | 103 | { |
b9bc7b8b | 104 | pr_info("Calling function with %lu frame size to depth %d ...\n", |
24cccab4 KC |
105 | REC_STACK_SIZE, recur_count); |
106 | recursive_loop(recur_count); | |
107 | pr_info("FAIL: survived without exhausting stack?!\n"); | |
00f496c4 KC |
108 | } |
109 | ||
7a11a1d1 AB |
110 | static noinline void __lkdtm_CORRUPT_STACK(void *stack) |
111 | { | |
93e78c6b | 112 | memset(stack, '\xff', 64); |
7a11a1d1 AB |
113 | } |
114 | ||
93e78c6b | 115 | /* This should trip the stack canary, not corrupt the return address. */ |
00f496c4 KC |
116 | noinline void lkdtm_CORRUPT_STACK(void) |
117 | { | |
118 | /* Use default char array length that triggers stack protection. */ | |
93e78c6b KC |
119 | char data[8] __aligned(sizeof(void *)); |
120 | ||
121 | __lkdtm_CORRUPT_STACK(&data); | |
122 | ||
123 | pr_info("Corrupted stack containing char array ...\n"); | |
124 | } | |
125 | ||
126 | /* Same as above but will only get a canary with -fstack-protector-strong */ | |
127 | noinline void lkdtm_CORRUPT_STACK_STRONG(void) | |
128 | { | |
129 | union { | |
130 | unsigned short shorts[4]; | |
131 | unsigned long *ptr; | |
132 | } data __aligned(sizeof(void *)); | |
133 | ||
7a11a1d1 | 134 | __lkdtm_CORRUPT_STACK(&data); |
00f496c4 | 135 | |
93e78c6b | 136 | pr_info("Corrupted stack containing union ...\n"); |
00f496c4 KC |
137 | } |
138 | ||
139 | void lkdtm_UNALIGNED_LOAD_STORE_WRITE(void) | |
140 | { | |
141 | static u8 data[5] __attribute__((aligned(4))) = {1, 2, 3, 4, 5}; | |
142 | u32 *p; | |
143 | u32 val = 0x12345678; | |
144 | ||
145 | p = (u32 *)(data + 1); | |
146 | if (*p == 0) | |
147 | val = 0x87654321; | |
148 | *p = val; | |
149 | } | |
150 | ||
151 | void lkdtm_SOFTLOCKUP(void) | |
152 | { | |
153 | preempt_disable(); | |
154 | for (;;) | |
155 | cpu_relax(); | |
156 | } | |
157 | ||
158 | void lkdtm_HARDLOCKUP(void) | |
159 | { | |
160 | local_irq_disable(); | |
161 | for (;;) | |
162 | cpu_relax(); | |
163 | } | |
164 | ||
165 | void lkdtm_SPINLOCKUP(void) | |
166 | { | |
167 | /* Must be called twice to trigger. */ | |
168 | spin_lock(&lock_me_up); | |
169 | /* Let sparse know we intended to exit holding the lock. */ | |
170 | __release(&lock_me_up); | |
171 | } | |
172 | ||
173 | void lkdtm_HUNG_TASK(void) | |
174 | { | |
175 | set_current_state(TASK_UNINTERRUPTIBLE); | |
176 | schedule(); | |
177 | } | |
178 | ||
ae2e1aad KC |
179 | volatile unsigned int huge = INT_MAX - 2; |
180 | volatile unsigned int ignored; | |
181 | ||
182 | void lkdtm_OVERFLOW_SIGNED(void) | |
183 | { | |
184 | int value; | |
185 | ||
186 | value = huge; | |
187 | pr_info("Normal signed addition ...\n"); | |
188 | value += 1; | |
189 | ignored = value; | |
190 | ||
191 | pr_info("Overflowing signed addition ...\n"); | |
192 | value += 4; | |
193 | ignored = value; | |
194 | } | |
195 | ||
196 | ||
197 | void lkdtm_OVERFLOW_UNSIGNED(void) | |
198 | { | |
199 | unsigned int value; | |
200 | ||
201 | value = huge; | |
202 | pr_info("Normal unsigned addition ...\n"); | |
203 | value += 1; | |
204 | ignored = value; | |
205 | ||
206 | pr_info("Overflowing unsigned addition ...\n"); | |
207 | value += 4; | |
208 | ignored = value; | |
209 | } | |
210 | ||
211 | /* Intentially using old-style flex array definition of 1 byte. */ | |
212 | struct array_bounds_flex_array { | |
213 | int one; | |
214 | int two; | |
215 | char data[1]; | |
216 | }; | |
217 | ||
218 | struct array_bounds { | |
219 | int one; | |
220 | int two; | |
221 | char data[8]; | |
222 | int three; | |
223 | }; | |
224 | ||
225 | void lkdtm_ARRAY_BOUNDS(void) | |
226 | { | |
227 | struct array_bounds_flex_array *not_checked; | |
228 | struct array_bounds *checked; | |
229 | volatile int i; | |
230 | ||
231 | not_checked = kmalloc(sizeof(*not_checked) * 2, GFP_KERNEL); | |
232 | checked = kmalloc(sizeof(*checked) * 2, GFP_KERNEL); | |
233 | ||
234 | pr_info("Array access within bounds ...\n"); | |
235 | /* For both, touch all bytes in the actual member size. */ | |
236 | for (i = 0; i < sizeof(checked->data); i++) | |
237 | checked->data[i] = 'A'; | |
238 | /* | |
239 | * For the uninstrumented flex array member, also touch 1 byte | |
240 | * beyond to verify it is correctly uninstrumented. | |
241 | */ | |
242 | for (i = 0; i < sizeof(not_checked->data) + 1; i++) | |
243 | not_checked->data[i] = 'A'; | |
244 | ||
245 | pr_info("Array access beyond bounds ...\n"); | |
246 | for (i = 0; i < sizeof(checked->data) + 1; i++) | |
247 | checked->data[i] = 'B'; | |
248 | ||
249 | kfree(not_checked); | |
250 | kfree(checked); | |
251 | } | |
252 | ||
6819d101 KC |
253 | void lkdtm_CORRUPT_LIST_ADD(void) |
254 | { | |
255 | /* | |
256 | * Initially, an empty list via LIST_HEAD: | |
257 | * test_head.next = &test_head | |
258 | * test_head.prev = &test_head | |
259 | */ | |
260 | LIST_HEAD(test_head); | |
261 | struct lkdtm_list good, bad; | |
262 | void *target[2] = { }; | |
263 | void *redirection = ⌖ | |
264 | ||
265 | pr_info("attempting good list addition\n"); | |
266 | ||
267 | /* | |
268 | * Adding to the list performs these actions: | |
269 | * test_head.next->prev = &good.node | |
270 | * good.node.next = test_head.next | |
271 | * good.node.prev = test_head | |
272 | * test_head.next = good.node | |
273 | */ | |
274 | list_add(&good.node, &test_head); | |
275 | ||
276 | pr_info("attempting corrupted list addition\n"); | |
277 | /* | |
278 | * In simulating this "write what where" primitive, the "what" is | |
279 | * the address of &bad.node, and the "where" is the address held | |
280 | * by "redirection". | |
281 | */ | |
282 | test_head.next = redirection; | |
283 | list_add(&bad.node, &test_head); | |
284 | ||
285 | if (target[0] == NULL && target[1] == NULL) | |
286 | pr_err("Overwrite did not happen, but no BUG?!\n"); | |
287 | else | |
288 | pr_err("list_add() corruption not detected!\n"); | |
289 | } | |
290 | ||
291 | void lkdtm_CORRUPT_LIST_DEL(void) | |
292 | { | |
293 | LIST_HEAD(test_head); | |
294 | struct lkdtm_list item; | |
295 | void *target[2] = { }; | |
296 | void *redirection = ⌖ | |
297 | ||
298 | list_add(&item.node, &test_head); | |
299 | ||
300 | pr_info("attempting good list removal\n"); | |
301 | list_del(&item.node); | |
302 | ||
303 | pr_info("attempting corrupted list removal\n"); | |
304 | list_add(&item.node, &test_head); | |
305 | ||
306 | /* As with the list_add() test above, this corrupts "next". */ | |
307 | item.node.next = redirection; | |
308 | list_del(&item.node); | |
309 | ||
310 | if (target[0] == NULL && target[1] == NULL) | |
311 | pr_err("Overwrite did not happen, but no BUG?!\n"); | |
312 | else | |
313 | pr_err("list_del() corruption not detected!\n"); | |
314 | } | |
e22aa9d7 | 315 | |
7b25a85c | 316 | /* Test if unbalanced set_fs(KERNEL_DS)/set_fs(USER_DS) check exists. */ |
e22aa9d7 KC |
317 | void lkdtm_CORRUPT_USER_DS(void) |
318 | { | |
319 | pr_info("setting bad task size limit\n"); | |
320 | set_fs(KERNEL_DS); | |
321 | ||
322 | /* Make sure we do not keep running with a KERNEL_DS! */ | |
3cf5d076 | 323 | force_sig(SIGKILL); |
e22aa9d7 | 324 | } |
7b25a85c KC |
325 | |
326 | /* Test that VMAP_STACK is actually allocating with a leading guard page */ | |
327 | void lkdtm_STACK_GUARD_PAGE_LEADING(void) | |
328 | { | |
329 | const unsigned char *stack = task_stack_page(current); | |
330 | const unsigned char *ptr = stack - 1; | |
331 | volatile unsigned char byte; | |
332 | ||
333 | pr_info("attempting bad read from page below current stack\n"); | |
334 | ||
335 | byte = *ptr; | |
336 | ||
337 | pr_err("FAIL: accessed page before stack!\n"); | |
338 | } | |
339 | ||
340 | /* Test that VMAP_STACK is actually allocating with a trailing guard page */ | |
341 | void lkdtm_STACK_GUARD_PAGE_TRAILING(void) | |
342 | { | |
343 | const unsigned char *stack = task_stack_page(current); | |
344 | const unsigned char *ptr = stack + THREAD_SIZE; | |
345 | volatile unsigned char byte; | |
346 | ||
347 | pr_info("attempting bad read from page above current stack\n"); | |
348 | ||
349 | byte = *ptr; | |
350 | ||
351 | pr_err("FAIL: accessed page after stack!\n"); | |
352 | } | |
06b32fdb KC |
353 | |
354 | void lkdtm_UNSET_SMEP(void) | |
355 | { | |
0e31e357 | 356 | #if IS_ENABLED(CONFIG_X86_64) && !IS_ENABLED(CONFIG_UML) |
06b32fdb KC |
357 | #define MOV_CR4_DEPTH 64 |
358 | void (*direct_write_cr4)(unsigned long val); | |
359 | unsigned char *insn; | |
360 | unsigned long cr4; | |
361 | int i; | |
362 | ||
363 | cr4 = native_read_cr4(); | |
364 | ||
365 | if ((cr4 & X86_CR4_SMEP) != X86_CR4_SMEP) { | |
366 | pr_err("FAIL: SMEP not in use\n"); | |
367 | return; | |
368 | } | |
369 | cr4 &= ~(X86_CR4_SMEP); | |
370 | ||
371 | pr_info("trying to clear SMEP normally\n"); | |
372 | native_write_cr4(cr4); | |
373 | if (cr4 == native_read_cr4()) { | |
374 | pr_err("FAIL: pinning SMEP failed!\n"); | |
375 | cr4 |= X86_CR4_SMEP; | |
376 | pr_info("restoring SMEP\n"); | |
377 | native_write_cr4(cr4); | |
378 | return; | |
379 | } | |
380 | pr_info("ok: SMEP did not get cleared\n"); | |
381 | ||
382 | /* | |
383 | * To test the post-write pinning verification we need to call | |
384 | * directly into the middle of native_write_cr4() where the | |
385 | * cr4 write happens, skipping any pinning. This searches for | |
386 | * the cr4 writing instruction. | |
387 | */ | |
388 | insn = (unsigned char *)native_write_cr4; | |
389 | for (i = 0; i < MOV_CR4_DEPTH; i++) { | |
390 | /* mov %rdi, %cr4 */ | |
391 | if (insn[i] == 0x0f && insn[i+1] == 0x22 && insn[i+2] == 0xe7) | |
392 | break; | |
393 | /* mov %rdi,%rax; mov %rax, %cr4 */ | |
394 | if (insn[i] == 0x48 && insn[i+1] == 0x89 && | |
395 | insn[i+2] == 0xf8 && insn[i+3] == 0x0f && | |
396 | insn[i+4] == 0x22 && insn[i+5] == 0xe0) | |
397 | break; | |
398 | } | |
399 | if (i >= MOV_CR4_DEPTH) { | |
400 | pr_info("ok: cannot locate cr4 writing call gadget\n"); | |
401 | return; | |
402 | } | |
403 | direct_write_cr4 = (void *)(insn + i); | |
404 | ||
405 | pr_info("trying to clear SMEP with call gadget\n"); | |
406 | direct_write_cr4(cr4); | |
407 | if (native_read_cr4() & X86_CR4_SMEP) { | |
408 | pr_info("ok: SMEP removal was reverted\n"); | |
409 | } else { | |
410 | pr_err("FAIL: cleared SMEP not detected!\n"); | |
411 | cr4 |= X86_CR4_SMEP; | |
412 | pr_info("restoring SMEP\n"); | |
413 | native_write_cr4(cr4); | |
414 | } | |
415 | #else | |
cea23efb | 416 | pr_err("XFAIL: this test is x86_64-only\n"); |
06b32fdb KC |
417 | #endif |
418 | } | |
b09511c2 | 419 | |
b09511c2 AL |
420 | void lkdtm_DOUBLE_FAULT(void) |
421 | { | |
cea23efb | 422 | #ifdef CONFIG_X86_32 |
b09511c2 AL |
423 | /* |
424 | * Trigger #DF by setting the stack limit to zero. This clobbers | |
425 | * a GDT TLS slot, which is okay because the current task will die | |
426 | * anyway due to the double fault. | |
427 | */ | |
428 | struct desc_struct d = { | |
429 | .type = 3, /* expand-up, writable, accessed data */ | |
430 | .p = 1, /* present */ | |
431 | .d = 1, /* 32-bit */ | |
432 | .g = 0, /* limit in bytes */ | |
433 | .s = 1, /* not system */ | |
434 | }; | |
435 | ||
436 | local_irq_disable(); | |
437 | write_gdt_entry(get_cpu_gdt_rw(smp_processor_id()), | |
438 | GDT_ENTRY_TLS_MIN, &d, DESCTYPE_S); | |
439 | ||
440 | /* | |
441 | * Put our zero-limit segment in SS and then trigger a fault. The | |
442 | * 4-byte access to (%esp) will fault with #SS, and the attempt to | |
443 | * deliver the fault will recursively cause #SS and result in #DF. | |
444 | * This whole process happens while NMIs and MCEs are blocked by the | |
445 | * MOV SS window. This is nice because an NMI with an invalid SS | |
446 | * would also double-fault, resulting in the NMI or MCE being lost. | |
447 | */ | |
448 | asm volatile ("movw %0, %%ss; addl $0, (%%esp)" :: | |
449 | "r" ((unsigned short)(GDT_ENTRY_TLS_MIN << 3))); | |
450 | ||
cea23efb KC |
451 | pr_err("FAIL: tried to double fault but didn't die\n"); |
452 | #else | |
453 | pr_err("XFAIL: this test is ia32-only\n"); | |
b09511c2 | 454 | #endif |
cea23efb | 455 | } |
6cb6982f ADK |
456 | |
457 | #ifdef CONFIG_ARM64_PTR_AUTH | |
458 | static noinline void change_pac_parameters(void) | |
459 | { | |
460 | /* Reset the keys of current task */ | |
461 | ptrauth_thread_init_kernel(current); | |
462 | ptrauth_thread_switch_kernel(current); | |
463 | } | |
464 | ||
465 | #define CORRUPT_PAC_ITERATE 10 | |
466 | noinline void lkdtm_CORRUPT_PAC(void) | |
467 | { | |
468 | int i; | |
469 | ||
470 | if (!system_supports_address_auth()) { | |
471 | pr_err("FAIL: arm64 pointer authentication feature not present\n"); | |
472 | return; | |
473 | } | |
474 | ||
475 | pr_info("Change the PAC parameters to force function return failure\n"); | |
476 | /* | |
477 | * Pac is a hash value computed from input keys, return address and | |
478 | * stack pointer. As pac has fewer bits so there is a chance of | |
479 | * collision, so iterate few times to reduce the collision probability. | |
480 | */ | |
481 | for (i = 0; i < CORRUPT_PAC_ITERATE; i++) | |
482 | change_pac_parameters(); | |
483 | ||
484 | pr_err("FAIL: %s test failed. Kernel may be unstable from here\n", __func__); | |
485 | } | |
486 | #else /* !CONFIG_ARM64_PTR_AUTH */ | |
487 | noinline void lkdtm_CORRUPT_PAC(void) | |
488 | { | |
489 | pr_err("FAIL: arm64 pointer authentication config disabled\n"); | |
490 | } | |
491 | #endif |