| From 24f32be00eef7f223464409a64ab7d39e2e50ea6 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Wed, 31 Mar 2021 14:07:04 +0000 |
| Subject: powerpc/signal32: Fix Oops on sigreturn with unmapped VDSO |
| |
| From: Christophe Leroy <christophe.leroy@csgroup.eu> |
| |
| [ Upstream commit acca57217c688c5bbbd5140974533d81e8757cc9 ] |
| |
| PPC32 encounters a KUAP fault when trying to handle a signal with |
| VDSO unmapped. |
| |
| Kernel attempted to read user page (7fc07ec0) - exploit attempt? (uid: 0) |
| BUG: Unable to handle kernel data access on read at 0x7fc07ec0 |
| Faulting instruction address: 0xc00111d4 |
| Oops: Kernel access of bad area, sig: 11 [#1] |
| BE PAGE_SIZE=16K PREEMPT CMPC885 |
| CPU: 0 PID: 353 Comm: sigreturn_vdso Not tainted 5.12.0-rc4-s3k-dev-01553-gb30c310ea220 #4814 |
| NIP: c00111d4 LR: c0005a28 CTR: 00000000 |
| REGS: cadb3dd0 TRAP: 0300 Not tainted (5.12.0-rc4-s3k-dev-01553-gb30c310ea220) |
| MSR: 00009032 <EE,ME,IR,DR,RI> CR: 48000884 XER: 20000000 |
| DAR: 7fc07ec0 DSISR: 88000000 |
| GPR00: c0007788 cadb3e90 c28d4a40 7fc07ec0 7fc07ed0 000004e0 7fc07ce0 00000000 |
| GPR08: 00000001 00000001 7fc07ec0 00000000 28000282 1001b828 100a0920 00000000 |
| GPR16: 100cac0c 100b0000 105c43a4 105c5685 100d0000 100d0000 100d0000 100b2e9e |
| GPR24: ffffffff 105c43c8 00000000 7fc07ec8 cadb3f40 cadb3ec8 c28d4a40 00000000 |
| NIP [c00111d4] flush_icache_range+0x90/0xb4 |
| LR [c0005a28] handle_signal32+0x1bc/0x1c4 |
| Call Trace: |
| [cadb3e90] [100d0000] 0x100d0000 (unreliable) |
| [cadb3ec0] [c0007788] do_notify_resume+0x260/0x314 |
| [cadb3f20] [c000c764] syscall_exit_prepare+0x120/0x184 |
| [cadb3f30] [c00100b4] ret_from_syscall+0xc/0x28 |
| --- interrupt: c00 at 0xfe807f8 |
| NIP: 0fe807f8 LR: 10001060 CTR: c0139378 |
| REGS: cadb3f40 TRAP: 0c00 Not tainted (5.12.0-rc4-s3k-dev-01553-gb30c310ea220) |
| MSR: 0000d032 <EE,PR,ME,IR,DR,RI> CR: 28000482 XER: 20000000 |
| |
| GPR00: 00000025 7fc081c0 77bb1690 00000000 0000000a 28000482 00000001 0ff03a38 |
| GPR08: 0000d032 00006de5 c28d4a40 00000009 88000482 1001b828 100a0920 00000000 |
| GPR16: 100cac0c 100b0000 105c43a4 105c5685 100d0000 100d0000 100d0000 100b2e9e |
| GPR24: ffffffff 105c43c8 00000000 77ba7628 10002398 10010000 10002124 00024000 |
| NIP [0fe807f8] 0xfe807f8 |
| LR [10001060] 0x10001060 |
| --- interrupt: c00 |
| Instruction dump: |
| 38630010 7c001fac 38630010 4200fff0 7c0004ac 4c00012c 4e800020 7c001fac |
| 2c0a0000 38630010 4082ffcc 4bffffe4 <7c00186c> 2c070000 39430010 4082ff8c |
| ---[ end trace 3973fb72b049cb06 ]--- |
| |
| This is because flush_icache_range() is called on user addresses. |
| |
| The same problem was detected some time ago on PPC64. It was fixed by |
| enabling KUAP in commit 59bee45b9712 ("powerpc/mm: Fix missing KUAP |
| disable in flush_coherent_icache()"). |
| |
| PPC32 doesn't use flush_coherent_icache() and fallbacks on |
| clean_dcache_range() and invalidate_icache_range(). |
| |
| We could fix it similarly by enabling user access in those functions, |
| but this is overkill for just flushing two instructions. |
| |
| The two instructions are 8 bytes aligned, so a single dcbst/icbi is |
| enough to flush them. Do like __patch_instruction() and inline |
| a dcbst followed by an icbi just after the write of the instructions, |
| while user access is still allowed. The isync is not required because |
| rfi will be used to return to user. |
| |
| icbi() is handled as a read so read-write user access is needed. |
| |
| Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu> |
| Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> |
| Link: https://lore.kernel.org/r/bde9154e5351a5ac7bca3d59cdb5a5e8edacbb79.1617199569.git.christophe.leroy@csgroup.eu |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| arch/powerpc/kernel/signal_32.c | 20 ++++++++------------ |
| 1 file changed, 8 insertions(+), 12 deletions(-) |
| |
| diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c |
| index 934cbdf6dd10..30eddc69c9cf 100644 |
| --- a/arch/powerpc/kernel/signal_32.c |
| +++ b/arch/powerpc/kernel/signal_32.c |
| @@ -775,7 +775,7 @@ int handle_rt_signal32(struct ksignal *ksig, sigset_t *oldset, |
| else |
| prepare_save_user_regs(1); |
| |
| - if (!user_write_access_begin(frame, sizeof(*frame))) |
| + if (!user_access_begin(frame, sizeof(*frame))) |
| goto badframe; |
| |
| /* Put the siginfo & fill in most of the ucontext */ |
| @@ -809,17 +809,15 @@ int handle_rt_signal32(struct ksignal *ksig, sigset_t *oldset, |
| unsafe_put_user(PPC_INST_ADDI + __NR_rt_sigreturn, &mctx->mc_pad[0], |
| failed); |
| unsafe_put_user(PPC_INST_SC, &mctx->mc_pad[1], failed); |
| + asm("dcbst %y0; sync; icbi %y0; sync" :: "Z" (mctx->mc_pad[0])); |
| } |
| unsafe_put_sigset_t(&frame->uc.uc_sigmask, oldset, failed); |
| |
| - user_write_access_end(); |
| + user_access_end(); |
| |
| if (copy_siginfo_to_user(&frame->info, &ksig->info)) |
| goto badframe; |
| |
| - if (tramp == (unsigned long)mctx->mc_pad) |
| - flush_icache_range(tramp, tramp + 2 * sizeof(unsigned long)); |
| - |
| regs->link = tramp; |
| |
| #ifdef CONFIG_PPC_FPU_REGS |
| @@ -844,7 +842,7 @@ int handle_rt_signal32(struct ksignal *ksig, sigset_t *oldset, |
| return 0; |
| |
| failed: |
| - user_write_access_end(); |
| + user_access_end(); |
| |
| badframe: |
| signal_fault(tsk, regs, "handle_rt_signal32", frame); |
| @@ -879,7 +877,7 @@ int handle_signal32(struct ksignal *ksig, sigset_t *oldset, |
| else |
| prepare_save_user_regs(1); |
| |
| - if (!user_write_access_begin(frame, sizeof(*frame))) |
| + if (!user_access_begin(frame, sizeof(*frame))) |
| goto badframe; |
| sc = (struct sigcontext __user *) &frame->sctx; |
| |
| @@ -908,11 +906,9 @@ int handle_signal32(struct ksignal *ksig, sigset_t *oldset, |
| /* Set up the sigreturn trampoline: li r0,sigret; sc */ |
| unsafe_put_user(PPC_INST_ADDI + __NR_sigreturn, &mctx->mc_pad[0], failed); |
| unsafe_put_user(PPC_INST_SC, &mctx->mc_pad[1], failed); |
| + asm("dcbst %y0; sync; icbi %y0; sync" :: "Z" (mctx->mc_pad[0])); |
| } |
| - user_write_access_end(); |
| - |
| - if (tramp == (unsigned long)mctx->mc_pad) |
| - flush_icache_range(tramp, tramp + 2 * sizeof(unsigned long)); |
| + user_access_end(); |
| |
| regs->link = tramp; |
| |
| @@ -934,7 +930,7 @@ int handle_signal32(struct ksignal *ksig, sigset_t *oldset, |
| return 0; |
| |
| failed: |
| - user_write_access_end(); |
| + user_access_end(); |
| |
| badframe: |
| signal_fault(tsk, regs, "handle_signal32", frame); |
| -- |
| 2.30.2 |
| |