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:
Kernel attempted to write user page (3fff7ab68190) - exploit attempt? (uid: 65536) ------------[ cut here ]------------ Bug: Write fault blocked by KUAP! WARNING: CPU: 56 PID: 4939 at arch/powerpc/mm/fault.c:228 ___do_page_fault+0x7b4/0xaa0 CPU: 56 PID: 4939 Comm: git Tainted: G W 5.19.8-00005-gba424747260d #1 NIP: c0000000000555e4 LR: c0000000000555e0 CTR: c00000000079d9d0 REGS: c00000008f507370 TRAP: 0700 Tainted: G W (5.19.8-00005-gba424747260d) MSR: 9000000000021033 <SF,HV,ME,IR,DR,RI,LE> CR: 28042222 XER: 20040000 CFAR: c000000000123780 IRQMASK: 3 NIP [c0000000000555e4] ___do_page_fault+0x7b4/0xaa0 LR [c0000000000555e0] ___do_page_fault+0x7b0/0xaa0 Call Trace: [c00000008f507610] [c0000000000555e0] ___do_page_fault+0x7b0/0xaa0 (unreliable) [c00000008f5076c0] [c000000000055938] do_page_fault+0x68/0x130 [c00000008f5076f0] [c000000000008914] data_access_common_virt+0x194/0x1f0 --- interrupt: 300 at __copy_tofrom_user_base+0x9c/0x5a4 NIP: c00000000007b1a8 LR: c00000000073f4d4 CTR: 0000000000000080 REGS: c00000008f507760 TRAP: 0300 Tainted: G W (5.19.8-00005-gba424747260d) MSR: 900000000280b033 <SF,HV,VEC,VSX,EE,FP,ME,IR,DR,RI,LE> CR: 24002220 XER: 20040000 CFAR: c00000000007b174 DAR: 00003fff7ab68190 DSISR: 0a000000 IRQMASK: 0 NIP [c00000000007b1a8] __copy_tofrom_user_base+0x9c/0x5a4 LR [c00000000073f4d4] copyout+0x74/0x150 --- interrupt: 300 [c00000008f507a30] [c0000000007430cc] copy_page_to_iter+0x12c/0x4b0 [c00000008f507ab0] [c0000000002c7c20] filemap_read+0x200/0x460 [c00000008f507bf0] [c0000000005f96f4] xfs_file_buffered_read+0x104/0x170 [c00000008f507c30] [c0000000005f9800] xfs_file_read_iter+0xa0/0x150 [c00000008f507c70] [c0000000003bddc8] new_sync_read+0x108/0x180 [c00000008f507d10] [c0000000003c06b0] vfs_read+0x1d0/0x240 [c00000008f507d60] [c0000000003c0ba4] ksys_read+0x84/0x140 [c00000008f507db0] [c00000000002a3fc] system_call_exception+0x15c/0x300 [c00000008f507e10] [c00000000000c63c] system_call_common+0xec/0x250 --- interrupt: c00 at 0x3fff83aa7238 NIP: 00003fff83aa7238 LR: 00003fff83a923b8 CTR: 0000000000000000 REGS: c00000008f507e80 TRAP: 0c00 Tainted: G W (5.19.8-00005-gba424747260d) MSR: 900000000280f033 <SF,HV,VEC,VSX,EE,PR,FP,ME,IR,DR,RI,LE> CR: 80002482 XER: 00000000 IRQMASK: 0 NIP [00003fff83aa7238] 0x3fff83aa7238 LR [00003fff83a923b8] 0x3fff83a923b8 --- interrupt: c00 Instruction dump: e87f0100 48101021 60000000 2c230000 4182fee8 408e0128 3c82ff80 3884e978 3c62ff80 3863ea78 480ce13d 60000000 <0fe00000> fb010070 fb810090 e80100c0 ---[ end trace 0000000000000000 ]--- Fix this by saving and restoring the kernel-side AMR/IAMR values when switching tasks. Fixes: 890274c2dc4c ("powerpc/64s: Implement KUAP for Radix MMU") Signed-off-by: Samuel Holland <sam...@sholland.org> --- I have no idea if this is the right change to make, and it could be optimized, but my system has been stable with this patch for 5 days now. Without the patch, I hit the bug every few minutes when my load average is <1, and I hit it immediately if I try to do a parallel kernel build. Because of the instability (file I/O randomly raises SIGBUS), I don't think anyone would run a system in this configuration, so I don't think this bug is exploitable. arch/powerpc/kernel/process.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index 0fbda89cd1bb..69b189d63124 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -1150,6 +1150,12 @@ static inline void save_sprs(struct thread_struct *t) */ t->tar = mfspr(SPRN_TAR); } + if (t->regs) { + if (mmu_has_feature(MMU_FTR_BOOK3S_KUAP)) + t->regs->amr = mfspr(SPRN_AMR); + if (mmu_has_feature(MMU_FTR_BOOK3S_KUEP)) + t->regs->iamr = mfspr(SPRN_IAMR); + } #endif } @@ -1228,6 +1234,13 @@ static inline void restore_sprs(struct thread_struct *old_thread, if (cpu_has_feature(CPU_FTR_P9_TIDR) && old_thread->tidr != new_thread->tidr) mtspr(SPRN_TIDR, new_thread->tidr); + if (new_thread->regs) { + if (mmu_has_feature(MMU_FTR_BOOK3S_KUAP)) + mtspr(SPRN_AMR, new_thread->regs->amr); + if (mmu_has_feature(MMU_FTR_BOOK3S_KUEP)) + mtspr(SPRN_IAMR, new_thread->regs->iamr); + isync(); + } #endif } -- 2.35.1