From 9a49a528dcf3c2022ff89f700d5d0345b9abf288 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 22 Feb 2016 14:09:29 -0800 Subject: lkdtm: add function for testing .rodata section This adds a function that lives in the .rodata section. The section flags are corrected using objcopy since there is no way with gcc to declare section flags in an architecture-agnostic way. Signed-off-by: Kees Cook --- drivers/misc/lkdtm.h | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 drivers/misc/lkdtm.h (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h new file mode 100644 index 000000000000..9531fa3be4c3 --- /dev/null +++ b/drivers/misc/lkdtm.h @@ -0,0 +1,6 @@ +#ifndef __LKDTM_H +#define __LKDTM_H + +void lkdtm_rodata_do_nothing(void); + +#endif -- cgit v1.2.3-70-g09d2 From a3dff71c1c88fc184a1ae5e425ba621d547d16ec Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 26 Jun 2016 08:46:23 -0700 Subject: lkdtm: split usercopy tests to separate file This splits the USERCOPY_* tests into the new lkdtm_usercopy.c file to help separate things better for readability. Signed-off-by: Kees Cook --- drivers/misc/Makefile | 1 + drivers/misc/lkdtm.h | 13 ++ drivers/misc/lkdtm_core.c | 279 ++----------------------------------- drivers/misc/lkdtm_usercopy.c | 315 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 266 deletions(-) create mode 100644 drivers/misc/lkdtm_usercopy.c (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7d45ed4a1549..e6b2778731ff 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_PANEL) += panel.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o +lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o OBJCOPYFLAGS := OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \ diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index 9531fa3be4c3..ef290a2c8816 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -1,6 +1,19 @@ #ifndef __LKDTM_H #define __LKDTM_H +/* lkdtm_rodata.c */ void lkdtm_rodata_do_nothing(void); +/* lkdtm_usercopy.c */ +void __init lkdtm_usercopy_init(void); +void __exit lkdtm_usercopy_exit(void); +void lkdtm_USERCOPY_HEAP_SIZE_TO(void); +void lkdtm_USERCOPY_HEAP_SIZE_FROM(void); +void lkdtm_USERCOPY_HEAP_FLAG_TO(void); +void lkdtm_USERCOPY_HEAP_FLAG_FROM(void); +void lkdtm_USERCOPY_STACK_FRAME_TO(void); +void lkdtm_USERCOPY_STACK_FRAME_FROM(void); +void lkdtm_USERCOPY_STACK_BEYOND(void); +void lkdtm_USERCOPY_KERNEL(void); + #endif diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index 1454d58aa278..74376920ed55 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -193,10 +193,6 @@ static DEFINE_SPINLOCK(lock_me_up); static u8 data_area[EXEC_SIZE]; -static size_t cache_size = 1024; -static struct kmem_cache *bad_cache; - -static const unsigned char test_text[] = "This is a test.\n"; static const unsigned long rodata = 0xAA55AA55; static unsigned long ro_after_init __ro_after_init = 0x55AA5500; @@ -403,255 +399,6 @@ static void execute_user_location(void *dst) func(); } -/* - * Instead of adding -Wno-return-local-addr, just pass the stack address - * through a function to obfuscate it from the compiler. - */ -static noinline unsigned char *trick_compiler(unsigned char *stack) -{ - return stack + 0; -} - -static noinline unsigned char *do_usercopy_stack_callee(int value) -{ - unsigned char buf[32]; - int i; - - /* Exercise stack to avoid everything living in registers. */ - for (i = 0; i < sizeof(buf); i++) { - buf[i] = value & 0xff; - } - - return trick_compiler(buf); -} - -static noinline void do_usercopy_stack(bool to_user, bool bad_frame) -{ - unsigned long user_addr; - unsigned char good_stack[32]; - unsigned char *bad_stack; - int i; - - /* Exercise stack to avoid everything living in registers. */ - for (i = 0; i < sizeof(good_stack); i++) - good_stack[i] = test_text[i % sizeof(test_text)]; - - /* This is a pointer to outside our current stack frame. */ - if (bad_frame) { - bad_stack = do_usercopy_stack_callee((uintptr_t)bad_stack); - } else { - /* Put start address just inside stack. */ - bad_stack = task_stack_page(current) + THREAD_SIZE; - bad_stack -= sizeof(unsigned long); - } - - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - return; - } - - if (to_user) { - pr_info("attempting good copy_to_user of local stack\n"); - if (copy_to_user((void __user *)user_addr, good_stack, - sizeof(good_stack))) { - pr_warn("copy_to_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_to_user of distant stack\n"); - if (copy_to_user((void __user *)user_addr, bad_stack, - sizeof(good_stack))) { - pr_warn("copy_to_user failed, but lacked Oops\n"); - goto free_user; - } - } else { - /* - * There isn't a safe way to not be protected by usercopy - * if we're going to write to another thread's stack. - */ - if (!bad_frame) - goto free_user; - - pr_info("attempting good copy_from_user of local stack\n"); - if (copy_from_user(good_stack, (void __user *)user_addr, - sizeof(good_stack))) { - pr_warn("copy_from_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_from_user of distant stack\n"); - if (copy_from_user(bad_stack, (void __user *)user_addr, - sizeof(good_stack))) { - pr_warn("copy_from_user failed, but lacked Oops\n"); - goto free_user; - } - } - -free_user: - vm_munmap(user_addr, PAGE_SIZE); -} - -static void do_usercopy_kernel(void) -{ - unsigned long user_addr; - - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - return; - } - - pr_info("attempting good copy_to_user from kernel rodata\n"); - if (copy_to_user((void __user *)user_addr, test_text, - sizeof(test_text))) { - pr_warn("copy_to_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_to_user from kernel text\n"); - if (copy_to_user((void __user *)user_addr, vm_mmap, PAGE_SIZE)) { - pr_warn("copy_to_user failed, but lacked Oops\n"); - goto free_user; - } - -free_user: - vm_munmap(user_addr, PAGE_SIZE); -} - -static void do_usercopy_heap_size(bool to_user) -{ - unsigned long user_addr; - unsigned char *one, *two; - size_t size = 1024; - - one = kmalloc(size, GFP_KERNEL); - two = kmalloc(size, GFP_KERNEL); - if (!one || !two) { - pr_warn("Failed to allocate kernel memory\n"); - goto free_kernel; - } - - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - goto free_kernel; - } - - memset(one, 'A', size); - memset(two, 'B', size); - - if (to_user) { - pr_info("attempting good copy_to_user of correct size\n"); - if (copy_to_user((void __user *)user_addr, one, size)) { - pr_warn("copy_to_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_to_user of too large size\n"); - if (copy_to_user((void __user *)user_addr, one, 2 * size)) { - pr_warn("copy_to_user failed, but lacked Oops\n"); - goto free_user; - } - } else { - pr_info("attempting good copy_from_user of correct size\n"); - if (copy_from_user(one, (void __user *)user_addr, size)) { - pr_warn("copy_from_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_from_user of too large size\n"); - if (copy_from_user(one, (void __user *)user_addr, 2 * size)) { - pr_warn("copy_from_user failed, but lacked Oops\n"); - goto free_user; - } - } - -free_user: - vm_munmap(user_addr, PAGE_SIZE); -free_kernel: - kfree(one); - kfree(two); -} - -static void do_usercopy_heap_flag(bool to_user) -{ - unsigned long user_addr; - unsigned char *good_buf = NULL; - unsigned char *bad_buf = NULL; - - /* Make sure cache was prepared. */ - if (!bad_cache) { - pr_warn("Failed to allocate kernel cache\n"); - return; - } - - /* - * Allocate one buffer from each cache (kmalloc will have the - * SLAB_USERCOPY flag already, but "bad_cache" won't). - */ - good_buf = kmalloc(cache_size, GFP_KERNEL); - bad_buf = kmem_cache_alloc(bad_cache, GFP_KERNEL); - if (!good_buf || !bad_buf) { - pr_warn("Failed to allocate buffers from caches\n"); - goto free_alloc; - } - - /* Allocate user memory we'll poke at. */ - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - goto free_alloc; - } - - memset(good_buf, 'A', cache_size); - memset(bad_buf, 'B', cache_size); - - if (to_user) { - pr_info("attempting good copy_to_user with SLAB_USERCOPY\n"); - if (copy_to_user((void __user *)user_addr, good_buf, - cache_size)) { - pr_warn("copy_to_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_to_user w/o SLAB_USERCOPY\n"); - if (copy_to_user((void __user *)user_addr, bad_buf, - cache_size)) { - pr_warn("copy_to_user failed, but lacked Oops\n"); - goto free_user; - } - } else { - pr_info("attempting good copy_from_user with SLAB_USERCOPY\n"); - if (copy_from_user(good_buf, (void __user *)user_addr, - cache_size)) { - pr_warn("copy_from_user failed unexpectedly?!\n"); - goto free_user; - } - - pr_info("attempting bad copy_from_user w/o SLAB_USERCOPY\n"); - if (copy_from_user(bad_buf, (void __user *)user_addr, - cache_size)) { - pr_warn("copy_from_user failed, but lacked Oops\n"); - goto free_user; - } - } - -free_user: - vm_munmap(user_addr, PAGE_SIZE); -free_alloc: - if (bad_buf) - kmem_cache_free(bad_cache, bad_buf); - kfree(good_buf); -} static void lkdtm_do_action(enum ctype which) { @@ -964,28 +711,28 @@ static void lkdtm_do_action(enum ctype which) return; } case CT_USERCOPY_HEAP_SIZE_TO: - do_usercopy_heap_size(true); + lkdtm_USERCOPY_HEAP_SIZE_TO(); break; case CT_USERCOPY_HEAP_SIZE_FROM: - do_usercopy_heap_size(false); + lkdtm_USERCOPY_HEAP_SIZE_FROM(); break; case CT_USERCOPY_HEAP_FLAG_TO: - do_usercopy_heap_flag(true); + lkdtm_USERCOPY_HEAP_FLAG_TO(); break; case CT_USERCOPY_HEAP_FLAG_FROM: - do_usercopy_heap_flag(false); + lkdtm_USERCOPY_HEAP_FLAG_FROM(); break; case CT_USERCOPY_STACK_FRAME_TO: - do_usercopy_stack(true, true); + lkdtm_USERCOPY_STACK_FRAME_TO(); break; case CT_USERCOPY_STACK_FRAME_FROM: - do_usercopy_stack(false, true); + lkdtm_USERCOPY_STACK_FRAME_FROM(); break; case CT_USERCOPY_STACK_BEYOND: - do_usercopy_stack(true, false); + lkdtm_USERCOPY_STACK_BEYOND(); break; case CT_USERCOPY_KERNEL: - do_usercopy_kernel(); + lkdtm_USERCOPY_KERNEL(); break; case CT_NONE: default: @@ -1276,13 +1023,12 @@ static int __init lkdtm_module_init(void) int n_debugfs_entries = 1; /* Assume only the direct entry */ int i; + /* Handle test-specific initialization. */ + lkdtm_usercopy_init(); + /* Make sure we can write to __ro_after_init values during __init */ ro_after_init |= 0xAA; - /* Prepare cache that lacks SLAB_USERCOPY flag. */ - bad_cache = kmem_cache_create("lkdtm-no-usercopy", cache_size, 0, - 0, NULL); - /* Register debugfs interface */ lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL); if (!lkdtm_debugfs_root) { @@ -1334,7 +1080,8 @@ static void __exit lkdtm_module_exit(void) { debugfs_remove_recursive(lkdtm_debugfs_root); - kmem_cache_destroy(bad_cache); + /* Handle test-specific clean-up. */ + lkdtm_usercopy_exit(); unregister_jprobe(&lkdtm); pr_info("Crash point unregistered\n"); diff --git a/drivers/misc/lkdtm_usercopy.c b/drivers/misc/lkdtm_usercopy.c new file mode 100644 index 000000000000..9c748e819a35 --- /dev/null +++ b/drivers/misc/lkdtm_usercopy.c @@ -0,0 +1,315 @@ +/* + * This is for all the tests related to copy_to_user() and copy_from_user() + * hardening. + */ +#define pr_fmt(fmt) "lkdtm: " fmt + +#include +#include +#include +#include +#include +#include + +static size_t cache_size = 1024; +static struct kmem_cache *bad_cache; + +static const unsigned char test_text[] = "This is a test.\n"; + +/* + * Instead of adding -Wno-return-local-addr, just pass the stack address + * through a function to obfuscate it from the compiler. + */ +static noinline unsigned char *trick_compiler(unsigned char *stack) +{ + return stack + 0; +} + +static noinline unsigned char *do_usercopy_stack_callee(int value) +{ + unsigned char buf[32]; + int i; + + /* Exercise stack to avoid everything living in registers. */ + for (i = 0; i < sizeof(buf); i++) { + buf[i] = value & 0xff; + } + + return trick_compiler(buf); +} + +static noinline void do_usercopy_stack(bool to_user, bool bad_frame) +{ + unsigned long user_addr; + unsigned char good_stack[32]; + unsigned char *bad_stack; + int i; + + /* Exercise stack to avoid everything living in registers. */ + for (i = 0; i < sizeof(good_stack); i++) + good_stack[i] = test_text[i % sizeof(test_text)]; + + /* This is a pointer to outside our current stack frame. */ + if (bad_frame) { + bad_stack = do_usercopy_stack_callee((uintptr_t)bad_stack); + } else { + /* Put start address just inside stack. */ + bad_stack = task_stack_page(current) + THREAD_SIZE; + bad_stack -= sizeof(unsigned long); + } + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + return; + } + + if (to_user) { + pr_info("attempting good copy_to_user of local stack\n"); + if (copy_to_user((void __user *)user_addr, good_stack, + sizeof(good_stack))) { + pr_warn("copy_to_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_to_user of distant stack\n"); + if (copy_to_user((void __user *)user_addr, bad_stack, + sizeof(good_stack))) { + pr_warn("copy_to_user failed, but lacked Oops\n"); + goto free_user; + } + } else { + /* + * There isn't a safe way to not be protected by usercopy + * if we're going to write to another thread's stack. + */ + if (!bad_frame) + goto free_user; + + pr_info("attempting good copy_from_user of local stack\n"); + if (copy_from_user(good_stack, (void __user *)user_addr, + sizeof(good_stack))) { + pr_warn("copy_from_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_from_user of distant stack\n"); + if (copy_from_user(bad_stack, (void __user *)user_addr, + sizeof(good_stack))) { + pr_warn("copy_from_user failed, but lacked Oops\n"); + goto free_user; + } + } + +free_user: + vm_munmap(user_addr, PAGE_SIZE); +} + +static void do_usercopy_heap_size(bool to_user) +{ + unsigned long user_addr; + unsigned char *one, *two; + const size_t size = 1024; + + one = kmalloc(size, GFP_KERNEL); + two = kmalloc(size, GFP_KERNEL); + if (!one || !two) { + pr_warn("Failed to allocate kernel memory\n"); + goto free_kernel; + } + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + goto free_kernel; + } + + memset(one, 'A', size); + memset(two, 'B', size); + + if (to_user) { + pr_info("attempting good copy_to_user of correct size\n"); + if (copy_to_user((void __user *)user_addr, one, size)) { + pr_warn("copy_to_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_to_user of too large size\n"); + if (copy_to_user((void __user *)user_addr, one, 2 * size)) { + pr_warn("copy_to_user failed, but lacked Oops\n"); + goto free_user; + } + } else { + pr_info("attempting good copy_from_user of correct size\n"); + if (copy_from_user(one, (void __user *)user_addr, size)) { + pr_warn("copy_from_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_from_user of too large size\n"); + if (copy_from_user(one, (void __user *)user_addr, 2 * size)) { + pr_warn("copy_from_user failed, but lacked Oops\n"); + goto free_user; + } + } + +free_user: + vm_munmap(user_addr, PAGE_SIZE); +free_kernel: + kfree(one); + kfree(two); +} + +static void do_usercopy_heap_flag(bool to_user) +{ + unsigned long user_addr; + unsigned char *good_buf = NULL; + unsigned char *bad_buf = NULL; + + /* Make sure cache was prepared. */ + if (!bad_cache) { + pr_warn("Failed to allocate kernel cache\n"); + return; + } + + /* + * Allocate one buffer from each cache (kmalloc will have the + * SLAB_USERCOPY flag already, but "bad_cache" won't). + */ + good_buf = kmalloc(cache_size, GFP_KERNEL); + bad_buf = kmem_cache_alloc(bad_cache, GFP_KERNEL); + if (!good_buf || !bad_buf) { + pr_warn("Failed to allocate buffers from caches\n"); + goto free_alloc; + } + + /* Allocate user memory we'll poke at. */ + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + goto free_alloc; + } + + memset(good_buf, 'A', cache_size); + memset(bad_buf, 'B', cache_size); + + if (to_user) { + pr_info("attempting good copy_to_user with SLAB_USERCOPY\n"); + if (copy_to_user((void __user *)user_addr, good_buf, + cache_size)) { + pr_warn("copy_to_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_to_user w/o SLAB_USERCOPY\n"); + if (copy_to_user((void __user *)user_addr, bad_buf, + cache_size)) { + pr_warn("copy_to_user failed, but lacked Oops\n"); + goto free_user; + } + } else { + pr_info("attempting good copy_from_user with SLAB_USERCOPY\n"); + if (copy_from_user(good_buf, (void __user *)user_addr, + cache_size)) { + pr_warn("copy_from_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_from_user w/o SLAB_USERCOPY\n"); + if (copy_from_user(bad_buf, (void __user *)user_addr, + cache_size)) { + pr_warn("copy_from_user failed, but lacked Oops\n"); + goto free_user; + } + } + +free_user: + vm_munmap(user_addr, PAGE_SIZE); +free_alloc: + if (bad_buf) + kmem_cache_free(bad_cache, bad_buf); + kfree(good_buf); +} + +/* Callable tests. */ +void lkdtm_USERCOPY_HEAP_SIZE_TO(void) +{ + do_usercopy_heap_size(true); +} + +void lkdtm_USERCOPY_HEAP_SIZE_FROM(void) +{ + do_usercopy_heap_size(false); +} + +void lkdtm_USERCOPY_HEAP_FLAG_TO(void) +{ + do_usercopy_heap_flag(true); +} + +void lkdtm_USERCOPY_HEAP_FLAG_FROM(void) +{ + do_usercopy_heap_flag(false); +} + +void lkdtm_USERCOPY_STACK_FRAME_TO(void) +{ + do_usercopy_stack(true, true); +} + +void lkdtm_USERCOPY_STACK_FRAME_FROM(void) +{ + do_usercopy_stack(false, true); +} + +void lkdtm_USERCOPY_STACK_BEYOND(void) +{ + do_usercopy_stack(true, false); +} + +void lkdtm_USERCOPY_KERNEL(void) +{ + unsigned long user_addr; + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + return; + } + + pr_info("attempting good copy_to_user from kernel rodata\n"); + if (copy_to_user((void __user *)user_addr, test_text, + sizeof(test_text))) { + pr_warn("copy_to_user failed unexpectedly?!\n"); + goto free_user; + } + + pr_info("attempting bad copy_to_user from kernel text\n"); + if (copy_to_user((void __user *)user_addr, vm_mmap, PAGE_SIZE)) { + pr_warn("copy_to_user failed, but lacked Oops\n"); + goto free_user; + } + +free_user: + vm_munmap(user_addr, PAGE_SIZE); +} + +void __init lkdtm_usercopy_init(void) +{ + /* Prepare cache that lacks SLAB_USERCOPY flag. */ + bad_cache = kmem_cache_create("lkdtm-no-usercopy", cache_size, 0, + 0, NULL); +} + +void __exit lkdtm_usercopy_exit(void) +{ + kmem_cache_destroy(bad_cache); +} -- cgit v1.2.3-70-g09d2 From 0d9eb29b13f0e326c4e19b85d3a4ac46e335e6d2 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 26 Jun 2016 15:12:31 -0700 Subject: lkdtm: split memory permissions tests to separate file This splits the EXEC_*, WRITE_* and related tests into the new lkdtm_perms.c file to help separate things better for readability. Signed-off-by: Kees Cook --- drivers/misc/Makefile | 1 + drivers/misc/lkdtm.h | 14 ++++ drivers/misc/lkdtm_core.c | 174 +++++--------------------------------- drivers/misc/lkdtm_perms.c | 203 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 155 deletions(-) create mode 100644 drivers/misc/lkdtm_perms.c (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e6b2778731ff..9f6e95bc0635 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o +lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index ef290a2c8816..40f681cd6efe 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -1,6 +1,19 @@ #ifndef __LKDTM_H #define __LKDTM_H +/* lkdtm_perms.c */ +void __init lkdtm_perms_init(void); +void lkdtm_WRITE_RO(void); +void lkdtm_WRITE_RO_AFTER_INIT(void); +void lkdtm_WRITE_KERN(void); +void lkdtm_EXEC_DATA(void); +void lkdtm_EXEC_STACK(void); +void lkdtm_EXEC_KMALLOC(void); +void lkdtm_EXEC_VMALLOC(void); +void lkdtm_EXEC_RODATA(void); +void lkdtm_EXEC_USERSPACE(void); +void lkdtm_ACCESS_USERSPACE(void); + /* lkdtm_rodata.c */ void lkdtm_rodata_do_nothing(void); @@ -16,4 +29,5 @@ void lkdtm_USERCOPY_STACK_FRAME_FROM(void); void lkdtm_USERCOPY_STACK_BEYOND(void); void lkdtm_USERCOPY_KERNEL(void); + #endif diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index 74376920ed55..0b3e3770068a 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -44,9 +44,6 @@ #include #include #include -#include -#include -#include #ifdef CONFIG_IDE #include @@ -67,7 +64,6 @@ #define REC_NUM_DEFAULT ((THREAD_SIZE / REC_STACK_SIZE) * 2) #define DEFAULT_COUNT 10 -#define EXEC_SIZE 64 enum cname { CN_INVALID, @@ -191,11 +187,6 @@ static int count = DEFAULT_COUNT; static DEFINE_SPINLOCK(count_lock); static DEFINE_SPINLOCK(lock_me_up); -static u8 data_area[EXEC_SIZE]; - -static const unsigned long rodata = 0xAA55AA55; -static unsigned long ro_after_init __ro_after_init = 0x55AA5500; - module_param(recur_count, int, 0644); MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test"); module_param(cpoint_name, charp, 0444); @@ -348,18 +339,6 @@ static int recursive_loop(int remaining) return recursive_loop(remaining - 1); } -static void do_nothing(void) -{ - return; -} - -/* Must immediately follow do_nothing for size calculuations to work out. */ -static void do_overwritten(void) -{ - pr_info("do_overwritten wasn't overwritten!\n"); - return; -} - static noinline void corrupt_stack(void) { /* Use default char array length that triggers stack protection. */ @@ -368,38 +347,6 @@ static noinline void corrupt_stack(void) memset((void *)data, 0, 64); } -static noinline void execute_location(void *dst, bool write) -{ - void (*func)(void) = dst; - - pr_info("attempting ok execution at %p\n", do_nothing); - do_nothing(); - - if (write) { - memcpy(dst, do_nothing, EXEC_SIZE); - flush_icache_range((unsigned long)dst, - (unsigned long)dst + EXEC_SIZE); - } - pr_info("attempting bad execution at %p\n", func); - func(); -} - -static void execute_user_location(void *dst) -{ - /* Intentionally crossing kernel/user memory boundary. */ - void (*func)(void) = dst; - - pr_info("attempting ok execution at %p\n", do_nothing); - do_nothing(); - - if (copy_to_user((void __user *)dst, do_nothing, EXEC_SIZE)) - return; - flush_icache_range((unsigned long)dst, (unsigned long)dst + EXEC_SIZE); - pr_info("attempting bad execution at %p\n", func); - func(); -} - - static void lkdtm_do_action(enum ctype which) { switch (which) { @@ -577,116 +524,35 @@ static void lkdtm_do_action(enum ctype which) schedule(); break; case CT_EXEC_DATA: - execute_location(data_area, true); + lkdtm_EXEC_DATA(); break; - case CT_EXEC_STACK: { - u8 stack_area[EXEC_SIZE]; - execute_location(stack_area, true); + case CT_EXEC_STACK: + lkdtm_EXEC_STACK(); break; - } - case CT_EXEC_KMALLOC: { - u32 *kmalloc_area = kmalloc(EXEC_SIZE, GFP_KERNEL); - execute_location(kmalloc_area, true); - kfree(kmalloc_area); + case CT_EXEC_KMALLOC: + lkdtm_EXEC_KMALLOC(); break; - } - case CT_EXEC_VMALLOC: { - u32 *vmalloc_area = vmalloc(EXEC_SIZE); - execute_location(vmalloc_area, true); - vfree(vmalloc_area); + case CT_EXEC_VMALLOC: + lkdtm_EXEC_VMALLOC(); break; - } case CT_EXEC_RODATA: - execute_location(lkdtm_rodata_do_nothing, false); + lkdtm_EXEC_RODATA(); break; - case CT_EXEC_USERSPACE: { - unsigned long user_addr; - - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - return; - } - execute_user_location((void *)user_addr); - vm_munmap(user_addr, PAGE_SIZE); + case CT_EXEC_USERSPACE: + lkdtm_EXEC_USERSPACE(); break; - } - case CT_ACCESS_USERSPACE: { - unsigned long user_addr, tmp = 0; - unsigned long *ptr; - - user_addr = vm_mmap(NULL, 0, PAGE_SIZE, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (user_addr >= TASK_SIZE) { - pr_warn("Failed to allocate user memory\n"); - return; - } - - if (copy_to_user((void __user *)user_addr, &tmp, sizeof(tmp))) { - pr_warn("copy_to_user failed\n"); - vm_munmap(user_addr, PAGE_SIZE); - return; - } - - ptr = (unsigned long *)user_addr; - - pr_info("attempting bad read at %p\n", ptr); - tmp = *ptr; - tmp += 0xc0dec0de; - - pr_info("attempting bad write at %p\n", ptr); - *ptr = tmp; - - vm_munmap(user_addr, PAGE_SIZE); - + case CT_ACCESS_USERSPACE: + lkdtm_ACCESS_USERSPACE(); break; - } - case CT_WRITE_RO: { - /* Explicitly cast away "const" for the test. */ - unsigned long *ptr = (unsigned long *)&rodata; - - pr_info("attempting bad rodata write at %p\n", ptr); - *ptr ^= 0xabcd1234; - + case CT_WRITE_RO: + lkdtm_WRITE_RO(); break; - } - case CT_WRITE_RO_AFTER_INIT: { - unsigned long *ptr = &ro_after_init; - - /* - * Verify we were written to during init. Since an Oops - * is considered a "success", a failure is to just skip the - * real test. - */ - if ((*ptr & 0xAA) != 0xAA) { - pr_info("%p was NOT written during init!?\n", ptr); - break; - } - - pr_info("attempting bad ro_after_init write at %p\n", ptr); - *ptr ^= 0xabcd1234; - + case CT_WRITE_RO_AFTER_INIT: + lkdtm_WRITE_RO_AFTER_INIT(); break; - } - case CT_WRITE_KERN: { - size_t size; - unsigned char *ptr; - - size = (unsigned long)do_overwritten - - (unsigned long)do_nothing; - ptr = (unsigned char *)do_overwritten; - - pr_info("attempting bad %zu byte write at %p\n", size, ptr); - memcpy(ptr, (unsigned char *)do_nothing, size); - flush_icache_range((unsigned long)ptr, - (unsigned long)(ptr + size)); - - do_overwritten(); + case CT_WRITE_KERN: + lkdtm_WRITE_KERN(); break; - } case CT_ATOMIC_UNDERFLOW: { atomic_t under = ATOMIC_INIT(INT_MIN); @@ -1024,11 +890,9 @@ static int __init lkdtm_module_init(void) int i; /* Handle test-specific initialization. */ + lkdtm_perms_init(); lkdtm_usercopy_init(); - /* Make sure we can write to __ro_after_init values during __init */ - ro_after_init |= 0xAA; - /* Register debugfs interface */ lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL); if (!lkdtm_debugfs_root) { diff --git a/drivers/misc/lkdtm_perms.c b/drivers/misc/lkdtm_perms.c new file mode 100644 index 000000000000..8201006502e2 --- /dev/null +++ b/drivers/misc/lkdtm_perms.c @@ -0,0 +1,203 @@ +/* + * This is for all the tests related to validating kernel memory + * permissions: non-executable regions, non-writable regions, and + * even non-readable regions. + */ +#define pr_fmt(fmt) "lkdtm: " fmt + +#include +#include +#include +#include +#include +#include + +#include "lkdtm.h" + +/* Whether or not to fill the target memory area with do_nothing(). */ +#define CODE_WRITE true +#define CODE_AS_IS false + +/* How many bytes to copy to be sure we've copied enough of do_nothing(). */ +#define EXEC_SIZE 64 + +/* This is non-const, so it will end up in the .data section. */ +static u8 data_area[EXEC_SIZE]; + +/* This is cost, so it will end up in the .rodata section. */ +static const unsigned long rodata = 0xAA55AA55; + +/* This is marked __ro_after_init, so it should ultimately be .rodata. */ +static unsigned long ro_after_init __ro_after_init = 0x55AA5500; + +/* + * This just returns to the caller. It is designed to be copied into + * non-executable memory regions. + */ +static void do_nothing(void) +{ + return; +} + +/* Must immediately follow do_nothing for size calculuations to work out. */ +static void do_overwritten(void) +{ + pr_info("do_overwritten wasn't overwritten!\n"); + return; +} + +static noinline void execute_location(void *dst, bool write) +{ + void (*func)(void) = dst; + + pr_info("attempting ok execution at %p\n", do_nothing); + do_nothing(); + + if (write == CODE_WRITE) { + memcpy(dst, do_nothing, EXEC_SIZE); + flush_icache_range((unsigned long)dst, + (unsigned long)dst + EXEC_SIZE); + } + pr_info("attempting bad execution at %p\n", func); + func(); +} + +static void execute_user_location(void *dst) +{ + /* Intentionally crossing kernel/user memory boundary. */ + void (*func)(void) = dst; + + pr_info("attempting ok execution at %p\n", do_nothing); + do_nothing(); + + if (copy_to_user((void __user *)dst, do_nothing, EXEC_SIZE)) + return; + flush_icache_range((unsigned long)dst, (unsigned long)dst + EXEC_SIZE); + pr_info("attempting bad execution at %p\n", func); + func(); +} + +void lkdtm_WRITE_RO(void) +{ + /* Explicitly cast away "const" for the test. */ + unsigned long *ptr = (unsigned long *)&rodata; + + pr_info("attempting bad rodata write at %p\n", ptr); + *ptr ^= 0xabcd1234; +} + +void lkdtm_WRITE_RO_AFTER_INIT(void) +{ + unsigned long *ptr = &ro_after_init; + + /* + * Verify we were written to during init. Since an Oops + * is considered a "success", a failure is to just skip the + * real test. + */ + if ((*ptr & 0xAA) != 0xAA) { + pr_info("%p was NOT written during init!?\n", ptr); + return; + } + + pr_info("attempting bad ro_after_init write at %p\n", ptr); + *ptr ^= 0xabcd1234; +} + +void lkdtm_WRITE_KERN(void) +{ + size_t size; + unsigned char *ptr; + + size = (unsigned long)do_overwritten - (unsigned long)do_nothing; + ptr = (unsigned char *)do_overwritten; + + pr_info("attempting bad %zu byte write at %p\n", size, ptr); + memcpy(ptr, (unsigned char *)do_nothing, size); + flush_icache_range((unsigned long)ptr, (unsigned long)(ptr + size)); + + do_overwritten(); +} + +void lkdtm_EXEC_DATA(void) +{ + execute_location(data_area, CODE_WRITE); +} + +void lkdtm_EXEC_STACK(void) +{ + u8 stack_area[EXEC_SIZE]; + execute_location(stack_area, CODE_WRITE); +} + +void lkdtm_EXEC_KMALLOC(void) +{ + u32 *kmalloc_area = kmalloc(EXEC_SIZE, GFP_KERNEL); + execute_location(kmalloc_area, CODE_WRITE); + kfree(kmalloc_area); +} + +void lkdtm_EXEC_VMALLOC(void) +{ + u32 *vmalloc_area = vmalloc(EXEC_SIZE); + execute_location(vmalloc_area, CODE_WRITE); + vfree(vmalloc_area); +} + +void lkdtm_EXEC_RODATA(void) +{ + execute_location(lkdtm_rodata_do_nothing, CODE_AS_IS); +} + +void lkdtm_EXEC_USERSPACE(void) +{ + unsigned long user_addr; + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + return; + } + execute_user_location((void *)user_addr); + vm_munmap(user_addr, PAGE_SIZE); +} + +void lkdtm_ACCESS_USERSPACE(void) +{ + unsigned long user_addr, tmp = 0; + unsigned long *ptr; + + user_addr = vm_mmap(NULL, 0, PAGE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (user_addr >= TASK_SIZE) { + pr_warn("Failed to allocate user memory\n"); + return; + } + + if (copy_to_user((void __user *)user_addr, &tmp, sizeof(tmp))) { + pr_warn("copy_to_user failed\n"); + vm_munmap(user_addr, PAGE_SIZE); + return; + } + + ptr = (unsigned long *)user_addr; + + pr_info("attempting bad read at %p\n", ptr); + tmp = *ptr; + tmp += 0xc0dec0de; + + pr_info("attempting bad write at %p\n", ptr); + *ptr = tmp; + + vm_munmap(user_addr, PAGE_SIZE); +} + +void __init lkdtm_perms_init(void) +{ + /* Make sure we can write to __ro_after_init values during __init */ + ro_after_init |= 0xAA; + +} -- cgit v1.2.3-70-g09d2 From ffc514f3fcac4aa76735ada55228c814153943e6 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 26 Jun 2016 21:45:23 -0700 Subject: lkdtm: split heap corruption tests to separate file This splits the *_AFTER_FREE and related tests into the new lkdtm_heap.c file to help separate things better for readability. Signed-off-by: Kees Cook --- drivers/misc/Makefile | 1 + drivers/misc/lkdtm.h | 7 +++ drivers/misc/lkdtm_core.c | 124 ++++----------------------------------- drivers/misc/lkdtm_heap.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 114 deletions(-) create mode 100644 drivers/misc/lkdtm_heap.c (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9f6e95bc0635..d66f657cf9f2 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o +lkdtm-$(CONFIG_LKDTM) += lkdtm_heap.o lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index 40f681cd6efe..9397360a2b11 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -1,6 +1,13 @@ #ifndef __LKDTM_H #define __LKDTM_H +/* lkdtm_heap.c */ +void lkdtm_OVERWRITE_ALLOCATION(void); +void lkdtm_WRITE_AFTER_FREE(void); +void lkdtm_READ_AFTER_FREE(void); +void lkdtm_WRITE_BUDDY_AFTER_FREE(void); +void lkdtm_READ_BUDDY_AFTER_FREE(void); + /* lkdtm_perms.c */ void __init lkdtm_perms_init(void); void lkdtm_WRITE_RO(void); diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index 0b3e3770068a..1c5056c286c5 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -384,125 +384,21 @@ static void lkdtm_do_action(enum ctype which) *p = val; break; } - case CT_OVERWRITE_ALLOCATION: { - size_t len = 1020; - u32 *data = kmalloc(len, GFP_KERNEL); - - data[1024 / sizeof(u32)] = 0x12345678; - kfree(data); + case CT_OVERWRITE_ALLOCATION: + lkdtm_OVERWRITE_ALLOCATION(); break; - } - case CT_WRITE_AFTER_FREE: { - int *base, *again; - size_t len = 1024; - /* - * The slub allocator uses the first word to store the free - * pointer in some configurations. Use the middle of the - * allocation to avoid running into the freelist - */ - size_t offset = (len / sizeof(*base)) / 2; - - base = kmalloc(len, GFP_KERNEL); - pr_info("Allocated memory %p-%p\n", base, &base[offset * 2]); - pr_info("Attempting bad write to freed memory at %p\n", - &base[offset]); - kfree(base); - base[offset] = 0x0abcdef0; - /* Attempt to notice the overwrite. */ - again = kmalloc(len, GFP_KERNEL); - kfree(again); - if (again != base) - pr_info("Hmm, didn't get the same memory range.\n"); - + case CT_WRITE_AFTER_FREE: + lkdtm_WRITE_AFTER_FREE(); break; - } - case CT_READ_AFTER_FREE: { - int *base, *val, saw; - size_t len = 1024; - /* - * The slub allocator uses the first word to store the free - * pointer in some configurations. Use the middle of the - * allocation to avoid running into the freelist - */ - size_t offset = (len / sizeof(*base)) / 2; - - base = kmalloc(len, GFP_KERNEL); - if (!base) - break; - - val = kmalloc(len, GFP_KERNEL); - if (!val) { - kfree(base); - break; - } - - *val = 0x12345678; - base[offset] = *val; - pr_info("Value in memory before free: %x\n", base[offset]); - - kfree(base); - - pr_info("Attempting bad read from freed memory\n"); - saw = base[offset]; - if (saw != *val) { - /* Good! Poisoning happened, so declare a win. */ - pr_info("Memory correctly poisoned (%x)\n", saw); - BUG(); - } - pr_info("Memory was not poisoned\n"); - - kfree(val); + case CT_READ_AFTER_FREE: + lkdtm_READ_AFTER_FREE(); break; - } - case CT_WRITE_BUDDY_AFTER_FREE: { - unsigned long p = __get_free_page(GFP_KERNEL); - if (!p) - break; - pr_info("Writing to the buddy page before free\n"); - memset((void *)p, 0x3, PAGE_SIZE); - free_page(p); - schedule(); - pr_info("Attempting bad write to the buddy page after free\n"); - memset((void *)p, 0x78, PAGE_SIZE); - /* Attempt to notice the overwrite. */ - p = __get_free_page(GFP_KERNEL); - free_page(p); - schedule(); - + case CT_WRITE_BUDDY_AFTER_FREE: + lkdtm_WRITE_BUDDY_AFTER_FREE(); break; - } - case CT_READ_BUDDY_AFTER_FREE: { - unsigned long p = __get_free_page(GFP_KERNEL); - int saw, *val; - int *base; - - if (!p) - break; - - val = kmalloc(1024, GFP_KERNEL); - if (!val) { - free_page(p); - break; - } - - base = (int *)p; - - *val = 0x12345678; - base[0] = *val; - pr_info("Value in memory before free: %x\n", base[0]); - free_page(p); - pr_info("Attempting to read from freed memory\n"); - saw = base[0]; - if (saw != *val) { - /* Good! Poisoning happened, so declare a win. */ - pr_info("Memory correctly poisoned (%x)\n", saw); - BUG(); - } - pr_info("Buddy page was not poisoned\n"); - - kfree(val); + case CT_READ_BUDDY_AFTER_FREE: + lkdtm_READ_BUDDY_AFTER_FREE(); break; - } case CT_SOFTLOCKUP: preempt_disable(); for (;;) diff --git a/drivers/misc/lkdtm_heap.c b/drivers/misc/lkdtm_heap.c new file mode 100644 index 000000000000..12f50e8dcbfe --- /dev/null +++ b/drivers/misc/lkdtm_heap.c @@ -0,0 +1,146 @@ +/* + * This is for all the tests relating directly to heap memory, including + * page allocation and slab allocations. + */ +#define pr_fmt(fmt) "lkdtm: " fmt + +#include +#include + +#include "lkdtm.h" + +/* + * This tries to stay within the next largest power-of-2 kmalloc cache + * to avoid actually overwriting anything important if it's not detected + * correctly. + */ +void lkdtm_OVERWRITE_ALLOCATION(void) +{ + size_t len = 1020; + u32 *data = kmalloc(len, GFP_KERNEL); + + data[1024 / sizeof(u32)] = 0x12345678; + kfree(data); +} + +void lkdtm_WRITE_AFTER_FREE(void) +{ + int *base, *again; + size_t len = 1024; + /* + * The slub allocator uses the first word to store the free + * pointer in some configurations. Use the middle of the + * allocation to avoid running into the freelist + */ + size_t offset = (len / sizeof(*base)) / 2; + + base = kmalloc(len, GFP_KERNEL); + pr_info("Allocated memory %p-%p\n", base, &base[offset * 2]); + pr_info("Attempting bad write to freed memory at %p\n", + &base[offset]); + kfree(base); + base[offset] = 0x0abcdef0; + /* Attempt to notice the overwrite. */ + again = kmalloc(len, GFP_KERNEL); + kfree(again); + if (again != base) + pr_info("Hmm, didn't get the same memory range.\n"); +} + +void lkdtm_READ_AFTER_FREE(void) +{ + int *base, *val, saw; + size_t len = 1024; + /* + * The slub allocator uses the first word to store the free + * pointer in some configurations. Use the middle of the + * allocation to avoid running into the freelist + */ + size_t offset = (len / sizeof(*base)) / 2; + + base = kmalloc(len, GFP_KERNEL); + if (!base) { + pr_info("Unable to allocate base memory.\n"); + return; + } + + val = kmalloc(len, GFP_KERNEL); + if (!val) { + pr_info("Unable to allocate val memory.\n"); + kfree(base); + return; + } + + *val = 0x12345678; + base[offset] = *val; + pr_info("Value in memory before free: %x\n", base[offset]); + + kfree(base); + + pr_info("Attempting bad read from freed memory\n"); + saw = base[offset]; + if (saw != *val) { + /* Good! Poisoning happened, so declare a win. */ + pr_info("Memory correctly poisoned (%x)\n", saw); + BUG(); + } + pr_info("Memory was not poisoned\n"); + + kfree(val); +} + +void lkdtm_WRITE_BUDDY_AFTER_FREE(void) +{ + unsigned long p = __get_free_page(GFP_KERNEL); + if (!p) { + pr_info("Unable to allocate free page\n"); + return; + } + + pr_info("Writing to the buddy page before free\n"); + memset((void *)p, 0x3, PAGE_SIZE); + free_page(p); + schedule(); + pr_info("Attempting bad write to the buddy page after free\n"); + memset((void *)p, 0x78, PAGE_SIZE); + /* Attempt to notice the overwrite. */ + p = __get_free_page(GFP_KERNEL); + free_page(p); + schedule(); +} + +void lkdtm_READ_BUDDY_AFTER_FREE(void) +{ + unsigned long p = __get_free_page(GFP_KERNEL); + int saw, *val; + int *base; + + if (!p) { + pr_info("Unable to allocate free page\n"); + return; + } + + val = kmalloc(1024, GFP_KERNEL); + if (!val) { + pr_info("Unable to allocate val memory.\n"); + free_page(p); + return; + } + + base = (int *)p; + + *val = 0x12345678; + base[0] = *val; + pr_info("Value in memory before free: %x\n", base[0]); + free_page(p); + pr_info("Attempting to read from freed memory\n"); + saw = base[0]; + if (saw != *val) { + /* Good! Poisoning happened, so declare a win. */ + pr_info("Memory correctly poisoned (%x)\n", saw); + BUG(); + } + pr_info("Buddy page was not poisoned\n"); + + kfree(val); +} -- cgit v1.2.3-70-g09d2 From 00f496c416122e7f5a572a4511cf87c7240ba761 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 26 Jun 2016 22:17:25 -0700 Subject: lkdtm: split remaining logic bug tests to separate file This splits all the remaining tests from lkdtm_core.c into the new lkdtm_bugs.c file to help separate things better for readability. Signed-off-by: Kees Cook --- drivers/misc/Makefile | 1 + drivers/misc/lkdtm.h | 17 ++++++ drivers/misc/lkdtm_bugs.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/lkdtm_core.c | 116 ++++++++--------------------------- 4 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 drivers/misc/lkdtm_bugs.c (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d66f657cf9f2..4387ccb79e64 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o +lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o lkdtm-$(CONFIG_LKDTM) += lkdtm_heap.o lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index 9397360a2b11..d70a41741bb3 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -1,6 +1,23 @@ #ifndef __LKDTM_H #define __LKDTM_H +/* lkdtm_bugs.c */ +void __init lkdtm_bugs_init(int *recur_param); +void lkdtm_PANIC(void); +void lkdtm_BUG(void); +void lkdtm_WARNING(void); +void lkdtm_EXCEPTION(void); +void lkdtm_LOOP(void); +void lkdtm_OVERFLOW(void); +void lkdtm_CORRUPT_STACK(void); +void lkdtm_UNALIGNED_LOAD_STORE_WRITE(void); +void lkdtm_SOFTLOCKUP(void); +void lkdtm_HARDLOCKUP(void); +void lkdtm_SPINLOCKUP(void); +void lkdtm_HUNG_TASK(void); +void lkdtm_ATOMIC_UNDERFLOW(void); +void lkdtm_ATOMIC_OVERFLOW(void); + /* lkdtm_heap.c */ void lkdtm_OVERWRITE_ALLOCATION(void); void lkdtm_WRITE_AFTER_FREE(void); diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c new file mode 100644 index 000000000000..e87071f9c003 --- /dev/null +++ b/drivers/misc/lkdtm_bugs.c @@ -0,0 +1,152 @@ +/* + * This is for all the tests related to logic bugs (e.g. bad dereferences, + * bad alignment, bad loops, bad locking, bad scheduling, deep stacks, and + * lockups) along with other things that don't fit well into existing LKDTM + * test source files. + */ +#define pr_fmt(fmt) "lkdtm: " fmt + +#include +#include + +#include "lkdtm.h" + +/* + * Make sure our attempts to over run the kernel stack doesn't trigger + * a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we + * recurse past the end of THREAD_SIZE by default. + */ +#if defined(CONFIG_FRAME_WARN) && (CONFIG_FRAME_WARN > 0) +#define REC_STACK_SIZE (CONFIG_FRAME_WARN / 2) +#else +#define REC_STACK_SIZE (THREAD_SIZE / 8) +#endif +#define REC_NUM_DEFAULT ((THREAD_SIZE / REC_STACK_SIZE) * 2) + +static int recur_count = REC_NUM_DEFAULT; + +static DEFINE_SPINLOCK(lock_me_up); + +static int recursive_loop(int remaining) +{ + char buf[REC_STACK_SIZE]; + + /* Make sure compiler does not optimize this away. */ + memset(buf, (remaining & 0xff) | 0x1, REC_STACK_SIZE); + if (!remaining) + return 0; + else + return recursive_loop(remaining - 1); +} + +/* If the depth is negative, use the default, otherwise keep parameter. */ +void __init lkdtm_bugs_init(int *recur_param) +{ + if (*recur_param < 0) + *recur_param = recur_count; + else + recur_count = *recur_param; +} + +void lkdtm_PANIC(void) +{ + panic("dumptest"); +} + +void lkdtm_BUG(void) +{ + BUG(); +} + +void lkdtm_WARNING(void) +{ + WARN_ON(1); +} + +void lkdtm_EXCEPTION(void) +{ + *((int *) 0) = 0; +} + +void lkdtm_LOOP(void) +{ + for (;;) + ; +} + +void lkdtm_OVERFLOW(void) +{ + (void) recursive_loop(recur_count); +} + +noinline void lkdtm_CORRUPT_STACK(void) +{ + /* Use default char array length that triggers stack protection. */ + char data[8]; + + memset((void *)data, 0, 64); +} + +void lkdtm_UNALIGNED_LOAD_STORE_WRITE(void) +{ + static u8 data[5] __attribute__((aligned(4))) = {1, 2, 3, 4, 5}; + u32 *p; + u32 val = 0x12345678; + + p = (u32 *)(data + 1); + if (*p == 0) + val = 0x87654321; + *p = val; +} + +void lkdtm_SOFTLOCKUP(void) +{ + preempt_disable(); + for (;;) + cpu_relax(); +} + +void lkdtm_HARDLOCKUP(void) +{ + local_irq_disable(); + for (;;) + cpu_relax(); +} + +void lkdtm_SPINLOCKUP(void) +{ + /* Must be called twice to trigger. */ + spin_lock(&lock_me_up); + /* Let sparse know we intended to exit holding the lock. */ + __release(&lock_me_up); +} + +void lkdtm_HUNG_TASK(void) +{ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); +} + +void lkdtm_ATOMIC_UNDERFLOW(void) +{ + atomic_t under = ATOMIC_INIT(INT_MIN); + + pr_info("attempting good atomic increment\n"); + atomic_inc(&under); + atomic_dec(&under); + + pr_info("attempting bad atomic underflow\n"); + atomic_dec(&under); +} + +void lkdtm_ATOMIC_OVERFLOW(void) +{ + atomic_t over = ATOMIC_INIT(INT_MAX); + + pr_info("attempting good atomic decrement\n"); + atomic_dec(&over); + atomic_inc(&over); + + pr_info("attempting bad atomic overflow\n"); + atomic_inc(&over); +} diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index 1c5056c286c5..e0f10131511f 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -51,20 +51,11 @@ #include "lkdtm.h" -/* - * Make sure our attempts to over run the kernel stack doesn't trigger - * a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we - * recurse past the end of THREAD_SIZE by default. - */ -#if defined(CONFIG_FRAME_WARN) && (CONFIG_FRAME_WARN > 0) -#define REC_STACK_SIZE (CONFIG_FRAME_WARN / 2) -#else -#define REC_STACK_SIZE (THREAD_SIZE / 8) -#endif -#define REC_NUM_DEFAULT ((THREAD_SIZE / REC_STACK_SIZE) * 2) - #define DEFAULT_COUNT 10 +static int count = DEFAULT_COUNT; +static DEFINE_SPINLOCK(count_lock); + enum cname { CN_INVALID, CN_INT_HARDWARE_ENTRY, @@ -179,13 +170,10 @@ static void lkdtm_handler(void); static char* cpoint_name; static char* cpoint_type; static int cpoint_count = DEFAULT_COUNT; -static int recur_count = REC_NUM_DEFAULT; +static int recur_count = -1; static enum cname cpoint = CN_INVALID; static enum ctype cptype = CT_NONE; -static int count = DEFAULT_COUNT; -static DEFINE_SPINLOCK(count_lock); -static DEFINE_SPINLOCK(lock_me_up); module_param(recur_count, int, 0644); MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test"); @@ -327,63 +315,33 @@ static int lkdtm_parse_commandline(void) return -EINVAL; } -static int recursive_loop(int remaining) -{ - char buf[REC_STACK_SIZE]; - - /* Make sure compiler does not optimize this away. */ - memset(buf, (remaining & 0xff) | 0x1, REC_STACK_SIZE); - if (!remaining) - return 0; - else - return recursive_loop(remaining - 1); -} - -static noinline void corrupt_stack(void) -{ - /* Use default char array length that triggers stack protection. */ - char data[8]; - - memset((void *)data, 0, 64); -} - static void lkdtm_do_action(enum ctype which) { switch (which) { case CT_PANIC: - panic("dumptest"); + lkdtm_PANIC(); break; case CT_BUG: - BUG(); + lkdtm_BUG(); break; case CT_WARNING: - WARN_ON(1); + lkdtm_WARNING(); break; case CT_EXCEPTION: - *((int *) 0) = 0; + lkdtm_EXCEPTION(); break; case CT_LOOP: - for (;;) - ; + lkdtm_LOOP(); break; case CT_OVERFLOW: - (void) recursive_loop(recur_count); + lkdtm_OVERFLOW(); break; case CT_CORRUPT_STACK: - corrupt_stack(); - break; - case CT_UNALIGNED_LOAD_STORE_WRITE: { - static u8 data[5] __attribute__((aligned(4))) = {1, 2, - 3, 4, 5}; - u32 *p; - u32 val = 0x12345678; - - p = (u32 *)(data + 1); - if (*p == 0) - val = 0x87654321; - *p = val; - break; - } + lkdtm_CORRUPT_STACK(); + break; + case CT_UNALIGNED_LOAD_STORE_WRITE: + lkdtm_UNALIGNED_LOAD_STORE_WRITE(); + break; case CT_OVERWRITE_ALLOCATION: lkdtm_OVERWRITE_ALLOCATION(); break; @@ -400,24 +358,16 @@ static void lkdtm_do_action(enum ctype which) lkdtm_READ_BUDDY_AFTER_FREE(); break; case CT_SOFTLOCKUP: - preempt_disable(); - for (;;) - cpu_relax(); + lkdtm_SOFTLOCKUP(); break; case CT_HARDLOCKUP: - local_irq_disable(); - for (;;) - cpu_relax(); + lkdtm_HARDLOCKUP(); break; case CT_SPINLOCKUP: - /* Must be called twice to trigger. */ - spin_lock(&lock_me_up); - /* Let sparse know we intended to exit holding the lock. */ - __release(&lock_me_up); + lkdtm_SPINLOCKUP(); break; case CT_HUNG_TASK: - set_current_state(TASK_UNINTERRUPTIBLE); - schedule(); + lkdtm_HUNG_TASK(); break; case CT_EXEC_DATA: lkdtm_EXEC_DATA(); @@ -449,29 +399,12 @@ static void lkdtm_do_action(enum ctype which) case CT_WRITE_KERN: lkdtm_WRITE_KERN(); break; - case CT_ATOMIC_UNDERFLOW: { - atomic_t under = ATOMIC_INIT(INT_MIN); - - pr_info("attempting good atomic increment\n"); - atomic_inc(&under); - atomic_dec(&under); - - pr_info("attempting bad atomic underflow\n"); - atomic_dec(&under); + case CT_ATOMIC_UNDERFLOW: + lkdtm_ATOMIC_UNDERFLOW(); + break; + case CT_ATOMIC_OVERFLOW: + lkdtm_ATOMIC_OVERFLOW(); break; - } - case CT_ATOMIC_OVERFLOW: { - atomic_t over = ATOMIC_INIT(INT_MAX); - - pr_info("attempting good atomic decrement\n"); - atomic_dec(&over); - atomic_inc(&over); - - pr_info("attempting bad atomic overflow\n"); - atomic_inc(&over); - - return; - } case CT_USERCOPY_HEAP_SIZE_TO: lkdtm_USERCOPY_HEAP_SIZE_TO(); break; @@ -786,6 +719,7 @@ static int __init lkdtm_module_init(void) int i; /* Handle test-specific initialization. */ + lkdtm_bugs_init(&recur_count); lkdtm_perms_init(); lkdtm_usercopy_init(); -- cgit v1.2.3-70-g09d2 From 6d2e91a662256fd88ec0505567a59d21094ed415 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 15 Jul 2016 16:04:39 -0700 Subject: lkdtm: silence warnings about function declarations When building under W=1, the lack of lkdtm.h in lkdtm_usercopy.c and lkdtm_rodata.c was discovered. This fixes the issue and consolidates the common header and the pr_fmt macro for simplicity and regularity across each test source file. Signed-off-by: Kees Cook --- drivers/misc/lkdtm.h | 5 ++++- drivers/misc/lkdtm_bugs.c | 6 +----- drivers/misc/lkdtm_core.c | 6 +----- drivers/misc/lkdtm_heap.c | 6 +----- drivers/misc/lkdtm_perms.c | 6 +----- drivers/misc/lkdtm_rodata.c | 2 +- drivers/misc/lkdtm_usercopy.c | 4 +--- 7 files changed, 10 insertions(+), 25 deletions(-) (limited to 'drivers/misc/lkdtm.h') diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index d70a41741bb3..fdf954c2107f 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -1,6 +1,10 @@ #ifndef __LKDTM_H #define __LKDTM_H +#define pr_fmt(fmt) "lkdtm: " fmt + +#include + /* lkdtm_bugs.c */ void __init lkdtm_bugs_init(int *recur_param); void lkdtm_PANIC(void); @@ -53,5 +57,4 @@ void lkdtm_USERCOPY_STACK_FRAME_FROM(void); void lkdtm_USERCOPY_STACK_BEYOND(void); void lkdtm_USERCOPY_KERNEL(void); - #endif diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c index e87071f9c003..182ae1894b32 100644 --- a/drivers/misc/lkdtm_bugs.c +++ b/drivers/misc/lkdtm_bugs.c @@ -4,12 +4,8 @@ * lockups) along with other things that don't fit well into existing LKDTM * test source files. */ -#define pr_fmt(fmt) "lkdtm: " fmt - -#include -#include - #include "lkdtm.h" +#include /* * Make sure our attempts to over run the kernel stack doesn't trigger diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index 717aad6d374b..f9154b8d67f6 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -30,9 +30,7 @@ * * See Documentation/fault-injection/provoke-crashes.txt for instructions */ -#define pr_fmt(fmt) "lkdtm: " fmt - -#include +#include "lkdtm.h" #include #include #include @@ -49,8 +47,6 @@ #include #endif -#include "lkdtm.h" - #define DEFAULT_COUNT 10 static int lkdtm_debugfs_open(struct inode *inode, struct file *file); diff --git a/drivers/misc/lkdtm_heap.c b/drivers/misc/lkdtm_heap.c index 12f50e8dcbfe..0f1581664c1c 100644 --- a/drivers/misc/lkdtm_heap.c +++ b/drivers/misc/lkdtm_heap.c @@ -2,12 +2,8 @@ * This is for all the tests relating directly to heap memory, including * page allocation and slab allocations. */ -#define pr_fmt(fmt) "lkdtm: " fmt - -#include -#include - #include "lkdtm.h" +#include /* * This tries to stay within the next largest power-of-2 kmalloc cache diff --git a/drivers/misc/lkdtm_perms.c b/drivers/misc/lkdtm_perms.c index 8201006502e2..45f1c0f96612 100644 --- a/drivers/misc/lkdtm_perms.c +++ b/drivers/misc/lkdtm_perms.c @@ -3,17 +3,13 @@ * permissions: non-executable regions, non-writable regions, and * even non-readable regions. */ -#define pr_fmt(fmt) "lkdtm: " fmt - -#include +#include "lkdtm.h" #include #include #include #include #include -#include "lkdtm.h" - /* Whether or not to fill the target memory area with do_nothing(). */ #define CODE_WRITE true #define CODE_AS_IS false diff --git a/drivers/misc/lkdtm_rodata.c b/drivers/misc/lkdtm_rodata.c index 4d0d851f02b9..166b1db3969f 100644 --- a/drivers/misc/lkdtm_rodata.c +++ b/drivers/misc/lkdtm_rodata.c @@ -2,7 +2,7 @@ * This includes functions that are meant to live entirely in .rodata * (via objcopy tricks), to validate the non-executability of .rodata. */ -#include +#include "lkdtm.h" void lkdtm_rodata_do_nothing(void) { diff --git a/drivers/misc/lkdtm_usercopy.c b/drivers/misc/lkdtm_usercopy.c index 9c748e819a35..5a3fd76eec27 100644 --- a/drivers/misc/lkdtm_usercopy.c +++ b/drivers/misc/lkdtm_usercopy.c @@ -2,9 +2,7 @@ * This is for all the tests related to copy_to_user() and copy_from_user() * hardening. */ -#define pr_fmt(fmt) "lkdtm: " fmt - -#include +#include "lkdtm.h" #include #include #include -- cgit v1.2.3-70-g09d2