Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86: merge 32/64-bit versions of 'strncpy_from_user()' and speed it up

This merges the 32- and 64-bit versions of the x86 strncpy_from_user()
by just rewriting it in C rather than the ancient inline asm versions
that used lodsb/stosb and had been duplicated for (trivial) differences
between the 32-bit and 64-bit versions.

While doing that, it also speeds them up by doing the accesses a word at
a time. Finally, the new routines also properly handle the case of
hitting the end of the address space, which we have never done correctly
before (fs/namei.c has a hack around it for that reason).

Despite all these improvements, it actually removes more lines than it
adds, due to the de-duplication. Also, we no longer export (or define)
the legacy __strncpy_from_user() function (that was defined to not do
the user permission checks), since it's not actually used anywhere, and
the user address space checks are built in to the new code.

Other architecture maintainers have been notified that the old hack in
fs/namei.c will be going away in the 3.5 merge window, in case they
copied the x86 approach of being a bit cavalier about the end of the
address space.

Cc: linux-arch@vger.kernel.org
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Peter Anvin" <hpa@zytor.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

+105 -145
+2
arch/x86/include/asm/uaccess.h
··· 557 557 558 558 extern unsigned long 559 559 copy_from_user_nmi(void *to, const void __user *from, unsigned long n); 560 + extern __must_check long 561 + strncpy_from_user(char *dst, const char __user *src, long count); 560 562 561 563 /* 562 564 * movsl can be slow when source and dest are not both 8-byte aligned
-5
arch/x86/include/asm/uaccess_32.h
··· 213 213 return n; 214 214 } 215 215 216 - long __must_check strncpy_from_user(char *dst, const char __user *src, 217 - long count); 218 - long __must_check __strncpy_from_user(char *dst, 219 - const char __user *src, long count); 220 - 221 216 /** 222 217 * strlen_user: - Get the size of a string in user space. 223 218 * @str: The string to measure.
-4
arch/x86/include/asm/uaccess_64.h
··· 208 208 } 209 209 } 210 210 211 - __must_check long 212 - strncpy_from_user(char *dst, const char __user *src, long count); 213 - __must_check long 214 - __strncpy_from_user(char *dst, const char __user *src, long count); 215 211 __must_check long strnlen_user(const char __user *str, long n); 216 212 __must_check long __strnlen_user(const char __user *str, long n); 217 213 __must_check long strlen_user(const char __user *str);
+103
arch/x86/lib/usercopy.c
··· 7 7 #include <linux/highmem.h> 8 8 #include <linux/module.h> 9 9 10 + #include <asm/word-at-a-time.h> 11 + 10 12 /* 11 13 * best effort, GUP based copy_from_user() that is NMI-safe 12 14 */ ··· 43 41 return len; 44 42 } 45 43 EXPORT_SYMBOL_GPL(copy_from_user_nmi); 44 + 45 + static inline unsigned long count_bytes(unsigned long mask) 46 + { 47 + mask = (mask - 1) & ~mask; 48 + mask >>= 7; 49 + return count_masked_bytes(mask); 50 + } 51 + 52 + /* 53 + * Do a strncpy, return length of string without final '\0'. 54 + * 'count' is the user-supplied count (return 'count' if we 55 + * hit it), 'max' is the address space maximum (and we return 56 + * -EFAULT if we hit it). 57 + */ 58 + static inline long do_strncpy_from_user(char *dst, const char __user *src, long count, long max) 59 + { 60 + long res = 0; 61 + 62 + /* 63 + * Truncate 'max' to the user-specified limit, so that 64 + * we only have one limit we need to check in the loop 65 + */ 66 + if (max > count) 67 + max = count; 68 + 69 + while (max >= sizeof(unsigned long)) { 70 + unsigned long c; 71 + 72 + /* Fall back to byte-at-a-time if we get a page fault */ 73 + if (unlikely(__get_user(c,(unsigned long __user *)(src+res)))) 74 + break; 75 + /* This can write a few bytes past the NUL character, but that's ok */ 76 + *(unsigned long *)(dst+res) = c; 77 + c = has_zero(c); 78 + if (c) 79 + return res + count_bytes(c); 80 + res += sizeof(unsigned long); 81 + max -= sizeof(unsigned long); 82 + } 83 + 84 + while (max) { 85 + char c; 86 + 87 + if (unlikely(__get_user(c,src+res))) 88 + return -EFAULT; 89 + dst[res] = c; 90 + if (!c) 91 + return res; 92 + res++; 93 + max--; 94 + } 95 + 96 + /* 97 + * Uhhuh. We hit 'max'. But was that the user-specified maximum 98 + * too? If so, that's ok - we got as much as the user asked for. 99 + */ 100 + if (res >= count) 101 + return count; 102 + 103 + /* 104 + * Nope: we hit the address space limit, and we still had more 105 + * characters the caller would have wanted. That's an EFAULT. 106 + */ 107 + return -EFAULT; 108 + } 109 + 110 + /** 111 + * strncpy_from_user: - Copy a NUL terminated string from userspace. 112 + * @dst: Destination address, in kernel space. This buffer must be at 113 + * least @count bytes long. 114 + * @src: Source address, in user space. 115 + * @count: Maximum number of bytes to copy, including the trailing NUL. 116 + * 117 + * Copies a NUL-terminated string from userspace to kernel space. 118 + * 119 + * On success, returns the length of the string (not including the trailing 120 + * NUL). 121 + * 122 + * If access to userspace fails, returns -EFAULT (some data may have been 123 + * copied). 124 + * 125 + * If @count is smaller than the length of the string, copies @count bytes 126 + * and returns @count. 127 + */ 128 + long 129 + strncpy_from_user(char *dst, const char __user *src, long count) 130 + { 131 + unsigned long max_addr, src_addr; 132 + 133 + if (unlikely(count <= 0)) 134 + return 0; 135 + 136 + max_addr = current_thread_info()->addr_limit.seg; 137 + src_addr = (unsigned long)src; 138 + if (likely(src_addr < max_addr)) { 139 + unsigned long max = max_addr - src_addr; 140 + return do_strncpy_from_user(dst, src, count, max); 141 + } 142 + return -EFAULT; 143 + } 144 + EXPORT_SYMBOL(strncpy_from_user);
-87
arch/x86/lib/usercopy_32.c
··· 33 33 __movsl_is_ok((unsigned long)(a1), (unsigned long)(a2), (n)) 34 34 35 35 /* 36 - * Copy a null terminated string from userspace. 37 - */ 38 - 39 - #define __do_strncpy_from_user(dst, src, count, res) \ 40 - do { \ 41 - int __d0, __d1, __d2; \ 42 - might_fault(); \ 43 - __asm__ __volatile__( \ 44 - " testl %1,%1\n" \ 45 - " jz 2f\n" \ 46 - "0: lodsb\n" \ 47 - " stosb\n" \ 48 - " testb %%al,%%al\n" \ 49 - " jz 1f\n" \ 50 - " decl %1\n" \ 51 - " jnz 0b\n" \ 52 - "1: subl %1,%0\n" \ 53 - "2:\n" \ 54 - ".section .fixup,\"ax\"\n" \ 55 - "3: movl %5,%0\n" \ 56 - " jmp 2b\n" \ 57 - ".previous\n" \ 58 - _ASM_EXTABLE(0b,3b) \ 59 - : "=&d"(res), "=&c"(count), "=&a" (__d0), "=&S" (__d1), \ 60 - "=&D" (__d2) \ 61 - : "i"(-EFAULT), "0"(count), "1"(count), "3"(src), "4"(dst) \ 62 - : "memory"); \ 63 - } while (0) 64 - 65 - /** 66 - * __strncpy_from_user: - Copy a NUL terminated string from userspace, with less checking. 67 - * @dst: Destination address, in kernel space. This buffer must be at 68 - * least @count bytes long. 69 - * @src: Source address, in user space. 70 - * @count: Maximum number of bytes to copy, including the trailing NUL. 71 - * 72 - * Copies a NUL-terminated string from userspace to kernel space. 73 - * Caller must check the specified block with access_ok() before calling 74 - * this function. 75 - * 76 - * On success, returns the length of the string (not including the trailing 77 - * NUL). 78 - * 79 - * If access to userspace fails, returns -EFAULT (some data may have been 80 - * copied). 81 - * 82 - * If @count is smaller than the length of the string, copies @count bytes 83 - * and returns @count. 84 - */ 85 - long 86 - __strncpy_from_user(char *dst, const char __user *src, long count) 87 - { 88 - long res; 89 - __do_strncpy_from_user(dst, src, count, res); 90 - return res; 91 - } 92 - EXPORT_SYMBOL(__strncpy_from_user); 93 - 94 - /** 95 - * strncpy_from_user: - Copy a NUL terminated string from userspace. 96 - * @dst: Destination address, in kernel space. This buffer must be at 97 - * least @count bytes long. 98 - * @src: Source address, in user space. 99 - * @count: Maximum number of bytes to copy, including the trailing NUL. 100 - * 101 - * Copies a NUL-terminated string from userspace to kernel space. 102 - * 103 - * On success, returns the length of the string (not including the trailing 104 - * NUL). 105 - * 106 - * If access to userspace fails, returns -EFAULT (some data may have been 107 - * copied). 108 - * 109 - * If @count is smaller than the length of the string, copies @count bytes 110 - * and returns @count. 111 - */ 112 - long 113 - strncpy_from_user(char *dst, const char __user *src, long count) 114 - { 115 - long res = -EFAULT; 116 - if (access_ok(VERIFY_READ, src, 1)) 117 - __do_strncpy_from_user(dst, src, count, res); 118 - return res; 119 - } 120 - EXPORT_SYMBOL(strncpy_from_user); 121 - 122 - /* 123 36 * Zero Userspace 124 37 */ 125 38
-49
arch/x86/lib/usercopy_64.c
··· 9 9 #include <asm/uaccess.h> 10 10 11 11 /* 12 - * Copy a null terminated string from userspace. 13 - */ 14 - 15 - #define __do_strncpy_from_user(dst,src,count,res) \ 16 - do { \ 17 - long __d0, __d1, __d2; \ 18 - might_fault(); \ 19 - __asm__ __volatile__( \ 20 - " testq %1,%1\n" \ 21 - " jz 2f\n" \ 22 - "0: lodsb\n" \ 23 - " stosb\n" \ 24 - " testb %%al,%%al\n" \ 25 - " jz 1f\n" \ 26 - " decq %1\n" \ 27 - " jnz 0b\n" \ 28 - "1: subq %1,%0\n" \ 29 - "2:\n" \ 30 - ".section .fixup,\"ax\"\n" \ 31 - "3: movq %5,%0\n" \ 32 - " jmp 2b\n" \ 33 - ".previous\n" \ 34 - _ASM_EXTABLE(0b,3b) \ 35 - : "=&r"(res), "=&c"(count), "=&a" (__d0), "=&S" (__d1), \ 36 - "=&D" (__d2) \ 37 - : "i"(-EFAULT), "0"(count), "1"(count), "3"(src), "4"(dst) \ 38 - : "memory"); \ 39 - } while (0) 40 - 41 - long 42 - __strncpy_from_user(char *dst, const char __user *src, long count) 43 - { 44 - long res; 45 - __do_strncpy_from_user(dst, src, count, res); 46 - return res; 47 - } 48 - EXPORT_SYMBOL(__strncpy_from_user); 49 - 50 - long 51 - strncpy_from_user(char *dst, const char __user *src, long count) 52 - { 53 - long res = -EFAULT; 54 - if (access_ok(VERIFY_READ, src, 1)) 55 - return __strncpy_from_user(dst, src, count); 56 - return res; 57 - } 58 - EXPORT_SYMBOL(strncpy_from_user); 59 - 60 - /* 61 12 * Zero Userspace 62 13 */ 63 14