Re: [PATCH] powerpc: Save AMR/IAMR when switching tasks
From: Samuel Holland <samuel@sholland.org>
Date: 2022-09-21 03:35:46
Also in:
lkml
On 9/19/22 07:37, Michael Ellerman wrote:
Christophe Leroy [off-list ref] writes:quoted
Le 16/09/2022 à 07:05, Samuel Holland a écrit :quoted
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?
It looks like your patch works. Thanks for the correct fix! I replaced my patch with the one below, and enabled CONFIG_PPC_KUAP_DEBUG=y, and I was able to do several kernel builds without any crashes or splats in dmesg. I suppose the other calls to exit_vmx_usercopy() in copyuser_power7.S would not cause a crash, because there is no userspace memory access afterward, but couldn't they still leave KUAP erroneously unlocked? Regards, Samuel
quoted hunk ↗ jump to hunk
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 .Lexitdiff --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())