test_user_copy.c (9303B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Kernel module for testing copy_to/from_user infrastructure. 4 * 5 * Copyright 2013 Google Inc. All Rights Reserved 6 * 7 * Authors: 8 * Kees Cook <keescook@chromium.org> 9 */ 10 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 12 13#include <linux/mman.h> 14#include <linux/module.h> 15#include <linux/sched.h> 16#include <linux/slab.h> 17#include <linux/uaccess.h> 18#include <linux/vmalloc.h> 19 20/* 21 * Several 32-bit architectures support 64-bit {get,put}_user() calls. 22 * As there doesn't appear to be anything that can safely determine 23 * their capability at compile-time, we just have to opt-out certain archs. 24 */ 25#if BITS_PER_LONG == 64 || (!(defined(CONFIG_ARM) && !defined(MMU)) && \ 26 !defined(CONFIG_M68K) && \ 27 !defined(CONFIG_MICROBLAZE) && \ 28 !defined(CONFIG_NIOS2) && \ 29 !defined(CONFIG_PPC32) && \ 30 !defined(CONFIG_SUPERH)) 31# define TEST_U64 32#endif 33 34#define test(condition, msg, ...) \ 35({ \ 36 int cond = (condition); \ 37 if (cond) \ 38 pr_warn("[%d] " msg "\n", __LINE__, ##__VA_ARGS__); \ 39 cond; \ 40}) 41 42static bool is_zeroed(void *from, size_t size) 43{ 44 return memchr_inv(from, 0x0, size) == NULL; 45} 46 47static int test_check_nonzero_user(char *kmem, char __user *umem, size_t size) 48{ 49 int ret = 0; 50 size_t start, end, i, zero_start, zero_end; 51 52 if (test(size < 2 * PAGE_SIZE, "buffer too small")) 53 return -EINVAL; 54 55 /* 56 * We want to cross a page boundary to exercise the code more 57 * effectively. We also don't want to make the size we scan too large, 58 * otherwise the test can take a long time and cause soft lockups. So 59 * scan a 1024 byte region across the page boundary. 60 */ 61 size = 1024; 62 start = PAGE_SIZE - (size / 2); 63 64 kmem += start; 65 umem += start; 66 67 zero_start = size / 4; 68 zero_end = size - zero_start; 69 70 /* 71 * We conduct a series of check_nonzero_user() tests on a block of 72 * memory with the following byte-pattern (trying every possible 73 * [start,end] pair): 74 * 75 * [ 00 ff 00 ff ... 00 00 00 00 ... ff 00 ff 00 ] 76 * 77 * And we verify that check_nonzero_user() acts identically to 78 * memchr_inv(). 79 */ 80 81 memset(kmem, 0x0, size); 82 for (i = 1; i < zero_start; i += 2) 83 kmem[i] = 0xff; 84 for (i = zero_end; i < size; i += 2) 85 kmem[i] = 0xff; 86 87 ret |= test(copy_to_user(umem, kmem, size), 88 "legitimate copy_to_user failed"); 89 90 for (start = 0; start <= size; start++) { 91 for (end = start; end <= size; end++) { 92 size_t len = end - start; 93 int retval = check_zeroed_user(umem + start, len); 94 int expected = is_zeroed(kmem + start, len); 95 96 ret |= test(retval != expected, 97 "check_nonzero_user(=%d) != memchr_inv(=%d) mismatch (start=%zu, end=%zu)", 98 retval, expected, start, end); 99 } 100 } 101 102 return ret; 103} 104 105static int test_copy_struct_from_user(char *kmem, char __user *umem, 106 size_t size) 107{ 108 int ret = 0; 109 char *umem_src = NULL, *expected = NULL; 110 size_t ksize, usize; 111 112 umem_src = kmalloc(size, GFP_KERNEL); 113 ret = test(umem_src == NULL, "kmalloc failed"); 114 if (ret) 115 goto out_free; 116 117 expected = kmalloc(size, GFP_KERNEL); 118 ret = test(expected == NULL, "kmalloc failed"); 119 if (ret) 120 goto out_free; 121 122 /* Fill umem with a fixed byte pattern. */ 123 memset(umem_src, 0x3e, size); 124 ret |= test(copy_to_user(umem, umem_src, size), 125 "legitimate copy_to_user failed"); 126 127 /* Check basic case -- (usize == ksize). */ 128 ksize = size; 129 usize = size; 130 131 memcpy(expected, umem_src, ksize); 132 133 memset(kmem, 0x0, size); 134 ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 135 "copy_struct_from_user(usize == ksize) failed"); 136 ret |= test(memcmp(kmem, expected, ksize), 137 "copy_struct_from_user(usize == ksize) gives unexpected copy"); 138 139 /* Old userspace case -- (usize < ksize). */ 140 ksize = size; 141 usize = size / 2; 142 143 memcpy(expected, umem_src, usize); 144 memset(expected + usize, 0x0, ksize - usize); 145 146 memset(kmem, 0x0, size); 147 ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 148 "copy_struct_from_user(usize < ksize) failed"); 149 ret |= test(memcmp(kmem, expected, ksize), 150 "copy_struct_from_user(usize < ksize) gives unexpected copy"); 151 152 /* New userspace (-E2BIG) case -- (usize > ksize). */ 153 ksize = size / 2; 154 usize = size; 155 156 memset(kmem, 0x0, size); 157 ret |= test(copy_struct_from_user(kmem, ksize, umem, usize) != -E2BIG, 158 "copy_struct_from_user(usize > ksize) didn't give E2BIG"); 159 160 /* New userspace (success) case -- (usize > ksize). */ 161 ksize = size / 2; 162 usize = size; 163 164 memcpy(expected, umem_src, ksize); 165 ret |= test(clear_user(umem + ksize, usize - ksize), 166 "legitimate clear_user failed"); 167 168 memset(kmem, 0x0, size); 169 ret |= test(copy_struct_from_user(kmem, ksize, umem, usize), 170 "copy_struct_from_user(usize > ksize) failed"); 171 ret |= test(memcmp(kmem, expected, ksize), 172 "copy_struct_from_user(usize > ksize) gives unexpected copy"); 173 174out_free: 175 kfree(expected); 176 kfree(umem_src); 177 return ret; 178} 179 180static int __init test_user_copy_init(void) 181{ 182 int ret = 0; 183 char *kmem; 184 char __user *usermem; 185 char *bad_usermem; 186 unsigned long user_addr; 187 u8 val_u8; 188 u16 val_u16; 189 u32 val_u32; 190#ifdef TEST_U64 191 u64 val_u64; 192#endif 193 194 kmem = kmalloc(PAGE_SIZE * 2, GFP_KERNEL); 195 if (!kmem) 196 return -ENOMEM; 197 198 user_addr = vm_mmap(NULL, 0, PAGE_SIZE * 2, 199 PROT_READ | PROT_WRITE | PROT_EXEC, 200 MAP_ANONYMOUS | MAP_PRIVATE, 0); 201 if (user_addr >= (unsigned long)(TASK_SIZE)) { 202 pr_warn("Failed to allocate user memory\n"); 203 kfree(kmem); 204 return -ENOMEM; 205 } 206 207 usermem = (char __user *)user_addr; 208 bad_usermem = (char *)user_addr; 209 210 /* 211 * Legitimate usage: none of these copies should fail. 212 */ 213 memset(kmem, 0x3a, PAGE_SIZE * 2); 214 ret |= test(copy_to_user(usermem, kmem, PAGE_SIZE), 215 "legitimate copy_to_user failed"); 216 memset(kmem, 0x0, PAGE_SIZE); 217 ret |= test(copy_from_user(kmem, usermem, PAGE_SIZE), 218 "legitimate copy_from_user failed"); 219 ret |= test(memcmp(kmem, kmem + PAGE_SIZE, PAGE_SIZE), 220 "legitimate usercopy failed to copy data"); 221 222#define test_legit(size, check) \ 223 do { \ 224 val_##size = check; \ 225 ret |= test(put_user(val_##size, (size __user *)usermem), \ 226 "legitimate put_user (" #size ") failed"); \ 227 val_##size = 0; \ 228 ret |= test(get_user(val_##size, (size __user *)usermem), \ 229 "legitimate get_user (" #size ") failed"); \ 230 ret |= test(val_##size != check, \ 231 "legitimate get_user (" #size ") failed to do copy"); \ 232 if (val_##size != check) { \ 233 pr_info("0x%llx != 0x%llx\n", \ 234 (unsigned long long)val_##size, \ 235 (unsigned long long)check); \ 236 } \ 237 } while (0) 238 239 test_legit(u8, 0x5a); 240 test_legit(u16, 0x5a5b); 241 test_legit(u32, 0x5a5b5c5d); 242#ifdef TEST_U64 243 test_legit(u64, 0x5a5b5c5d6a6b6c6d); 244#endif 245#undef test_legit 246 247 /* Test usage of check_nonzero_user(). */ 248 ret |= test_check_nonzero_user(kmem, usermem, 2 * PAGE_SIZE); 249 /* Test usage of copy_struct_from_user(). */ 250 ret |= test_copy_struct_from_user(kmem, usermem, 2 * PAGE_SIZE); 251 252 /* 253 * Invalid usage: none of these copies should succeed. 254 */ 255 256 /* Prepare kernel memory with check values. */ 257 memset(kmem, 0x5a, PAGE_SIZE); 258 memset(kmem + PAGE_SIZE, 0, PAGE_SIZE); 259 260 /* Reject kernel-to-kernel copies through copy_from_user(). */ 261 ret |= test(!copy_from_user(kmem, (char __user *)(kmem + PAGE_SIZE), 262 PAGE_SIZE), 263 "illegal all-kernel copy_from_user passed"); 264 265 /* Destination half of buffer should have been zeroed. */ 266 ret |= test(memcmp(kmem + PAGE_SIZE, kmem, PAGE_SIZE), 267 "zeroing failure for illegal all-kernel copy_from_user"); 268 269#if 0 270 /* 271 * When running with SMAP/PAN/etc, this will Oops the kernel 272 * due to the zeroing of userspace memory on failure. This needs 273 * to be tested in LKDTM instead, since this test module does not 274 * expect to explode. 275 */ 276 ret |= test(!copy_from_user(bad_usermem, (char __user *)kmem, 277 PAGE_SIZE), 278 "illegal reversed copy_from_user passed"); 279#endif 280 ret |= test(!copy_to_user((char __user *)kmem, kmem + PAGE_SIZE, 281 PAGE_SIZE), 282 "illegal all-kernel copy_to_user passed"); 283 ret |= test(!copy_to_user((char __user *)kmem, bad_usermem, 284 PAGE_SIZE), 285 "illegal reversed copy_to_user passed"); 286 287#define test_illegal(size, check) \ 288 do { \ 289 val_##size = (check); \ 290 ret |= test(!get_user(val_##size, (size __user *)kmem), \ 291 "illegal get_user (" #size ") passed"); \ 292 ret |= test(val_##size != (size)0, \ 293 "zeroing failure for illegal get_user (" #size ")"); \ 294 if (val_##size != (size)0) { \ 295 pr_info("0x%llx != 0\n", \ 296 (unsigned long long)val_##size); \ 297 } \ 298 ret |= test(!put_user(val_##size, (size __user *)kmem), \ 299 "illegal put_user (" #size ") passed"); \ 300 } while (0) 301 302 test_illegal(u8, 0x5a); 303 test_illegal(u16, 0x5a5b); 304 test_illegal(u32, 0x5a5b5c5d); 305#ifdef TEST_U64 306 test_illegal(u64, 0x5a5b5c5d6a6b6c6d); 307#endif 308#undef test_illegal 309 310 vm_munmap(user_addr, PAGE_SIZE * 2); 311 kfree(kmem); 312 313 if (ret == 0) { 314 pr_info("tests passed.\n"); 315 return 0; 316 } 317 318 return -EINVAL; 319} 320 321module_init(test_user_copy_init); 322 323static void __exit test_user_copy_exit(void) 324{ 325 pr_info("unloaded.\n"); 326} 327 328module_exit(test_user_copy_exit); 329 330MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); 331MODULE_LICENSE("GPL");