From: Greg Kroah-Hartman Date: Wed, 12 Jun 2024 12:45:59 +0000 (+0200) Subject: 6.6-stable patches X-Git-Tag: v4.19.316~132 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4286f2d2d44ce9c15b83a8736ab7ac14c07c41d6;p=thirdparty%2Fkernel%2Fstable-queue.git 6.6-stable patches added patches: riscv-signal-handle-syscall-restart-before-get_signal.patch --- diff --git a/queue-6.6/riscv-signal-handle-syscall-restart-before-get_signal.patch b/queue-6.6/riscv-signal-handle-syscall-restart-before-get_signal.patch new file mode 100644 index 00000000000..1489f756bc9 --- /dev/null +++ b/queue-6.6/riscv-signal-handle-syscall-restart-before-get_signal.patch @@ -0,0 +1,171 @@ +From ce4f78f1b53d3327fbd32764aa333bf05fb68818 Mon Sep 17 00:00:00 2001 +From: Haorong Lu +Date: Thu, 3 Aug 2023 15:44:54 -0700 +Subject: riscv: signal: handle syscall restart before get_signal + +From: Haorong Lu + +commit ce4f78f1b53d3327fbd32764aa333bf05fb68818 upstream. + +In the current riscv implementation, blocking syscalls like read() may +not correctly restart after being interrupted by ptrace. This problem +arises when the syscall restart process in arch_do_signal_or_restart() +is bypassed due to changes to the regs->cause register, such as an +ebreak instruction. + +Steps to reproduce: +1. Interrupt the tracee process with PTRACE_SEIZE & PTRACE_INTERRUPT. +2. Backup original registers and instruction at new_pc. +3. Change pc to new_pc, and inject an instruction (like ebreak) to this + address. +4. Resume with PTRACE_CONT and wait for the process to stop again after + executing ebreak. +5. Restore original registers and instructions, and detach from the + tracee process. +6. Now the read() syscall in tracee will return -1 with errno set to + ERESTARTSYS. + +Specifically, during an interrupt, the regs->cause changes from +EXC_SYSCALL to EXC_BREAKPOINT due to the injected ebreak, which is +inaccessible via ptrace so we cannot restore it. This alteration breaks +the syscall restart condition and ends the read() syscall with an +ERESTARTSYS error. According to include/linux/errno.h, it should never +be seen by user programs. X86 can avoid this issue as it checks the +syscall condition using a register (orig_ax) exposed to user space. +Arm64 handles syscall restart before calling get_signal, where it could +be paused and inspected by ptrace/debugger. + +This patch adjusts the riscv implementation to arm64 style, which also +checks syscall using a kernel register (syscallno). It ensures the +syscall restart process is not bypassed when changes to the cause +register occur, providing more consistent behavior across various +architectures. + +For a simplified reproduction program, feel free to visit: +https://github.com/ancientmodern/riscv-ptrace-bug-demo. + +Signed-off-by: Haorong Lu +Link: https://lore.kernel.org/r/20230803224458.4156006-1-ancientmodern4@gmail.com +Signed-off-by: Palmer Dabbelt +Cc: Conor Dooley +Signed-off-by: Greg Kroah-Hartman +--- + arch/riscv/kernel/signal.c | 85 ++++++++++++++++++++++++--------------------- + 1 file changed, 46 insertions(+), 39 deletions(-) + +--- a/arch/riscv/kernel/signal.c ++++ b/arch/riscv/kernel/signal.c +@@ -384,30 +384,6 @@ static void handle_signal(struct ksignal + sigset_t *oldset = sigmask_to_save(); + int ret; + +- /* Are we from a system call? */ +- if (regs->cause == EXC_SYSCALL) { +- /* Avoid additional syscall restarting via ret_from_exception */ +- regs->cause = -1UL; +- /* If so, check system call restarting.. */ +- switch (regs->a0) { +- case -ERESTART_RESTARTBLOCK: +- case -ERESTARTNOHAND: +- regs->a0 = -EINTR; +- break; +- +- case -ERESTARTSYS: +- if (!(ksig->ka.sa.sa_flags & SA_RESTART)) { +- regs->a0 = -EINTR; +- break; +- } +- fallthrough; +- case -ERESTARTNOINTR: +- regs->a0 = regs->orig_a0; +- regs->epc -= 0x4; +- break; +- } +- } +- + rseq_signal_deliver(ksig, regs); + + /* Set up the stack frame */ +@@ -421,36 +397,67 @@ static void handle_signal(struct ksignal + + void arch_do_signal_or_restart(struct pt_regs *regs) + { ++ unsigned long continue_addr = 0, restart_addr = 0; ++ int retval = 0; + struct ksignal ksig; ++ bool syscall = (regs->cause == EXC_SYSCALL); + +- if (get_signal(&ksig)) { +- /* Actually deliver the signal */ +- handle_signal(&ksig, regs); +- return; +- } ++ /* If we were from a system call, check for system call restarting */ ++ if (syscall) { ++ continue_addr = regs->epc; ++ restart_addr = continue_addr - 4; ++ retval = regs->a0; + +- /* Did we come from a system call? */ +- if (regs->cause == EXC_SYSCALL) { + /* Avoid additional syscall restarting via ret_from_exception */ + regs->cause = -1UL; + +- /* Restart the system call - no handlers present */ +- switch (regs->a0) { ++ /* ++ * Prepare for system call restart. We do this here so that a ++ * debugger will see the already changed PC. ++ */ ++ switch (retval) { + case -ERESTARTNOHAND: + case -ERESTARTSYS: + case -ERESTARTNOINTR: +- regs->a0 = regs->orig_a0; +- regs->epc -= 0x4; +- break; + case -ERESTART_RESTARTBLOCK: +- regs->a0 = regs->orig_a0; +- regs->a7 = __NR_restart_syscall; +- regs->epc -= 0x4; ++ regs->a0 = regs->orig_a0; ++ regs->epc = restart_addr; + break; + } + } + + /* ++ * Get the signal to deliver. When running under ptrace, at this point ++ * the debugger may change all of our registers. ++ */ ++ if (get_signal(&ksig)) { ++ /* ++ * Depending on the signal settings, we may need to revert the ++ * decision to restart the system call, but skip this if a ++ * debugger has chosen to restart at a different PC. ++ */ ++ if (regs->epc == restart_addr && ++ (retval == -ERESTARTNOHAND || ++ retval == -ERESTART_RESTARTBLOCK || ++ (retval == -ERESTARTSYS && ++ !(ksig.ka.sa.sa_flags & SA_RESTART)))) { ++ regs->a0 = -EINTR; ++ regs->epc = continue_addr; ++ } ++ ++ /* Actually deliver the signal */ ++ handle_signal(&ksig, regs); ++ return; ++ } ++ ++ /* ++ * Handle restarting a different system call. As above, if a debugger ++ * has chosen to restart at a different PC, ignore the restart. ++ */ ++ if (syscall && regs->epc == restart_addr && retval == -ERESTART_RESTARTBLOCK) ++ regs->a7 = __NR_restart_syscall; ++ ++ /* + * If there is no signal to deliver, we just put the saved + * sigmask back. + */ diff --git a/queue-6.6/series b/queue-6.6/series index 331f6d95d57..d9dd8164936 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -3,3 +3,4 @@ drm-i915-hwmon-get-rid-of-devm.patch mmc-core-do-not-force-a-retune-before-rpmb-switch.patch afs-don-t-cross-.backup-mountpoint-from-backup-volume.patch net-sfp-bus-fix-sfp-mode-detect-from-bitrate.patch +riscv-signal-handle-syscall-restart-before-get_signal.patch