Commit e28d0b67 authored by Christophe Leroy's avatar Christophe Leroy Committed by Michael Ellerman

powerpc/lib/sstep: Don't use __{get/put}_user() on kernel addresses

In the old days, when we didn't have kernel userspace access
protection and had set_fs(), it was wise to use __get_user()
and friends to read kernel memory.

Nowadays, get_user() and put_user() are granting userspace access and
are exclusively for userspace access.

Convert single step emulation functions to user_access_begin() and
friends and use unsafe_get_user() and unsafe_put_user().

When addressing kernel addresses, there is no need to open userspace
access. And for book3s/32 it is particularly important to no try and
open userspace access on kernel address, because that would break the
content of kernel space segment registers. No guard has been put
against that risk in order to avoid degrading performance.

copy_from_kernel_nofault() and copy_to_kernel_nofault() should
be used but they are out-of-line functions which would degrade
performance. Those two functions are making use of
__get_kernel_nofault() and __put_kernel_nofault() macros.
Those two macros are just wrappers behind __get_user_size_goto() and
__put_user_size_goto().

unsafe_get_user() and unsafe_put_user() are also wrappers of
__get_user_size_goto() and __put_user_size_goto(). Use them to
access kernel space. That allows refactoring userspace and
kernelspace access.
Reported-by: default avatarStan Johnson <userm57@yahoo.com>
Signed-off-by: default avatarChristophe Leroy <christophe.leroy@csgroup.eu>
Depends-on: 4fe5cda9 ("powerpc/uaccess: Implement user_read_access_begin and user_write_access_begin")
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/22831c9d17f948680a12c5292e7627288b15f713.1631817805.git.christophe.leroy@csgroup.eu
parent cbe654c7
...@@ -302,33 +302,51 @@ static nokprobe_inline void do_byte_reverse(void *ptr, int nb) ...@@ -302,33 +302,51 @@ static nokprobe_inline void do_byte_reverse(void *ptr, int nb)
} }
} }
static nokprobe_inline int read_mem_aligned(unsigned long *dest, static __always_inline int
unsigned long ea, int nb, __read_mem_aligned(unsigned long *dest, unsigned long ea, int nb, struct pt_regs *regs)
struct pt_regs *regs)
{ {
int err = 0;
unsigned long x = 0; unsigned long x = 0;
switch (nb) { switch (nb) {
case 1: case 1:
err = __get_user(x, (unsigned char __user *) ea); unsafe_get_user(x, (unsigned char __user *)ea, Efault);
break; break;
case 2: case 2:
err = __get_user(x, (unsigned short __user *) ea); unsafe_get_user(x, (unsigned short __user *)ea, Efault);
break; break;
case 4: case 4:
err = __get_user(x, (unsigned int __user *) ea); unsafe_get_user(x, (unsigned int __user *)ea, Efault);
break; break;
#ifdef __powerpc64__ #ifdef __powerpc64__
case 8: case 8:
err = __get_user(x, (unsigned long __user *) ea); unsafe_get_user(x, (unsigned long __user *)ea, Efault);
break; break;
#endif #endif
} }
if (!err)
*dest = x; *dest = x;
else return 0;
Efault:
regs->dar = ea; regs->dar = ea;
return -EFAULT;
}
static nokprobe_inline int
read_mem_aligned(unsigned long *dest, unsigned long ea, int nb, struct pt_regs *regs)
{
int err;
if (is_kernel_addr(ea))
return __read_mem_aligned(dest, ea, nb, regs);
if (user_read_access_begin((void __user *)ea, nb)) {
err = __read_mem_aligned(dest, ea, nb, regs);
user_read_access_end();
} else {
err = -EFAULT;
regs->dar = ea;
}
return err; return err;
} }
...@@ -336,10 +354,8 @@ static nokprobe_inline int read_mem_aligned(unsigned long *dest, ...@@ -336,10 +354,8 @@ static nokprobe_inline int read_mem_aligned(unsigned long *dest,
* Copy from userspace to a buffer, using the largest possible * Copy from userspace to a buffer, using the largest possible
* aligned accesses, up to sizeof(long). * aligned accesses, up to sizeof(long).
*/ */
static nokprobe_inline int copy_mem_in(u8 *dest, unsigned long ea, int nb, static __always_inline int __copy_mem_in(u8 *dest, unsigned long ea, int nb, struct pt_regs *regs)
struct pt_regs *regs)
{ {
int err = 0;
int c; int c;
for (; nb > 0; nb -= c) { for (; nb > 0; nb -= c) {
...@@ -348,31 +364,46 @@ static nokprobe_inline int copy_mem_in(u8 *dest, unsigned long ea, int nb, ...@@ -348,31 +364,46 @@ static nokprobe_inline int copy_mem_in(u8 *dest, unsigned long ea, int nb,
c = max_align(nb); c = max_align(nb);
switch (c) { switch (c) {
case 1: case 1:
err = __get_user(*dest, (unsigned char __user *) ea); unsafe_get_user(*dest, (u8 __user *)ea, Efault);
break; break;
case 2: case 2:
err = __get_user(*(u16 *)dest, unsafe_get_user(*(u16 *)dest, (u16 __user *)ea, Efault);
(unsigned short __user *) ea);
break; break;
case 4: case 4:
err = __get_user(*(u32 *)dest, unsafe_get_user(*(u32 *)dest, (u32 __user *)ea, Efault);
(unsigned int __user *) ea);
break; break;
#ifdef __powerpc64__ #ifdef __powerpc64__
case 8: case 8:
err = __get_user(*(unsigned long *)dest, unsafe_get_user(*(u64 *)dest, (u64 __user *)ea, Efault);
(unsigned long __user *) ea);
break; break;
#endif #endif
} }
if (err) {
regs->dar = ea;
return err;
}
dest += c; dest += c;
ea += c; ea += c;
} }
return 0; return 0;
Efault:
regs->dar = ea;
return -EFAULT;
}
static nokprobe_inline int copy_mem_in(u8 *dest, unsigned long ea, int nb, struct pt_regs *regs)
{
int err;
if (is_kernel_addr(ea))
return __copy_mem_in(dest, ea, nb, regs);
if (user_read_access_begin((void __user *)ea, nb)) {
err = __copy_mem_in(dest, ea, nb, regs);
user_read_access_end();
} else {
err = -EFAULT;
regs->dar = ea;
}
return err;
} }
static nokprobe_inline int read_mem_unaligned(unsigned long *dest, static nokprobe_inline int read_mem_unaligned(unsigned long *dest,
...@@ -410,30 +441,48 @@ static int read_mem(unsigned long *dest, unsigned long ea, int nb, ...@@ -410,30 +441,48 @@ static int read_mem(unsigned long *dest, unsigned long ea, int nb,
} }
NOKPROBE_SYMBOL(read_mem); NOKPROBE_SYMBOL(read_mem);
static nokprobe_inline int write_mem_aligned(unsigned long val, static __always_inline int
unsigned long ea, int nb, __write_mem_aligned(unsigned long val, unsigned long ea, int nb, struct pt_regs *regs)
struct pt_regs *regs)
{ {
int err = 0;
switch (nb) { switch (nb) {
case 1: case 1:
err = __put_user(val, (unsigned char __user *) ea); unsafe_put_user(val, (unsigned char __user *)ea, Efault);
break; break;
case 2: case 2:
err = __put_user(val, (unsigned short __user *) ea); unsafe_put_user(val, (unsigned short __user *)ea, Efault);
break; break;
case 4: case 4:
err = __put_user(val, (unsigned int __user *) ea); unsafe_put_user(val, (unsigned int __user *)ea, Efault);
break; break;
#ifdef __powerpc64__ #ifdef __powerpc64__
case 8: case 8:
err = __put_user(val, (unsigned long __user *) ea); unsafe_put_user(val, (unsigned long __user *)ea, Efault);
break; break;
#endif #endif
} }
if (err) return 0;
Efault:
regs->dar = ea; regs->dar = ea;
return -EFAULT;
}
static nokprobe_inline int
write_mem_aligned(unsigned long val, unsigned long ea, int nb, struct pt_regs *regs)
{
int err;
if (is_kernel_addr(ea))
return __write_mem_aligned(val, ea, nb, regs);
if (user_write_access_begin((void __user *)ea, nb)) {
err = __write_mem_aligned(val, ea, nb, regs);
user_write_access_end();
} else {
err = -EFAULT;
regs->dar = ea;
}
return err; return err;
} }
...@@ -441,10 +490,8 @@ static nokprobe_inline int write_mem_aligned(unsigned long val, ...@@ -441,10 +490,8 @@ static nokprobe_inline int write_mem_aligned(unsigned long val,
* Copy from a buffer to userspace, using the largest possible * Copy from a buffer to userspace, using the largest possible
* aligned accesses, up to sizeof(long). * aligned accesses, up to sizeof(long).
*/ */
static nokprobe_inline int copy_mem_out(u8 *dest, unsigned long ea, int nb, static nokprobe_inline int __copy_mem_out(u8 *dest, unsigned long ea, int nb, struct pt_regs *regs)
struct pt_regs *regs)
{ {
int err = 0;
int c; int c;
for (; nb > 0; nb -= c) { for (; nb > 0; nb -= c) {
...@@ -453,31 +500,46 @@ static nokprobe_inline int copy_mem_out(u8 *dest, unsigned long ea, int nb, ...@@ -453,31 +500,46 @@ static nokprobe_inline int copy_mem_out(u8 *dest, unsigned long ea, int nb,
c = max_align(nb); c = max_align(nb);
switch (c) { switch (c) {
case 1: case 1:
err = __put_user(*dest, (unsigned char __user *) ea); unsafe_put_user(*dest, (u8 __user *)ea, Efault);
break; break;
case 2: case 2:
err = __put_user(*(u16 *)dest, unsafe_put_user(*(u16 *)dest, (u16 __user *)ea, Efault);
(unsigned short __user *) ea);
break; break;
case 4: case 4:
err = __put_user(*(u32 *)dest, unsafe_put_user(*(u32 *)dest, (u32 __user *)ea, Efault);
(unsigned int __user *) ea);
break; break;
#ifdef __powerpc64__ #ifdef __powerpc64__
case 8: case 8:
err = __put_user(*(unsigned long *)dest, unsafe_put_user(*(u64 *)dest, (u64 __user *)ea, Efault);
(unsigned long __user *) ea);
break; break;
#endif #endif
} }
if (err) {
regs->dar = ea;
return err;
}
dest += c; dest += c;
ea += c; ea += c;
} }
return 0; return 0;
Efault:
regs->dar = ea;
return -EFAULT;
}
static nokprobe_inline int copy_mem_out(u8 *dest, unsigned long ea, int nb, struct pt_regs *regs)
{
int err;
if (is_kernel_addr(ea))
return __copy_mem_out(dest, ea, nb, regs);
if (user_write_access_begin((void __user *)ea, nb)) {
err = __copy_mem_out(dest, ea, nb, regs);
user_write_access_end();
} else {
err = -EFAULT;
regs->dar = ea;
}
return err;
} }
static nokprobe_inline int write_mem_unaligned(unsigned long val, static nokprobe_inline int write_mem_unaligned(unsigned long val,
...@@ -986,10 +1048,24 @@ static nokprobe_inline int do_vsx_store(struct instruction_op *op, ...@@ -986,10 +1048,24 @@ static nokprobe_inline int do_vsx_store(struct instruction_op *op,
} }
#endif /* CONFIG_VSX */ #endif /* CONFIG_VSX */
static int __emulate_dcbz(unsigned long ea)
{
unsigned long i;
unsigned long size = l1_dcache_bytes();
for (i = 0; i < size; i += sizeof(long))
unsafe_put_user(0, (unsigned long __user *)(ea + i), Efault);
return 0;
Efault:
return -EFAULT;
}
int emulate_dcbz(unsigned long ea, struct pt_regs *regs) int emulate_dcbz(unsigned long ea, struct pt_regs *regs)
{ {
int err; int err;
unsigned long i, size; unsigned long size;
#ifdef __powerpc64__ #ifdef __powerpc64__
size = ppc64_caches.l1d.block_size; size = ppc64_caches.l1d.block_size;
...@@ -1001,14 +1077,21 @@ int emulate_dcbz(unsigned long ea, struct pt_regs *regs) ...@@ -1001,14 +1077,21 @@ int emulate_dcbz(unsigned long ea, struct pt_regs *regs)
ea &= ~(size - 1); ea &= ~(size - 1);
if (!address_ok(regs, ea, size)) if (!address_ok(regs, ea, size))
return -EFAULT; return -EFAULT;
for (i = 0; i < size; i += sizeof(long)) {
err = __put_user(0, (unsigned long __user *) (ea + i)); if (is_kernel_addr(ea)) {
if (err) { err = __emulate_dcbz(ea);
} else if (user_write_access_begin((void __user *)ea, size)) {
err = __emulate_dcbz(ea);
user_write_access_end();
} else {
err = -EFAULT;
}
if (err)
regs->dar = ea; regs->dar = ea;
return err; return err;
}
}
return 0;
} }
NOKPROBE_SYMBOL(emulate_dcbz); NOKPROBE_SYMBOL(emulate_dcbz);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment