]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.6-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 12 Jun 2024 12:45:59 +0000 (14:45 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 12 Jun 2024 12:45:59 +0000 (14:45 +0200)
added patches:
riscv-signal-handle-syscall-restart-before-get_signal.patch

queue-6.6/riscv-signal-handle-syscall-restart-before-get_signal.patch [new file with mode: 0644]
queue-6.6/series

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 (file)
index 0000000..1489f75
--- /dev/null
@@ -0,0 +1,171 @@
+From ce4f78f1b53d3327fbd32764aa333bf05fb68818 Mon Sep 17 00:00:00 2001
+From: Haorong Lu <ancientmodern4@gmail.com>
+Date: Thu, 3 Aug 2023 15:44:54 -0700
+Subject: riscv: signal: handle syscall restart before get_signal
+
+From: Haorong Lu <ancientmodern4@gmail.com>
+
+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 <ancientmodern4@gmail.com>
+Link: https://lore.kernel.org/r/20230803224458.4156006-1-ancientmodern4@gmail.com
+Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
+Cc: Conor Dooley <conor@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ 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.
+        */
index 331f6d95d57253d88216ce22ca8a8c5e011bd896..d9dd81649361c72073aef9015ca54ddc8fc73b82 100644 (file)
@@ -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