162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Kernel module for testing copy_to/from_user infrastructure. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2013 Google Inc. All Rights Reserved 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Authors: 862306a36Sopenharmony_ci * Kees Cook <keescook@chromium.org> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/mman.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/sched.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/uaccess.h> 1862306a36Sopenharmony_ci#include <linux/vmalloc.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* 2162306a36Sopenharmony_ci * Several 32-bit architectures support 64-bit {get,put}_user() calls. 2262306a36Sopenharmony_ci * As there doesn't appear to be anything that can safely determine 2362306a36Sopenharmony_ci * their capability at compile-time, we just have to opt-out certain archs. 2462306a36Sopenharmony_ci */ 2562306a36Sopenharmony_ci#if BITS_PER_LONG == 64 || (!(defined(CONFIG_ARM) && !defined(MMU)) && \ 2662306a36Sopenharmony_ci !defined(CONFIG_M68K) && \ 2762306a36Sopenharmony_ci !defined(CONFIG_MICROBLAZE) && \ 2862306a36Sopenharmony_ci !defined(CONFIG_NIOS2) && \ 2962306a36Sopenharmony_ci !defined(CONFIG_PPC32) && \ 3062306a36Sopenharmony_ci !defined(CONFIG_SUPERH)) 3162306a36Sopenharmony_ci# define TEST_U64 3262306a36Sopenharmony_ci#endif 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define test(condition, msg, ...) \ 3562306a36Sopenharmony_ci({ \ 3662306a36Sopenharmony_ci int cond = (condition); \ 3762306a36Sopenharmony_ci if (cond) \ 3862306a36Sopenharmony_ci pr_warn("[%d] " msg "\n", __LINE__, ##__VA_ARGS__); \ 3962306a36Sopenharmony_ci cond; \ 4062306a36Sopenharmony_ci}) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic bool is_zeroed(void *from, size_t size) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci return memchr_inv(from, 0x0, size) == NULL; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int test_check_nonzero_user(char *kmem, char __user *umem, size_t size) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci int ret = 0; 5062306a36Sopenharmony_ci size_t start, end, i, zero_start, zero_end; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (test(size < 2 * PAGE_SIZE, "buffer too small")) 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* 5662306a36Sopenharmony_ci * We want to cross a page boundary to exercise the code more 5762306a36Sopenharmony_ci * effectively. We also don't want to make the size we scan too large, 5862306a36Sopenharmony_ci * otherwise the test can take a long time and cause soft lockups. So 5962306a36Sopenharmony_ci * scan a 1024 byte region across the page boundary. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_ci size = 1024; 6262306a36Sopenharmony_ci start = PAGE_SIZE - (size / 2); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci kmem += start; 6562306a36Sopenharmony_ci umem += start; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci zero_start = size / 4; 6862306a36Sopenharmony_ci zero_end = size - zero_start; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* 7162306a36Sopenharmony_ci * We conduct a series of check_nonzero_user() tests on a block of 7262306a36Sopenharmony_ci * memory with the following byte-pattern (trying every possible 7362306a36Sopenharmony_ci * [start,end] pair): 7462306a36Sopenharmony_ci * 7562306a36Sopenharmony_ci * [ 00 ff 00 ff ... 00 00 00 00 ... ff 00 ff 00 ] 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * And we verify that check_nonzero_user() acts identically to 7862306a36Sopenharmony_ci * memchr_inv(). 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci memset(kmem, 0x0, size); 8262306a36Sopenharmony_ci for (i = 1; i < zero_start; i += 2) 8362306a36Sopenharmony_ci kmem[i] = 0xff; 8462306a36Sopenharmony_ci for (i = zero_end; i < size; i += 2) 8562306a36Sopenharmony_ci kmem[i] = 0xff; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci ret |= test(copy_to_user(umem, kmem, size), 8862306a36Sopenharmony_ci "legitimate copy_to_user failed"); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci for (start = 0; start <= size; start++) { 9162306a36Sopenharmony_ci for (end = start; end <= size; end++) { 9262306a36Sopenharmony_ci size_t len = end - start; 9362306a36Sopenharmony_ci int retval = check_zeroed_user(umem + start, len); 9462306a36Sopenharmony_ci int expected = is_zeroed(kmem + start, len); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci ret |= test(retval != expected, 9762306a36Sopenharmony_ci "check_nonzero_user(=%d) != memchr_inv(=%d) mismatch (start=%zu, end=%zu)", 9862306a36Sopenharmony_ci retval, expected, start, end); 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci return ret; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int test_copy_struct_from_user(char *kmem, char __user *umem, 10662306a36Sopenharmony_ci size_t size) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci int ret = 0; 10962306a36Sopenharmony_ci char *umem_src = NULL, *expected = NULL; 11062306a36Sopenharmony_ci size_t ksize, usize; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci umem_src = kmalloc(size, GFP_KERNEL); 11362306a36Sopenharmony_ci ret = test(umem_src == NULL, "kmalloc failed"); 11462306a36Sopenharmony_ci if (ret) 11562306a36Sopenharmony_ci goto out_free; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci expected = kmalloc(size, GFP_KERNEL); 11862306a36Sopenharmony_ci ret = test(expected == NULL, "kmalloc failed"); 11962306a36Sopenharmony_ci if (ret) 12062306a36Sopenharmony_ci goto out_free; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* Fill umem with a fixed byte pattern. */ 12362306a36Sopenharmony_ci memset(umem_src, 0x3e, size); 12462306a36Sopenharmony_ci ret |= test(copy_to_user(umem, umem_src, size), 12562306a36Sopenharmony_ci "legitimate copy_to_user failed"); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Check basic case -- (usize == ksize). */ 12862306a36Sopenharmony_ci ksize = size; 12962306a36Sopenharmony_ci usize = size; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci memcpy(expected, umem_src, ksize); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci memset(kmem, 0x0, size); 13462306a36Sopenharmony_ci ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 13562306a36Sopenharmony_ci "copy_struct_from_user(usize == ksize) failed"); 13662306a36Sopenharmony_ci ret |= test(memcmp(kmem, expected, ksize), 13762306a36Sopenharmony_ci "copy_struct_from_user(usize == ksize) gives unexpected copy"); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Old userspace case -- (usize < ksize). */ 14062306a36Sopenharmony_ci ksize = size; 14162306a36Sopenharmony_ci usize = size / 2; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci memcpy(expected, umem_src, usize); 14462306a36Sopenharmony_ci memset(expected + usize, 0x0, ksize - usize); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci memset(kmem, 0x0, size); 14762306a36Sopenharmony_ci ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 14862306a36Sopenharmony_ci "copy_struct_from_user(usize < ksize) failed"); 14962306a36Sopenharmony_ci ret |= test(memcmp(kmem, expected, ksize), 15062306a36Sopenharmony_ci "copy_struct_from_user(usize < ksize) gives unexpected copy"); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* New userspace (-E2BIG) case -- (usize > ksize). */ 15362306a36Sopenharmony_ci ksize = size / 2; 15462306a36Sopenharmony_ci usize = size; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci memset(kmem, 0x0, size); 15762306a36Sopenharmony_ci ret |= test(copy_struct_from_user(kmem, ksize, umem, usize) != -E2BIG, 15862306a36Sopenharmony_ci "copy_struct_from_user(usize > ksize) didn't give E2BIG"); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* New userspace (success) case -- (usize > ksize). */ 16162306a36Sopenharmony_ci ksize = size / 2; 16262306a36Sopenharmony_ci usize = size; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci memcpy(expected, umem_src, ksize); 16562306a36Sopenharmony_ci ret |= test(clear_user(umem + ksize, usize - ksize), 16662306a36Sopenharmony_ci "legitimate clear_user failed"); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci memset(kmem, 0x0, size); 16962306a36Sopenharmony_ci ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 17062306a36Sopenharmony_ci "copy_struct_from_user(usize > ksize) failed"); 17162306a36Sopenharmony_ci ret |= test(memcmp(kmem, expected, ksize), 17262306a36Sopenharmony_ci "copy_struct_from_user(usize > ksize) gives unexpected copy"); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ciout_free: 17562306a36Sopenharmony_ci kfree(expected); 17662306a36Sopenharmony_ci kfree(umem_src); 17762306a36Sopenharmony_ci return ret; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic int __init test_user_copy_init(void) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci int ret = 0; 18362306a36Sopenharmony_ci char *kmem; 18462306a36Sopenharmony_ci char __user *usermem; 18562306a36Sopenharmony_ci char *bad_usermem; 18662306a36Sopenharmony_ci unsigned long user_addr; 18762306a36Sopenharmony_ci u8 val_u8; 18862306a36Sopenharmony_ci u16 val_u16; 18962306a36Sopenharmony_ci u32 val_u32; 19062306a36Sopenharmony_ci#ifdef TEST_U64 19162306a36Sopenharmony_ci u64 val_u64; 19262306a36Sopenharmony_ci#endif 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci kmem = kmalloc(PAGE_SIZE * 2, GFP_KERNEL); 19562306a36Sopenharmony_ci if (!kmem) 19662306a36Sopenharmony_ci return -ENOMEM; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci user_addr = vm_mmap(NULL, 0, PAGE_SIZE * 2, 19962306a36Sopenharmony_ci PROT_READ | PROT_WRITE | PROT_EXEC, 20062306a36Sopenharmony_ci MAP_ANONYMOUS | MAP_PRIVATE, 0); 20162306a36Sopenharmony_ci if (user_addr >= (unsigned long)(TASK_SIZE)) { 20262306a36Sopenharmony_ci pr_warn("Failed to allocate user memory\n"); 20362306a36Sopenharmony_ci kfree(kmem); 20462306a36Sopenharmony_ci return -ENOMEM; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci usermem = (char __user *)user_addr; 20862306a36Sopenharmony_ci bad_usermem = (char *)user_addr; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* 21162306a36Sopenharmony_ci * Legitimate usage: none of these copies should fail. 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_ci memset(kmem, 0x3a, PAGE_SIZE * 2); 21462306a36Sopenharmony_ci ret |= test(copy_to_user(usermem, kmem, PAGE_SIZE), 21562306a36Sopenharmony_ci "legitimate copy_to_user failed"); 21662306a36Sopenharmony_ci memset(kmem, 0x0, PAGE_SIZE); 21762306a36Sopenharmony_ci ret |= test(copy_from_user(kmem, usermem, PAGE_SIZE), 21862306a36Sopenharmony_ci "legitimate copy_from_user failed"); 21962306a36Sopenharmony_ci ret |= test(memcmp(kmem, kmem + PAGE_SIZE, PAGE_SIZE), 22062306a36Sopenharmony_ci "legitimate usercopy failed to copy data"); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci#define test_legit(size, check) \ 22362306a36Sopenharmony_ci do { \ 22462306a36Sopenharmony_ci val_##size = check; \ 22562306a36Sopenharmony_ci ret |= test(put_user(val_##size, (size __user *)usermem), \ 22662306a36Sopenharmony_ci "legitimate put_user (" #size ") failed"); \ 22762306a36Sopenharmony_ci val_##size = 0; \ 22862306a36Sopenharmony_ci ret |= test(get_user(val_##size, (size __user *)usermem), \ 22962306a36Sopenharmony_ci "legitimate get_user (" #size ") failed"); \ 23062306a36Sopenharmony_ci ret |= test(val_##size != check, \ 23162306a36Sopenharmony_ci "legitimate get_user (" #size ") failed to do copy"); \ 23262306a36Sopenharmony_ci if (val_##size != check) { \ 23362306a36Sopenharmony_ci pr_info("0x%llx != 0x%llx\n", \ 23462306a36Sopenharmony_ci (unsigned long long)val_##size, \ 23562306a36Sopenharmony_ci (unsigned long long)check); \ 23662306a36Sopenharmony_ci } \ 23762306a36Sopenharmony_ci } while (0) 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci test_legit(u8, 0x5a); 24062306a36Sopenharmony_ci test_legit(u16, 0x5a5b); 24162306a36Sopenharmony_ci test_legit(u32, 0x5a5b5c5d); 24262306a36Sopenharmony_ci#ifdef TEST_U64 24362306a36Sopenharmony_ci test_legit(u64, 0x5a5b5c5d6a6b6c6d); 24462306a36Sopenharmony_ci#endif 24562306a36Sopenharmony_ci#undef test_legit 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci /* Test usage of check_nonzero_user(). */ 24862306a36Sopenharmony_ci ret |= test_check_nonzero_user(kmem, usermem, 2 * PAGE_SIZE); 24962306a36Sopenharmony_ci /* Test usage of copy_struct_from_user(). */ 25062306a36Sopenharmony_ci ret |= test_copy_struct_from_user(kmem, usermem, 2 * PAGE_SIZE); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* 25362306a36Sopenharmony_ci * Invalid usage: none of these copies should succeed. 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* Prepare kernel memory with check values. */ 25762306a36Sopenharmony_ci memset(kmem, 0x5a, PAGE_SIZE); 25862306a36Sopenharmony_ci memset(kmem + PAGE_SIZE, 0, PAGE_SIZE); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Reject kernel-to-kernel copies through copy_from_user(). */ 26162306a36Sopenharmony_ci ret |= test(!copy_from_user(kmem, (char __user *)(kmem + PAGE_SIZE), 26262306a36Sopenharmony_ci PAGE_SIZE), 26362306a36Sopenharmony_ci "illegal all-kernel copy_from_user passed"); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Destination half of buffer should have been zeroed. */ 26662306a36Sopenharmony_ci ret |= test(memcmp(kmem + PAGE_SIZE, kmem, PAGE_SIZE), 26762306a36Sopenharmony_ci "zeroing failure for illegal all-kernel copy_from_user"); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci#if 0 27062306a36Sopenharmony_ci /* 27162306a36Sopenharmony_ci * When running with SMAP/PAN/etc, this will Oops the kernel 27262306a36Sopenharmony_ci * due to the zeroing of userspace memory on failure. This needs 27362306a36Sopenharmony_ci * to be tested in LKDTM instead, since this test module does not 27462306a36Sopenharmony_ci * expect to explode. 27562306a36Sopenharmony_ci */ 27662306a36Sopenharmony_ci ret |= test(!copy_from_user(bad_usermem, (char __user *)kmem, 27762306a36Sopenharmony_ci PAGE_SIZE), 27862306a36Sopenharmony_ci "illegal reversed copy_from_user passed"); 27962306a36Sopenharmony_ci#endif 28062306a36Sopenharmony_ci ret |= test(!copy_to_user((char __user *)kmem, kmem + PAGE_SIZE, 28162306a36Sopenharmony_ci PAGE_SIZE), 28262306a36Sopenharmony_ci "illegal all-kernel copy_to_user passed"); 28362306a36Sopenharmony_ci ret |= test(!copy_to_user((char __user *)kmem, bad_usermem, 28462306a36Sopenharmony_ci PAGE_SIZE), 28562306a36Sopenharmony_ci "illegal reversed copy_to_user passed"); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci#define test_illegal(size, check) \ 28862306a36Sopenharmony_ci do { \ 28962306a36Sopenharmony_ci val_##size = (check); \ 29062306a36Sopenharmony_ci ret |= test(!get_user(val_##size, (size __user *)kmem), \ 29162306a36Sopenharmony_ci "illegal get_user (" #size ") passed"); \ 29262306a36Sopenharmony_ci ret |= test(val_##size != (size)0, \ 29362306a36Sopenharmony_ci "zeroing failure for illegal get_user (" #size ")"); \ 29462306a36Sopenharmony_ci if (val_##size != (size)0) { \ 29562306a36Sopenharmony_ci pr_info("0x%llx != 0\n", \ 29662306a36Sopenharmony_ci (unsigned long long)val_##size); \ 29762306a36Sopenharmony_ci } \ 29862306a36Sopenharmony_ci ret |= test(!put_user(val_##size, (size __user *)kmem), \ 29962306a36Sopenharmony_ci "illegal put_user (" #size ") passed"); \ 30062306a36Sopenharmony_ci } while (0) 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci test_illegal(u8, 0x5a); 30362306a36Sopenharmony_ci test_illegal(u16, 0x5a5b); 30462306a36Sopenharmony_ci test_illegal(u32, 0x5a5b5c5d); 30562306a36Sopenharmony_ci#ifdef TEST_U64 30662306a36Sopenharmony_ci test_illegal(u64, 0x5a5b5c5d6a6b6c6d); 30762306a36Sopenharmony_ci#endif 30862306a36Sopenharmony_ci#undef test_illegal 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci vm_munmap(user_addr, PAGE_SIZE * 2); 31162306a36Sopenharmony_ci kfree(kmem); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (ret == 0) { 31462306a36Sopenharmony_ci pr_info("tests passed.\n"); 31562306a36Sopenharmony_ci return 0; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return -EINVAL; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cimodule_init(test_user_copy_init); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cistatic void __exit test_user_copy_exit(void) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci pr_info("unloaded.\n"); 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cimodule_exit(test_user_copy_exit); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ciMODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); 33162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 332