> On 17 Feb 2026, at 6:14 PM, Sayali Patil <[email protected]> wrote:
> 
> On powerpc with PREEMPT_FULL or PREEMPT_LAZY and function tracing enabled,
> KUAP warnings can be triggered from the VMX usercopy path under memory
> stress workloads.
> 
> KUAP requires that no subfunctions are called once userspace access has
> been enabled. The existing VMX copy implementation violates this
> requirement by invoking enter_vmx_usercopy() from the assembly path after
> userspace access has already been enabled. If preemption occurs
> in this window, the AMR state may not be preserved correctly,
> leading to unexpected userspace access state and resulting in
> KUAP warnings.
> 
> Fix this by moving VMX selection and enter_vmx_usercopy()/
> exit_vmx_usercopy() handling into the raw_copy_{to,from,in}_user()
> wrappers in uaccess.h. The new flow is:
> 
>  - Decide whether to use the VMX path based on size and CPU capability
>  - Call enter_vmx_usercopy() before enabling userspace access
>  - Enable userspace access and perform the VMX copy
>  - Disable userspace access
>  - Call exit_vmx_usercopy()
>  - Fall back to the base copy routine if the VMX copy faults
> 
> With this change, the VMX assembly routines no longer perform VMX state
> management or call helper functions; they only implement the
> copy operations.
> The previous feature-section based VMX selection inside
> __copy_tofrom_user_power7() is removed, and a dedicated
> __copy_tofrom_user_power7_vmx() entry point is introduced.
> 
> This ensures correct KUAP ordering, avoids subfunction calls
> while KUAP is unlocked, and eliminates the warnings while preserving
> the VMX fast path.
> 
> Fixes: de78a9c42a79 ("powerpc: Add a framework for Kernel Userspace Access 
> Protection")
> Reported-by: Shrikanth Hegde <[email protected]>
> Closes: 
> https://lore.kernel.org/all/[email protected]/
> Suggested-by: Christophe Leroy (CS GROUP) <[email protected]>
> Co-developed-by: Aboorva Devarajan <[email protected]>
> Signed-off-by: Aboorva Devarajan <[email protected]>
> Signed-off-by: Sayali Patil <[email protected]>
> ---
> arch/powerpc/include/asm/uaccess.h | 67 ++++++++++++++++++++++++++++++
> arch/powerpc/lib/copyuser_64.S     |  1 +
> arch/powerpc/lib/copyuser_power7.S | 45 +++++++-------------
> arch/powerpc/lib/vmx-helper.c      |  2 +
> 4 files changed, 85 insertions(+), 30 deletions(-)
> 
> diff --git a/arch/powerpc/include/asm/uaccess.h 
> b/arch/powerpc/include/asm/uaccess.h
> index 784a00e681fa..52e4a784d148 100644
> --- a/arch/powerpc/include/asm/uaccess.h
> +++ b/arch/powerpc/include/asm/uaccess.h
> @@ -13,6 +13,11 @@
> #define TASK_SIZE_MAX TASK_SIZE_USER64
> #endif
> 
> +#ifdef CONFIG_ALTIVEC
> +/* Threshold above which VMX copy path is used */
> +#define VMX_COPY_THRESHOLD 3328
> +#endif
> +
> #include <asm-generic/access_ok.h>
> 
> /*
> @@ -323,12 +328,42 @@ do { \
> extern unsigned long __copy_tofrom_user(void __user *to,
> const void __user *from, unsigned long size);
> 
> +extern unsigned long __copy_tofrom_user_base(void __user *to,
> + const void __user *from, unsigned long size);
> +
> +#ifdef CONFIG_ALTIVEC
> +extern unsigned long __copy_tofrom_user_power7_vmx(void __user *to,
> + const void __user *from, unsigned long size);
> +
> +static inline bool will_use_vmx(unsigned long n)
> +{
> + return cpu_has_feature(CPU_FTR_VMX_COPY) &&
> + n > VMX_COPY_THRESHOLD;
> +}
> +#endif
> +
> #ifdef __powerpc64__
> static inline unsigned long
> raw_copy_in_user(void __user *to, const void __user *from, unsigned long n)
> {
> unsigned long ret;
> 
> +#ifdef CONFIG_ALTIVEC
> + if (will_use_vmx(n) && enter_vmx_usercopy()) {
> + allow_read_write_user(to, from, n);
> + ret = __copy_tofrom_user_power7_vmx(to, from, n);
> + prevent_read_write_user(to, from, n);
> + exit_vmx_usercopy();
> + if (unlikely(ret)) {
> + allow_read_write_user(to, from, n);
> + ret = __copy_tofrom_user_base(to, from, n);
> + prevent_read_write_user(to, from, n);
> + }
> +
> + return ret;
> + }
> +#endif
> +
> allow_read_write_user(to, from, n);
> ret = __copy_tofrom_user(to, from, n);
> prevent_read_write_user(to, from, n);
> @@ -341,6 +376,22 @@ static inline unsigned long raw_copy_from_user(void *to,
> {
> unsigned long ret;
> 
> +#ifdef CONFIG_ALTIVEC
> + if (will_use_vmx(n) && enter_vmx_usercopy()) {
> + allow_read_from_user(from, n);
> + ret = __copy_tofrom_user_power7_vmx((__force void __user *)to, from, n);
> + prevent_read_from_user(from, n);
> + exit_vmx_usercopy();
> + if (unlikely(ret)) {
> + allow_read_from_user(from, n);
> + ret = __copy_tofrom_user_base((__force void __user *)to, from, n);
> + prevent_read_from_user(from, n);
> + }
> +
> + return ret;
> + }
> +#endif
> +
> allow_read_from_user(from, n);
> ret = __copy_tofrom_user((__force void __user *)to, from, n);
> prevent_read_from_user(from, n);
> @@ -352,6 +403,22 @@ raw_copy_to_user(void __user *to, const void *from, 
> unsigned long n)
> {
> unsigned long ret;
> 
> +#ifdef CONFIG_ALTIVEC
> + if (will_use_vmx(n) && enter_vmx_usercopy()) {
> + allow_write_to_user(to, n);
> + ret = __copy_tofrom_user_power7_vmx(to, (__force const void __user *)from, 
> n);
> + prevent_write_to_user(to, n);
> + exit_vmx_usercopy();
> + if (unlikely(ret)) {
> + allow_write_to_user(to, n);
> + ret = __copy_tofrom_user_base(to, (__force const void __user *)from, n);
> + prevent_write_to_user(to, n);
> + }
> +
> + return ret;
> + }
> +#endif
> +
> allow_write_to_user(to, n);
> ret = __copy_tofrom_user(to, (__force const void __user *)from, n);
> prevent_write_to_user(to, n);
> diff --git a/arch/powerpc/lib/copyuser_64.S b/arch/powerpc/lib/copyuser_64.S
> index 9af969d2cc0c..25a99108caff 100644
> --- a/arch/powerpc/lib/copyuser_64.S
> +++ b/arch/powerpc/lib/copyuser_64.S
> @@ -562,3 +562,4 @@ exc; std r10,32(3)
> li r5,4096
> b .Ldst_aligned
> EXPORT_SYMBOL(__copy_tofrom_user)
> +EXPORT_SYMBOL(__copy_tofrom_user_base)
> diff --git a/arch/powerpc/lib/copyuser_power7.S 
> b/arch/powerpc/lib/copyuser_power7.S
> index 8474c682a178..17dbcfbae25f 100644
> --- a/arch/powerpc/lib/copyuser_power7.S
> +++ b/arch/powerpc/lib/copyuser_power7.S
> @@ -5,13 +5,9 @@
>  *
>  * Author: Anton Blanchard <[email protected]>
>  */
> +#include <linux/export.h>
> #include <asm/ppc_asm.h>
> 
> -#ifndef SELFTEST_CASE
> -/* 0 == don't use VMX, 1 == use VMX */
> -#define SELFTEST_CASE 0
> -#endif
> -
> #ifdef __BIG_ENDIAN__
> #define LVS(VRT,RA,RB) lvsl VRT,RA,RB
> #define VPERM(VRT,VRA,VRB,VRC) vperm VRT,VRA,VRB,VRC
> @@ -47,10 +43,14 @@
> ld r15,STK_REG(R15)(r1)
> ld r14,STK_REG(R14)(r1)
> .Ldo_err3:
> - bl CFUNC(exit_vmx_usercopy)
> + ld      r6,STK_REG(R31)(r1) /* original destination pointer */
> + ld      r5,STK_REG(R29)(r1) /* original number of bytes */
> + subf    r7,r6,r3 /* #bytes copied */
> + subf    r3,r7,r5 /* #bytes not copied in r3 */
> ld r0,STACKFRAMESIZE+16(r1)
> mtlr r0
> - b .Lexit
> + addi    r1,r1,STACKFRAMESIZE
> + blr
> #endif /* CONFIG_ALTIVEC */
> 
> .Ldo_err2:
> @@ -74,7 +74,6 @@
> 
> _GLOBAL(__copy_tofrom_user_power7)
> cmpldi r5,16
> - cmpldi cr1,r5,3328
> 
> std r3,-STACKFRAMESIZE+STK_REG(R31)(r1)
> std r4,-STACKFRAMESIZE+STK_REG(R30)(r1)
> @@ -82,12 +81,6 @@ _GLOBAL(__copy_tofrom_user_power7)
> 
> blt .Lshort_copy
> 
> -#ifdef CONFIG_ALTIVEC
> -test_feature = SELFTEST_CASE
> -BEGIN_FTR_SECTION
> - bgt cr1,.Lvmx_copy
> -END_FTR_SECTION_IFSET(CPU_FTR_ALTIVEC)
> -#endif
> 
> .Lnonvmx_copy:
> /* Get the source 8B aligned */
> @@ -263,23 +256,14 @@ err1; stb r0,0(r3)
> 15: li r3,0
> blr
> 
> -.Lunwind_stack_nonvmx_copy:
> - addi r1,r1,STACKFRAMESIZE
> - b .Lnonvmx_copy
> -
> -.Lvmx_copy:
> #ifdef CONFIG_ALTIVEC
> +_GLOBAL(__copy_tofrom_user_power7_vmx)
> mflr r0
> std r0,16(r1)
> stdu r1,-STACKFRAMESIZE(r1)
> - bl CFUNC(enter_vmx_usercopy)
> - cmpwi cr1,r3,0
> - ld r0,STACKFRAMESIZE+16(r1)
> - ld r3,STK_REG(R31)(r1)
> - ld r4,STK_REG(R30)(r1)
> - ld r5,STK_REG(R29)(r1)
> - mtlr r0
> 
> + std     r3,STK_REG(R31)(r1)
> + std     r5,STK_REG(R29)(r1)
> /*
> * We prefetch both the source and destination using enhanced touch
> * instructions. We use a stream ID of 0 for the load side and
> @@ -300,8 +284,6 @@ err1; stb r0,0(r3)
> 
> DCBT_SETUP_STREAMS(r6, r7, r9, r10, r8)
> 
> - beq cr1,.Lunwind_stack_nonvmx_copy
> -
> /*
> * If source and destination are not relatively aligned we use a
> * slower permute loop.
> @@ -478,7 +460,8 @@ err3; lbz r0,0(r4)
> err3; stb r0,0(r3)
> 
> 15: addi r1,r1,STACKFRAMESIZE
> - b CFUNC(exit_vmx_usercopy) /* tail call optimise */
> + li r3,0
> + blr
> 
> .Lvmx_unaligned_copy:
> /* Get the destination 16B aligned */
> @@ -681,5 +664,7 @@ err3; lbz r0,0(r4)
> err3; stb r0,0(r3)
> 
> 15: addi r1,r1,STACKFRAMESIZE
> - b CFUNC(exit_vmx_usercopy) /* tail call optimise */
> + li r3,0
> + blr
> +EXPORT_SYMBOL(__copy_tofrom_user_power7_vmx)
> #endif /* CONFIG_ALTIVEC */
> diff --git a/arch/powerpc/lib/vmx-helper.c b/arch/powerpc/lib/vmx-helper.c
> index 54340912398f..554b248002b4 100644
> --- a/arch/powerpc/lib/vmx-helper.c
> +++ b/arch/powerpc/lib/vmx-helper.c
> @@ -27,6 +27,7 @@ int enter_vmx_usercopy(void)
> 
> return 1;
> }
> +EXPORT_SYMBOL(enter_vmx_usercopy);
> 
> /*
>  * This function must return 0 because we tail call optimise when calling
> @@ -49,6 +50,7 @@ int exit_vmx_usercopy(void)
> set_dec(1);
> return 0;
> }
> +EXPORT_SYMBOL(exit_vmx_usercopy);
> 
> int enter_vmx_ops(void)
> {
> -- 
> 2.52.0
> 
> 

Tested-by: Venkat Rao Bagalkote <[email protected]>

Confirmed fix using the repro steps in the Closes: tag.


With this patch:

No Warnings seen.


WithOut this patch:

[  647.366772] Kernel attempted to write user page (3fff86135000) - exploit 
attempt? (uid: 0)
[  647.366818] ------------[ cut here ]------------
[  647.366844] Bug: Write fault blocked by KUAP!
[  647.366864] WARNING: arch/powerpc/mm/fault.c:231 at 
bad_kernel_fault.constprop.0+0x1a8/0x2c8, CPU#15: stress-ng-vm-rw/5083
[  647.366912] Modules linked in: nft_fib_inet nft_fib_ipv4 nft_fib_ipv6 
nft_fib nft_reject_inet nf_reject_ipv4 nf_reject_ipv6 nft_reject nft_ct 
nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 bonding tls 
rfkill ip_set nf_tables nfnetlink sunrpc pseries_rng vmx_crypto fuse ext4 crc16 
mbcache jbd2 sd_mod sg ibmvscsi ibmveth scsi_transport_srp pseries_wdt
[  647.367436] CPU: 15 UID: 0 PID: 5083 Comm: stress-ng-vm-rw Kdump: loaded 
Tainted: G        W           6.19.0+ #2 PREEMPT(lazy) 
[  647.367470] Tainted: [W]=WARN
[  647.367493] Hardware name: IBM,9080-HEX Power11 (architected) 0x820200 
0xf000007 of:IBM,FW1110.01 (NH1110_069) hv:phyp pSeries
[  647.367523] NIP:  c00000000008cf9c LR: c00000000008cf98 CTR: c0000000002293ac
[  647.367552] REGS: c0000000e0753160 TRAP: 0700   Tainted: G        W          
  (6.19.0+)
[  647.367577] MSR:  8000000000029033 <SF,EE,ME,IR,DR,RI,LE>  CR: 28024824  
XER: 20040005
[  647.367732] CFAR: c000000000166418 IRQMASK: 3 
[  647.367732] GPR00: c00000000008cf98 c0000000e0753400 c00000000189c100 
0000000000000021 
[  647.367732] GPR04: 0000000000000000 c0000000e0753200 c0000000e07531f8 
0000000efa437000 
[  647.367732] GPR08: 0000000000000027 0000000000000000 c000000078ca8000 
0000000000004000 
[  647.367732] GPR12: c00000000033ca90 c000000effff4300 0000000000000000 
0000000000000000 
[  647.367732] GPR16: 0000000000000000 0000000000000000 0000000000000000 
0000000000000000 
[  647.367732] GPR20: 0000000000000000 0000000000000000 0000000000000000 
0000000002000000 
[  647.367732] GPR24: 0000000000000001 0000000000000001 0000000000000000 
c000000078c9d800 
[  647.367732] GPR28: 00003fff86135000 0000000000000300 00003fff86135000 
c0000000e07535a0 
[  647.368347] NIP [c00000000008cf9c] bad_kernel_fault.constprop.0+0x1a8/0x2c8
[  647.368375] LR [c00000000008cf98] bad_kernel_fault.constprop.0+0x1a4/0x2c8
[  647.368405] Call Trace:
[  647.368426] [c0000000e0753400] [c00000000008cf98] 
bad_kernel_fault.constprop.0+0x1a4/0x2c8 (unreliable)
[  647.368482] [c0000000e0753480] [c00000000008dc34] 
___do_page_fault+0x688/0xa54
[  647.368526] [c0000000e0753540] [c00000000008e2ac] do_page_fault+0x30/0x70
[  647.368564] [c0000000e0753570] [c000000000008be0] 
data_access_common_virt+0x210/0x220
[  647.368608] ---- interrupt: 300 at __copy_tofrom_user_base+0x9c/0x5a4
[  647.368638] NIP:  c0000000000be2a8 LR: c0000000009bcba8 CTR: 0000000000000080
[  647.368664] REGS: c0000000e07535a0 TRAP: 0300   Tainted: G        W          
  (6.19.0+)
[  647.368692] MSR:  800000000280b033 <SF,VEC,VSX,EE,FP,ME,IR,DR,RI,LE>  CR: 
24022824  XER: 20040000
[  647.368892] CFAR: c0000000000be274 DAR: 00003fff86135000 DSISR: 0a000000 
IRQMASK: 0 
[  647.368892] GPR00: 0000000000000000 c0000000e0753840 c00000000189c100 
00003fff86134ff0 
[  647.368892] GPR04: c00000009fee7010 0000000000001000 0000000000000000 
0000000000000000 
[  647.368892] GPR08: 0000000000000000 0000000000000000 0000000000000000 
0000000000000000 
[  647.368892] GPR12: c00000000033ca90 c000000effff4300 c0000001122bc9a8 
c0000000e0753b50 
[  647.368892] GPR16: 00000000002cb000 0000000000000000 0000000000000000 
0000000000000400 
[  647.368892] GPR20: 0000000000000001 0000000000000000 c000000078c9da10 
00003fff85000000 
[  647.368892] GPR24: 0000000000400000 c00000009fee7000 c000000002de75f0 
c0000000e0753b50 
[  647.368892] GPR28: c00c0000027fb9c0 c0000000e0753b50 0000000000000000 
0000000000001000 
[  647.369529] NIP [c0000000000be2a8] __copy_tofrom_user_base+0x9c/0x5a4
[  647.369560] LR [c0000000009bcba8] _copy_to_iter+0x128/0xaa4
[  647.369593] ---- interrupt: 300
[  647.369620] [c0000000e0753910] [c0000000009bd5fc] 
copy_page_to_iter+0xd8/0x1b8
[  647.369661] [c0000000e0753960] [c0000000005b340c] 
process_vm_rw_single_vec.constprop.0+0x1cc/0x3b0
[  647.369703] [c0000000e0753a20] [c0000000005b375c] 
process_vm_rw_core.constprop.0+0x16c/0x310
[  647.369746] [c0000000e0753b20] [c0000000005b3a28] process_vm_rw+0x128/0x184
[  647.369790] [c0000000e0753cb0] [c00000000003186c] 
system_call_exception+0x14c/0x340
[  647.369834] [c0000000e0753e50] [c00000000000d05c] 
system_call_vectored_common+0x15c/0x2ec
[  647.369877] ---- interrupt: 3000 at 0x3fff86d4dc1c
[  647.369906] NIP:  00003fff86d4dc1c LR: 00003fff86d4dc1c CTR: 0000000000000000
[  647.369932] REGS: c0000000e0753e80 TRAP: 3000   Tainted: G        W          
  (6.19.0+)
[  647.369958] MSR:  800000000280f033 <SF,VEC,VSX,EE,PR,FP,ME,IR,DR,RI,LE>  CR: 
44022402  XER: 00000000
[  647.370155] IRQMASK: 0 
[  647.370155] GPR00: 000000000000015f 00003fffcffb0040 000000012ddd7800 
00000000000013f2 
[  647.370155] GPR04: 00003fffcffb00c0 0000000000000001 00003fffcffb0100 
0000000000000001 
[  647.370155] GPR08: 0000000000000000 0000000000000000 0000000000000000 
0000000000000000 
[  647.370155] GPR12: 0000000000000000 00003fff871fb760 0000000000000000 
0000000000000000 
[  647.370155] GPR16: 0000000000000001 00000000ffffffff 0000000000000019 
0000000001000000 
[  647.370155] GPR20: 00003fffcffb00a0 00003fffcffb00b0 000000012ddd0008 
00003fff86f39e28 
[  647.370155] GPR24: 00003fff85800000 00003fffcffb0100 0000000040000000 
00003fff86800000 
[  647.370155] GPR28: 00003fff85800000 0000000000000000 00003fffcffb0248 
00003fffcffb00c0 
[  647.370710] NIP [00003fff86d4dc1c] 0x3fff86d4dc1c
[  647.370741] LR [00003fff86d4dc1c] 0x3fff86d4dc1c
[  647.370767] ---- interrupt: 3000
[  647.370793] Code: e87f0100 48115ebd 60000000 2c230000 4182ff34 40920120 
3c82ffd9 38844608 3c62ffd9 386346a0 480d93c1 60000000 <0fe00000> e8010090 
ebe10078 38210080 
[  647.371060] ---[ end trace 0000000000000000 ]—


Regards,
Venkat.


Reply via email to