![Linus Torvalds](/assets/img/avatar_default.png)
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>
135 lines
2.8 KiB
C
135 lines
2.8 KiB
C
/*
|
|
* User address space access functions.
|
|
*
|
|
* Copyright 1997 Andi Kleen <ak@muc.de>
|
|
* Copyright 1997 Linus Torvalds
|
|
* Copyright 2002 Andi Kleen <ak@suse.de>
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
/*
|
|
* Zero Userspace
|
|
*/
|
|
|
|
unsigned long __clear_user(void __user *addr, unsigned long size)
|
|
{
|
|
long __d0;
|
|
might_fault();
|
|
/* no memory constraint because it doesn't change any memory gcc knows
|
|
about */
|
|
asm volatile(
|
|
" testq %[size8],%[size8]\n"
|
|
" jz 4f\n"
|
|
"0: movq %[zero],(%[dst])\n"
|
|
" addq %[eight],%[dst]\n"
|
|
" decl %%ecx ; jnz 0b\n"
|
|
"4: movq %[size1],%%rcx\n"
|
|
" testl %%ecx,%%ecx\n"
|
|
" jz 2f\n"
|
|
"1: movb %b[zero],(%[dst])\n"
|
|
" incq %[dst]\n"
|
|
" decl %%ecx ; jnz 1b\n"
|
|
"2:\n"
|
|
".section .fixup,\"ax\"\n"
|
|
"3: lea 0(%[size1],%[size8],8),%[size8]\n"
|
|
" jmp 2b\n"
|
|
".previous\n"
|
|
_ASM_EXTABLE(0b,3b)
|
|
_ASM_EXTABLE(1b,2b)
|
|
: [size8] "=&c"(size), [dst] "=&D" (__d0)
|
|
: [size1] "r"(size & 7), "[size8]" (size / 8), "[dst]"(addr),
|
|
[zero] "r" (0UL), [eight] "r" (8UL));
|
|
return size;
|
|
}
|
|
EXPORT_SYMBOL(__clear_user);
|
|
|
|
unsigned long clear_user(void __user *to, unsigned long n)
|
|
{
|
|
if (access_ok(VERIFY_WRITE, to, n))
|
|
return __clear_user(to, n);
|
|
return n;
|
|
}
|
|
EXPORT_SYMBOL(clear_user);
|
|
|
|
/*
|
|
* Return the size of a string (including the ending 0)
|
|
*
|
|
* Return 0 on exception, a value greater than N if too long
|
|
*/
|
|
|
|
long __strnlen_user(const char __user *s, long n)
|
|
{
|
|
long res = 0;
|
|
char c;
|
|
|
|
while (1) {
|
|
if (res>n)
|
|
return n+1;
|
|
if (__get_user(c, s))
|
|
return 0;
|
|
if (!c)
|
|
return res+1;
|
|
res++;
|
|
s++;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(__strnlen_user);
|
|
|
|
long strnlen_user(const char __user *s, long n)
|
|
{
|
|
if (!access_ok(VERIFY_READ, s, 1))
|
|
return 0;
|
|
return __strnlen_user(s, n);
|
|
}
|
|
EXPORT_SYMBOL(strnlen_user);
|
|
|
|
long strlen_user(const char __user *s)
|
|
{
|
|
long res = 0;
|
|
char c;
|
|
|
|
for (;;) {
|
|
if (get_user(c, s))
|
|
return 0;
|
|
if (!c)
|
|
return res+1;
|
|
res++;
|
|
s++;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(strlen_user);
|
|
|
|
unsigned long copy_in_user(void __user *to, const void __user *from, unsigned len)
|
|
{
|
|
if (access_ok(VERIFY_WRITE, to, len) && access_ok(VERIFY_READ, from, len)) {
|
|
return copy_user_generic((__force void *)to, (__force void *)from, len);
|
|
}
|
|
return len;
|
|
}
|
|
EXPORT_SYMBOL(copy_in_user);
|
|
|
|
/*
|
|
* Try to copy last bytes and clear the rest if needed.
|
|
* Since protection fault in copy_from/to_user is not a normal situation,
|
|
* it is not necessary to optimize tail handling.
|
|
*/
|
|
unsigned long
|
|
copy_user_handle_tail(char *to, char *from, unsigned len, unsigned zerorest)
|
|
{
|
|
char c;
|
|
unsigned zero_len;
|
|
|
|
for (; len; --len) {
|
|
if (__get_user_nocheck(c, from++, sizeof(char)))
|
|
break;
|
|
if (__put_user_nocheck(c, to++, sizeof(char)))
|
|
break;
|
|
}
|
|
|
|
for (c = 0, zero_len = len; zerorest && zero_len; --zero_len)
|
|
if (__put_user_nocheck(c, to++, sizeof(char)))
|
|
break;
|
|
return len;
|
|
}
|