Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : #include <linux/compiler.h> 3 : : #include <linux/export.h> 4 : : #include <linux/kasan-checks.h> 5 : : #include <linux/thread_info.h> 6 : : #include <linux/uaccess.h> 7 : : #include <linux/kernel.h> 8 : : #include <linux/errno.h> 9 : : #include <linux/mm.h> 10 : : 11 : : #include <asm/byteorder.h> 12 : : #include <asm/word-at-a-time.h> 13 : : 14 : : #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS 15 : : #define IS_UNALIGNED(src, dst) 0 16 : : #else 17 : : #define IS_UNALIGNED(src, dst) \ 18 : : (((long) dst | (long) src) & (sizeof(long) - 1)) 19 : : #endif 20 : : 21 : : /* 22 : : * Do a strncpy, return length of string without final '\0'. 23 : : * 'count' is the user-supplied count (return 'count' if we 24 : : * hit it), 'max' is the address space maximum (and we return 25 : : * -EFAULT if we hit it). 26 : : */ 27 : 3 : static inline long do_strncpy_from_user(char *dst, const char __user *src, 28 : : unsigned long count, unsigned long max) 29 : : { 30 : : const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; 31 : : unsigned long res = 0; 32 : : 33 : : if (IS_UNALIGNED(src, dst)) 34 : : goto byte_at_a_time; 35 : : 36 : 3 : while (max >= sizeof(unsigned long)) { 37 : : unsigned long c, data; 38 : : 39 : : /* Fall back to byte-at-a-time if we get a page fault */ 40 : 3 : unsafe_get_user(c, (unsigned long __user *)(src+res), byte_at_a_time); 41 : : 42 : 3 : *(unsigned long *)(dst+res) = c; 43 : 3 : if (has_zero(c, &data, &constants)) { 44 : 3 : data = prep_zero_mask(c, data, &constants); 45 : : data = create_zero_mask(data); 46 : 3 : return res + find_zero(data); 47 : : } 48 : 3 : res += sizeof(unsigned long); 49 : 3 : max -= sizeof(unsigned long); 50 : : } 51 : : 52 : : byte_at_a_time: 53 : 3 : while (max) { 54 : : char c; 55 : : 56 : 3 : unsafe_get_user(c,src+res, efault); 57 : 3 : dst[res] = c; 58 : 3 : if (!c) 59 : 3 : return res; 60 : 3 : res++; 61 : 3 : max--; 62 : : } 63 : : 64 : : /* 65 : : * Uhhuh. We hit 'max'. But was that the user-specified maximum 66 : : * too? If so, that's ok - we got as much as the user asked for. 67 : : */ 68 : 3 : if (res >= count) 69 : 3 : return res; 70 : : 71 : : /* 72 : : * Nope: we hit the address space limit, and we still had more 73 : : * characters the caller would have wanted. That's an EFAULT. 74 : : */ 75 : : efault: 76 : : return -EFAULT; 77 : : } 78 : : 79 : : /** 80 : : * strncpy_from_user: - Copy a NUL terminated string from userspace. 81 : : * @dst: Destination address, in kernel space. This buffer must be at 82 : : * least @count bytes long. 83 : : * @src: Source address, in user space. 84 : : * @count: Maximum number of bytes to copy, including the trailing NUL. 85 : : * 86 : : * Copies a NUL-terminated string from userspace to kernel space. 87 : : * 88 : : * On success, returns the length of the string (not including the trailing 89 : : * NUL). 90 : : * 91 : : * If access to userspace fails, returns -EFAULT (some data may have been 92 : : * copied). 93 : : * 94 : : * If @count is smaller than the length of the string, copies @count bytes 95 : : * and returns @count. 96 : : */ 97 : 3 : long strncpy_from_user(char *dst, const char __user *src, long count) 98 : : { 99 : : unsigned long max_addr, src_addr; 100 : : 101 : 3 : if (unlikely(count <= 0)) 102 : : return 0; 103 : : 104 : 3 : max_addr = user_addr_max(); 105 : 3 : src_addr = (unsigned long)untagged_addr(src); 106 : 3 : if (likely(src_addr < max_addr)) { 107 : 3 : unsigned long max = max_addr - src_addr; 108 : : long retval; 109 : : 110 : : /* 111 : : * Truncate 'max' to the user-specified limit, so that 112 : : * we only have one limit we need to check in the loop 113 : : */ 114 : 3 : if (max > count) 115 : : max = count; 116 : : 117 : : kasan_check_write(dst, count); 118 : : check_object_size(dst, count, false); 119 : 3 : if (user_access_begin(src, max)) { 120 : 3 : retval = do_strncpy_from_user(dst, src, count, max); 121 : : user_access_end(); 122 : 3 : return retval; 123 : : } 124 : : } 125 : : return -EFAULT; 126 : : } 127 : : EXPORT_SYMBOL(strncpy_from_user);