mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-10 15:58:47 +00:00
d869844bd0
Commit cae2a173fe94 ("x86: clean up/fix 'copy_in_user()' tail zeroing") fixed the failure case tail zeroing of one special case of the x86-64 generic user-copy routine, namely when used for the user-to-user case ("copy_in_user()"). But in the process it broke an even more unusual case: using the user copy routine for kernel-to-kernel copying. Now, normally kernel-kernel copies are obviously done using memcpy(), but we have a couple of special cases when we use the user-copy functions. One is when we pass a kernel buffer to a regular user-buffer routine, using set_fs(KERNEL_DS). That's a "normal" case, and continued to work fine, because it never takes any faults (with the possible exception of a silent and successful vmalloc fault). But Jan Beulich pointed out another, very unusual, special case: when we use the user-copy routines not because it's a path that expects a user pointer, but for a couple of ftrace/kgdb cases that want to do a kernel copy, but do so using "unsafe" buffers, and use the user-copy routine to gracefully handle faults. IOW, for probe_kernel_write(). And that broke for the case of a faulting kernel destination, because we saw the kernel destination and wanted to try to clear the tail of the buffer. Which doesn't work, since that's what faults. This only triggers for things like kgdb and ftrace users (eg trying setting a breakpoint on read-only memory), but it's definitely a bug. The fix is to not compare against the kernel address start (TASK_SIZE), but instead use the same limits "access_ok()" uses. Reported-and-tested-by: Jan Beulich <jbeulich@suse.com> Cc: stable@vger.kernel.org # 4.0 Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
89 lines
2.1 KiB
C
89 lines
2.1 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 */
|
|
stac();
|
|
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));
|
|
clac();
|
|
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);
|
|
|
|
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.
|
|
*/
|
|
__visible unsigned long
|
|
copy_user_handle_tail(char *to, char *from, unsigned len)
|
|
{
|
|
for (; len; --len, to++) {
|
|
char c;
|
|
|
|
if (__get_user_nocheck(c, from++, sizeof(char)))
|
|
break;
|
|
if (__put_user_nocheck(c, to, sizeof(char)))
|
|
break;
|
|
}
|
|
clac();
|
|
|
|
/* If the destination is a kernel buffer, we always clear the end */
|
|
if (!__addr_ok(to))
|
|
memset(to, 0, len);
|
|
return len;
|
|
}
|