162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * This is for all the tests related to copy_to_user() and copy_from_user() 462306a36Sopenharmony_ci * hardening. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include "lkdtm.h" 762306a36Sopenharmony_ci#include <linux/slab.h> 862306a36Sopenharmony_ci#include <linux/highmem.h> 962306a36Sopenharmony_ci#include <linux/vmalloc.h> 1062306a36Sopenharmony_ci#include <linux/sched/task_stack.h> 1162306a36Sopenharmony_ci#include <linux/mman.h> 1262306a36Sopenharmony_ci#include <linux/uaccess.h> 1362306a36Sopenharmony_ci#include <asm/cacheflush.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/* 1662306a36Sopenharmony_ci * Many of the tests here end up using const sizes, but those would 1762306a36Sopenharmony_ci * normally be ignored by hardened usercopy, so force the compiler 1862306a36Sopenharmony_ci * into choosing the non-const path to make sure we trigger the 1962306a36Sopenharmony_ci * hardened usercopy checks by added "unconst" to all the const copies, 2062306a36Sopenharmony_ci * and making sure "cache_size" isn't optimized into a const. 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_cistatic volatile size_t unconst; 2362306a36Sopenharmony_cistatic volatile size_t cache_size = 1024; 2462306a36Sopenharmony_cistatic struct kmem_cache *whitelist_cache; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic const unsigned char test_text[] = "This is a test.\n"; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* 2962306a36Sopenharmony_ci * Instead of adding -Wno-return-local-addr, just pass the stack address 3062306a36Sopenharmony_ci * through a function to obfuscate it from the compiler. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistatic noinline unsigned char *trick_compiler(unsigned char *stack) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci return stack + unconst; 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic noinline unsigned char *do_usercopy_stack_callee(int value) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci unsigned char buf[128]; 4062306a36Sopenharmony_ci int i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* Exercise stack to avoid everything living in registers. */ 4362306a36Sopenharmony_ci for (i = 0; i < sizeof(buf); i++) { 4462306a36Sopenharmony_ci buf[i] = value & 0xff; 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* 4862306a36Sopenharmony_ci * Put the target buffer in the middle of stack allocation 4962306a36Sopenharmony_ci * so that we don't step on future stack users regardless 5062306a36Sopenharmony_ci * of stack growth direction. 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_ci return trick_compiler(&buf[(128/2)-32]); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic noinline void do_usercopy_stack(bool to_user, bool bad_frame) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci unsigned long user_addr; 5862306a36Sopenharmony_ci unsigned char good_stack[32]; 5962306a36Sopenharmony_ci unsigned char *bad_stack; 6062306a36Sopenharmony_ci int i; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* Exercise stack to avoid everything living in registers. */ 6362306a36Sopenharmony_ci for (i = 0; i < sizeof(good_stack); i++) 6462306a36Sopenharmony_ci good_stack[i] = test_text[i % sizeof(test_text)]; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* This is a pointer to outside our current stack frame. */ 6762306a36Sopenharmony_ci if (bad_frame) { 6862306a36Sopenharmony_ci bad_stack = do_usercopy_stack_callee((uintptr_t)&bad_stack); 6962306a36Sopenharmony_ci } else { 7062306a36Sopenharmony_ci /* Put start address just inside stack. */ 7162306a36Sopenharmony_ci bad_stack = task_stack_page(current) + THREAD_SIZE; 7262306a36Sopenharmony_ci bad_stack -= sizeof(unsigned long); 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci#ifdef ARCH_HAS_CURRENT_STACK_POINTER 7662306a36Sopenharmony_ci pr_info("stack : %px\n", (void *)current_stack_pointer); 7762306a36Sopenharmony_ci#endif 7862306a36Sopenharmony_ci pr_info("good_stack: %px-%px\n", good_stack, good_stack + sizeof(good_stack)); 7962306a36Sopenharmony_ci pr_info("bad_stack : %px-%px\n", bad_stack, bad_stack + sizeof(good_stack)); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci user_addr = vm_mmap(NULL, 0, PAGE_SIZE, 8262306a36Sopenharmony_ci PROT_READ | PROT_WRITE | PROT_EXEC, 8362306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 8462306a36Sopenharmony_ci if (user_addr >= TASK_SIZE) { 8562306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 8662306a36Sopenharmony_ci return; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (to_user) { 9062306a36Sopenharmony_ci pr_info("attempting good copy_to_user of local stack\n"); 9162306a36Sopenharmony_ci if (copy_to_user((void __user *)user_addr, good_stack, 9262306a36Sopenharmony_ci unconst + sizeof(good_stack))) { 9362306a36Sopenharmony_ci pr_warn("copy_to_user failed unexpectedly?!\n"); 9462306a36Sopenharmony_ci goto free_user; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci pr_info("attempting bad copy_to_user of distant stack\n"); 9862306a36Sopenharmony_ci if (copy_to_user((void __user *)user_addr, bad_stack, 9962306a36Sopenharmony_ci unconst + sizeof(good_stack))) { 10062306a36Sopenharmony_ci pr_warn("copy_to_user failed, but lacked Oops\n"); 10162306a36Sopenharmony_ci goto free_user; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci } else { 10462306a36Sopenharmony_ci /* 10562306a36Sopenharmony_ci * There isn't a safe way to not be protected by usercopy 10662306a36Sopenharmony_ci * if we're going to write to another thread's stack. 10762306a36Sopenharmony_ci */ 10862306a36Sopenharmony_ci if (!bad_frame) 10962306a36Sopenharmony_ci goto free_user; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci pr_info("attempting good copy_from_user of local stack\n"); 11262306a36Sopenharmony_ci if (copy_from_user(good_stack, (void __user *)user_addr, 11362306a36Sopenharmony_ci unconst + sizeof(good_stack))) { 11462306a36Sopenharmony_ci pr_warn("copy_from_user failed unexpectedly?!\n"); 11562306a36Sopenharmony_ci goto free_user; 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci pr_info("attempting bad copy_from_user of distant stack\n"); 11962306a36Sopenharmony_ci if (copy_from_user(bad_stack, (void __user *)user_addr, 12062306a36Sopenharmony_ci unconst + sizeof(good_stack))) { 12162306a36Sopenharmony_ci pr_warn("copy_from_user failed, but lacked Oops\n"); 12262306a36Sopenharmony_ci goto free_user; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cifree_user: 12762306a36Sopenharmony_ci vm_munmap(user_addr, PAGE_SIZE); 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci/* 13162306a36Sopenharmony_ci * This checks for whole-object size validation with hardened usercopy, 13262306a36Sopenharmony_ci * with or without usercopy whitelisting. 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_cistatic void do_usercopy_slab_size(bool to_user) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci unsigned long user_addr; 13762306a36Sopenharmony_ci unsigned char *one, *two; 13862306a36Sopenharmony_ci void __user *test_user_addr; 13962306a36Sopenharmony_ci void *test_kern_addr; 14062306a36Sopenharmony_ci size_t size = unconst + 1024; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci one = kmalloc(size, GFP_KERNEL); 14362306a36Sopenharmony_ci two = kmalloc(size, GFP_KERNEL); 14462306a36Sopenharmony_ci if (!one || !two) { 14562306a36Sopenharmony_ci pr_warn("Failed to allocate kernel memory\n"); 14662306a36Sopenharmony_ci goto free_kernel; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci user_addr = vm_mmap(NULL, 0, PAGE_SIZE, 15062306a36Sopenharmony_ci PROT_READ | PROT_WRITE | PROT_EXEC, 15162306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 15262306a36Sopenharmony_ci if (user_addr >= TASK_SIZE) { 15362306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 15462306a36Sopenharmony_ci goto free_kernel; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci memset(one, 'A', size); 15862306a36Sopenharmony_ci memset(two, 'B', size); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci test_user_addr = (void __user *)(user_addr + 16); 16162306a36Sopenharmony_ci test_kern_addr = one + 16; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (to_user) { 16462306a36Sopenharmony_ci pr_info("attempting good copy_to_user of correct size\n"); 16562306a36Sopenharmony_ci if (copy_to_user(test_user_addr, test_kern_addr, size / 2)) { 16662306a36Sopenharmony_ci pr_warn("copy_to_user failed unexpectedly?!\n"); 16762306a36Sopenharmony_ci goto free_user; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci pr_info("attempting bad copy_to_user of too large size\n"); 17162306a36Sopenharmony_ci if (copy_to_user(test_user_addr, test_kern_addr, size)) { 17262306a36Sopenharmony_ci pr_warn("copy_to_user failed, but lacked Oops\n"); 17362306a36Sopenharmony_ci goto free_user; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci } else { 17662306a36Sopenharmony_ci pr_info("attempting good copy_from_user of correct size\n"); 17762306a36Sopenharmony_ci if (copy_from_user(test_kern_addr, test_user_addr, size / 2)) { 17862306a36Sopenharmony_ci pr_warn("copy_from_user failed unexpectedly?!\n"); 17962306a36Sopenharmony_ci goto free_user; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci pr_info("attempting bad copy_from_user of too large size\n"); 18362306a36Sopenharmony_ci if (copy_from_user(test_kern_addr, test_user_addr, size)) { 18462306a36Sopenharmony_ci pr_warn("copy_from_user failed, but lacked Oops\n"); 18562306a36Sopenharmony_ci goto free_user; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci pr_err("FAIL: bad usercopy not detected!\n"); 18962306a36Sopenharmony_ci pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy"); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cifree_user: 19262306a36Sopenharmony_ci vm_munmap(user_addr, PAGE_SIZE); 19362306a36Sopenharmony_cifree_kernel: 19462306a36Sopenharmony_ci kfree(one); 19562306a36Sopenharmony_ci kfree(two); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/* 19962306a36Sopenharmony_ci * This checks for the specific whitelist window within an object. If this 20062306a36Sopenharmony_ci * test passes, then do_usercopy_slab_size() tests will pass too. 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_cistatic void do_usercopy_slab_whitelist(bool to_user) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci unsigned long user_alloc; 20562306a36Sopenharmony_ci unsigned char *buf = NULL; 20662306a36Sopenharmony_ci unsigned char __user *user_addr; 20762306a36Sopenharmony_ci size_t offset, size; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* Make sure cache was prepared. */ 21062306a36Sopenharmony_ci if (!whitelist_cache) { 21162306a36Sopenharmony_ci pr_warn("Failed to allocate kernel cache\n"); 21262306a36Sopenharmony_ci return; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* 21662306a36Sopenharmony_ci * Allocate a buffer with a whitelisted window in the buffer. 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci buf = kmem_cache_alloc(whitelist_cache, GFP_KERNEL); 21962306a36Sopenharmony_ci if (!buf) { 22062306a36Sopenharmony_ci pr_warn("Failed to allocate buffer from whitelist cache\n"); 22162306a36Sopenharmony_ci goto free_alloc; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci /* Allocate user memory we'll poke at. */ 22562306a36Sopenharmony_ci user_alloc = vm_mmap(NULL, 0, PAGE_SIZE, 22662306a36Sopenharmony_ci PROT_READ | PROT_WRITE | PROT_EXEC, 22762306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 22862306a36Sopenharmony_ci if (user_alloc >= TASK_SIZE) { 22962306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 23062306a36Sopenharmony_ci goto free_alloc; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci user_addr = (void __user *)user_alloc; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci memset(buf, 'B', cache_size); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* Whitelisted window in buffer, from kmem_cache_create_usercopy. */ 23762306a36Sopenharmony_ci offset = (cache_size / 4) + unconst; 23862306a36Sopenharmony_ci size = (cache_size / 16) + unconst; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (to_user) { 24162306a36Sopenharmony_ci pr_info("attempting good copy_to_user inside whitelist\n"); 24262306a36Sopenharmony_ci if (copy_to_user(user_addr, buf + offset, size)) { 24362306a36Sopenharmony_ci pr_warn("copy_to_user failed unexpectedly?!\n"); 24462306a36Sopenharmony_ci goto free_user; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci pr_info("attempting bad copy_to_user outside whitelist\n"); 24862306a36Sopenharmony_ci if (copy_to_user(user_addr, buf + offset - 1, size)) { 24962306a36Sopenharmony_ci pr_warn("copy_to_user failed, but lacked Oops\n"); 25062306a36Sopenharmony_ci goto free_user; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci } else { 25362306a36Sopenharmony_ci pr_info("attempting good copy_from_user inside whitelist\n"); 25462306a36Sopenharmony_ci if (copy_from_user(buf + offset, user_addr, size)) { 25562306a36Sopenharmony_ci pr_warn("copy_from_user failed unexpectedly?!\n"); 25662306a36Sopenharmony_ci goto free_user; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci pr_info("attempting bad copy_from_user outside whitelist\n"); 26062306a36Sopenharmony_ci if (copy_from_user(buf + offset - 1, user_addr, size)) { 26162306a36Sopenharmony_ci pr_warn("copy_from_user failed, but lacked Oops\n"); 26262306a36Sopenharmony_ci goto free_user; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci pr_err("FAIL: bad usercopy not detected!\n"); 26662306a36Sopenharmony_ci pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy"); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cifree_user: 26962306a36Sopenharmony_ci vm_munmap(user_alloc, PAGE_SIZE); 27062306a36Sopenharmony_cifree_alloc: 27162306a36Sopenharmony_ci if (buf) 27262306a36Sopenharmony_ci kmem_cache_free(whitelist_cache, buf); 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci/* Callable tests. */ 27662306a36Sopenharmony_cistatic void lkdtm_USERCOPY_SLAB_SIZE_TO(void) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci do_usercopy_slab_size(true); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic void lkdtm_USERCOPY_SLAB_SIZE_FROM(void) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci do_usercopy_slab_size(false); 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic void lkdtm_USERCOPY_SLAB_WHITELIST_TO(void) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci do_usercopy_slab_whitelist(true); 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic void lkdtm_USERCOPY_SLAB_WHITELIST_FROM(void) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci do_usercopy_slab_whitelist(false); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic void lkdtm_USERCOPY_STACK_FRAME_TO(void) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci do_usercopy_stack(true, true); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic void lkdtm_USERCOPY_STACK_FRAME_FROM(void) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci do_usercopy_stack(false, true); 30462306a36Sopenharmony_ci} 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cistatic void lkdtm_USERCOPY_STACK_BEYOND(void) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci do_usercopy_stack(true, false); 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic void lkdtm_USERCOPY_KERNEL(void) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci unsigned long user_addr; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci user_addr = vm_mmap(NULL, 0, PAGE_SIZE, 31662306a36Sopenharmony_ci PROT_READ | PROT_WRITE | PROT_EXEC, 31762306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 31862306a36Sopenharmony_ci if (user_addr >= TASK_SIZE) { 31962306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 32062306a36Sopenharmony_ci return; 32162306a36Sopenharmony_ci } 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci pr_info("attempting good copy_to_user from kernel rodata: %px\n", 32462306a36Sopenharmony_ci test_text); 32562306a36Sopenharmony_ci if (copy_to_user((void __user *)user_addr, test_text, 32662306a36Sopenharmony_ci unconst + sizeof(test_text))) { 32762306a36Sopenharmony_ci pr_warn("copy_to_user failed unexpectedly?!\n"); 32862306a36Sopenharmony_ci goto free_user; 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci pr_info("attempting bad copy_to_user from kernel text: %px\n", 33262306a36Sopenharmony_ci vm_mmap); 33362306a36Sopenharmony_ci if (copy_to_user((void __user *)user_addr, vm_mmap, 33462306a36Sopenharmony_ci unconst + PAGE_SIZE)) { 33562306a36Sopenharmony_ci pr_warn("copy_to_user failed, but lacked Oops\n"); 33662306a36Sopenharmony_ci goto free_user; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci pr_err("FAIL: bad copy_to_user() not detected!\n"); 33962306a36Sopenharmony_ci pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy"); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cifree_user: 34262306a36Sopenharmony_ci vm_munmap(user_addr, PAGE_SIZE); 34362306a36Sopenharmony_ci} 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci/* 34662306a36Sopenharmony_ci * This expects "kaddr" to point to a PAGE_SIZE allocation, which means 34762306a36Sopenharmony_ci * a more complete test that would include copy_from_user() would risk 34862306a36Sopenharmony_ci * memory corruption. Just test copy_to_user() here, as that exercises 34962306a36Sopenharmony_ci * almost exactly the same code paths. 35062306a36Sopenharmony_ci */ 35162306a36Sopenharmony_cistatic void do_usercopy_page_span(const char *name, void *kaddr) 35262306a36Sopenharmony_ci{ 35362306a36Sopenharmony_ci unsigned long uaddr; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci uaddr = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_WRITE, 35662306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 35762306a36Sopenharmony_ci if (uaddr >= TASK_SIZE) { 35862306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 35962306a36Sopenharmony_ci return; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci /* Initialize contents. */ 36362306a36Sopenharmony_ci memset(kaddr, 0xAA, PAGE_SIZE); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci /* Bump the kaddr forward to detect a page-spanning overflow. */ 36662306a36Sopenharmony_ci kaddr += PAGE_SIZE / 2; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci pr_info("attempting good copy_to_user() from kernel %s: %px\n", 36962306a36Sopenharmony_ci name, kaddr); 37062306a36Sopenharmony_ci if (copy_to_user((void __user *)uaddr, kaddr, 37162306a36Sopenharmony_ci unconst + (PAGE_SIZE / 2))) { 37262306a36Sopenharmony_ci pr_err("copy_to_user() failed unexpectedly?!\n"); 37362306a36Sopenharmony_ci goto free_user; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci pr_info("attempting bad copy_to_user() from kernel %s: %px\n", 37762306a36Sopenharmony_ci name, kaddr); 37862306a36Sopenharmony_ci if (copy_to_user((void __user *)uaddr, kaddr, unconst + PAGE_SIZE)) { 37962306a36Sopenharmony_ci pr_warn("Good, copy_to_user() failed, but lacked Oops(?!)\n"); 38062306a36Sopenharmony_ci goto free_user; 38162306a36Sopenharmony_ci } 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci pr_err("FAIL: bad copy_to_user() not detected!\n"); 38462306a36Sopenharmony_ci pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy"); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cifree_user: 38762306a36Sopenharmony_ci vm_munmap(uaddr, PAGE_SIZE); 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_cistatic void lkdtm_USERCOPY_VMALLOC(void) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci void *addr; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci addr = vmalloc(PAGE_SIZE); 39562306a36Sopenharmony_ci if (!addr) { 39662306a36Sopenharmony_ci pr_err("vmalloc() failed!?\n"); 39762306a36Sopenharmony_ci return; 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci do_usercopy_page_span("vmalloc", addr); 40062306a36Sopenharmony_ci vfree(addr); 40162306a36Sopenharmony_ci} 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cistatic void lkdtm_USERCOPY_FOLIO(void) 40462306a36Sopenharmony_ci{ 40562306a36Sopenharmony_ci struct folio *folio; 40662306a36Sopenharmony_ci void *addr; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci /* 40962306a36Sopenharmony_ci * FIXME: Folio checking currently misses 0-order allocations, so 41062306a36Sopenharmony_ci * allocate and bump forward to the last page. 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_ci folio = folio_alloc(GFP_KERNEL | __GFP_ZERO, 1); 41362306a36Sopenharmony_ci if (!folio) { 41462306a36Sopenharmony_ci pr_err("folio_alloc() failed!?\n"); 41562306a36Sopenharmony_ci return; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci addr = folio_address(folio); 41862306a36Sopenharmony_ci if (addr) 41962306a36Sopenharmony_ci do_usercopy_page_span("folio", addr + PAGE_SIZE); 42062306a36Sopenharmony_ci else 42162306a36Sopenharmony_ci pr_err("folio_address() failed?!\n"); 42262306a36Sopenharmony_ci folio_put(folio); 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_civoid __init lkdtm_usercopy_init(void) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci /* Prepare cache that lacks SLAB_USERCOPY flag. */ 42862306a36Sopenharmony_ci whitelist_cache = 42962306a36Sopenharmony_ci kmem_cache_create_usercopy("lkdtm-usercopy", cache_size, 43062306a36Sopenharmony_ci 0, 0, 43162306a36Sopenharmony_ci cache_size / 4, 43262306a36Sopenharmony_ci cache_size / 16, 43362306a36Sopenharmony_ci NULL); 43462306a36Sopenharmony_ci} 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_civoid __exit lkdtm_usercopy_exit(void) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci kmem_cache_destroy(whitelist_cache); 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic struct crashtype crashtypes[] = { 44262306a36Sopenharmony_ci CRASHTYPE(USERCOPY_SLAB_SIZE_TO), 44362306a36Sopenharmony_ci CRASHTYPE(USERCOPY_SLAB_SIZE_FROM), 44462306a36Sopenharmony_ci CRASHTYPE(USERCOPY_SLAB_WHITELIST_TO), 44562306a36Sopenharmony_ci CRASHTYPE(USERCOPY_SLAB_WHITELIST_FROM), 44662306a36Sopenharmony_ci CRASHTYPE(USERCOPY_STACK_FRAME_TO), 44762306a36Sopenharmony_ci CRASHTYPE(USERCOPY_STACK_FRAME_FROM), 44862306a36Sopenharmony_ci CRASHTYPE(USERCOPY_STACK_BEYOND), 44962306a36Sopenharmony_ci CRASHTYPE(USERCOPY_VMALLOC), 45062306a36Sopenharmony_ci CRASHTYPE(USERCOPY_FOLIO), 45162306a36Sopenharmony_ci CRASHTYPE(USERCOPY_KERNEL), 45262306a36Sopenharmony_ci}; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistruct crashtype_category usercopy_crashtypes = { 45562306a36Sopenharmony_ci .crashtypes = crashtypes, 45662306a36Sopenharmony_ci .len = ARRAY_SIZE(crashtypes), 45762306a36Sopenharmony_ci}; 458