[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <87h713leu8.fsf@mpe.ellerman.id.au>
Date: Mon, 19 Sep 2022 22:37:51 +1000
From: Michael Ellerman <mpe@...erman.id.au>
To: Christophe Leroy <christophe.leroy@...roup.eu>,
Samuel Holland <samuel@...lland.org>,
Nicholas Piggin <npiggin@...il.com>
Cc: "Eric W. Biederman" <ebiederm@...ssion.com>,
Kees Cook <keescook@...omium.org>,
Russell Currey <ruscur@...sell.cc>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"linuxppc-dev@...ts.ozlabs.org" <linuxppc-dev@...ts.ozlabs.org>
Subject: Re: [PATCH] powerpc: Save AMR/IAMR when switching tasks
Christophe Leroy <christophe.leroy@...roup.eu> writes:
> Le 16/09/2022 à 07:05, Samuel Holland a écrit :
>> With CONFIG_PREEMPT=y (involuntary preemption enabled), it is possible
>> to switch away from a task inside copy_{from,to}_user. This left the CPU
>> with userspace access enabled until after the next IRQ or privilege
>> level switch, when AMR/IAMR got reset to AMR_KU[AE]P_BLOCKED. Then, when
>> switching back to the original task, the userspace access would fault:
>
> This is not supposed to happen. You never switch away from a task
> magically. Task switch will always happen in an interrupt, that means
> copy_{from,to}_user() get interrupted.
Unfortunately this isn't true when CONFIG_PREEMPT=y.
We can switch away without an interrupt via:
__copy_tofrom_user()
-> __copy_tofrom_user_power7()
-> exit_vmx_usercopy()
-> preempt_enable()
-> __preempt_schedule()
-> preempt_schedule()
-> preempt_schedule_common()
-> __schedule()
I do some boot tests with CONFIG_PREEMPT=y, but I realise now those are
all on Power8, which is a bit of an oversight on my part.
And clearly no one else tests it, until now :)
I think the root of our problem is that our KUAP lock/unlock is at too
high a level, ie. we do it in C around the low-level copy to/from.
eg:
static inline unsigned long
raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
unsigned long ret;
allow_write_to_user(to, n);
ret = __copy_tofrom_user(to, (__force const void __user *)from, n);
prevent_write_to_user(to, n);
return ret;
}
There's a reason we did that, which is that we have various different
KUAP methods on different platforms, not a simple instruction like other
arches.
But that means we have that exit_vmx_usercopy() being called deep in the
guts of __copy_tofrom_user(), with KUAP disabled, and then we call into
the preempt machinery and eventually schedule.
I don't see an easy way to fix that "properly", it would be a big change
to all platforms to push the KUAP save/restore down into the low level
asm code.
But I think the patch below does fix it, although it abuses things a
little. Namely it only works because the 64s KUAP code can handle a
double call to prevent, and doesn't need the addresses or size for the
allow.
Still I think it might be our best option for an easy fix.
Samuel, can you try this on your system and check it works for you?
cheers
diff --git a/arch/powerpc/include/asm/processor.h b/arch/powerpc/include/asm/processor.h
index 97a77b37daa3..c50080c6a136 100644
--- a/arch/powerpc/include/asm/processor.h
+++ b/arch/powerpc/include/asm/processor.h
@@ -432,6 +432,7 @@ int speround_handler(struct pt_regs *regs);
/* VMX copying */
int enter_vmx_usercopy(void);
int exit_vmx_usercopy(void);
+void exit_vmx_usercopy_continue(void);
int enter_vmx_ops(void);
void *exit_vmx_ops(void *dest);
diff --git a/arch/powerpc/lib/copyuser_power7.S b/arch/powerpc/lib/copyuser_power7.S
index 28f0be523c06..77804860383c 100644
--- a/arch/powerpc/lib/copyuser_power7.S
+++ b/arch/powerpc/lib/copyuser_power7.S
@@ -47,7 +47,7 @@
ld r15,STK_REG(R15)(r1)
ld r14,STK_REG(R14)(r1)
.Ldo_err3:
- bl exit_vmx_usercopy
+ bl exit_vmx_usercopy_continue
ld r0,STACKFRAMESIZE+16(r1)
mtlr r0
b .Lexit
diff --git a/arch/powerpc/lib/vmx-helper.c b/arch/powerpc/lib/vmx-helper.c
index f76a50291fd7..78a18b8384ff 100644
--- a/arch/powerpc/lib/vmx-helper.c
+++ b/arch/powerpc/lib/vmx-helper.c
@@ -8,6 +8,7 @@
*/
#include <linux/uaccess.h>
#include <linux/hardirq.h>
+#include <asm/kup.h>
#include <asm/switch_to.h>
int enter_vmx_usercopy(void)
@@ -34,12 +35,19 @@ int enter_vmx_usercopy(void)
*/
int exit_vmx_usercopy(void)
{
+ prevent_user_access(KUAP_READ_WRITE);
disable_kernel_altivec();
pagefault_enable();
preempt_enable();
return 0;
}
+void exit_vmx_usercopy_continue(void)
+{
+ exit_vmx_usercopy();
+ allow_read_write_user(NULL, NULL, 0);
+}
+
int enter_vmx_ops(void)
{
if (in_interrupt())
Powered by blists - more mailing lists