[S390] signal race with restarting system calls

For a ERESTARTNOHAND/ERESTARTSYS/ERESTARTNOINTR restarting system call
do_signal will prepare the restart of the system call with a rewind of
the PSW before calling get_signal_to_deliver (where the debugger might
take control). For A ERESTART_RESTARTBLOCK restarting system call
do_signal will set -EINTR as return code.
There are two issues with this approach:
1) strace never sees ERESTARTNOHAND, ERESTARTSYS, ERESTARTNOINTR or
   ERESTART_RESTARTBLOCK as the rewinding already took place or the
   return code has been changed to -EINTR
2) if get_signal_to_deliver does not return with a signal to deliver
   the restart via the repeat of the svc instruction is left in place.
   This opens a race if another signal is made pending before the
   system call instruction can be reexecuted. The original system call
   will be restarted even if the second signal would have ended the
   system call with -EINTR.

These two issues can be solved by dropping the early rewind of the
system call before get_signal_to_deliver has been called and by using
the TIF_RESTART_SVC magic to do the restart if no signal has to be
delivered. The only situation where the system call restart via the
repeat of the svc instruction is appropriate is when a SA_RESTART
signal is delivered to user space.

Unfortunately this breaks inferior calls by the debugger again. The
system call number and the length of the system call instruction is
lost over the inferior call and user space will see ERESTARTNOHAND/
ERESTARTSYS/ERESTARTNOINTR/ERESTART_RESTARTBLOCK. To correct this a
new ptrace interface is added to save/restore the system call number
and system call instruction length.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
Martin Schwidefsky
2011-10-30 15:16:47 +01:00
parent 3ee49c8f12
commit 20b40a794b
10 changed files with 142 additions and 91 deletions

View File

@@ -30,6 +30,7 @@
#include <asm/ucontext.h>
#include <asm/uaccess.h>
#include <asm/lowcore.h>
#include <asm/compat.h>
#include "entry.h"
#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP)))
@@ -156,7 +157,7 @@ static int restore_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
current->thread.fp_regs.fpc &= FPC_VALID_MASK;
restore_fp_regs(&current->thread.fp_regs);
regs->svcnr = 0; /* disable syscall checks */
regs->svc_code = 0; /* disable syscall checks */
return 0;
}
@@ -401,7 +402,6 @@ static int handle_signal(unsigned long sig, struct k_sigaction *ka,
*/
void do_signal(struct pt_regs *regs)
{
unsigned long retval = 0, continue_addr = 0, restart_addr = 0;
siginfo_t info;
int signr;
struct k_sigaction ka;
@@ -421,54 +421,43 @@ void do_signal(struct pt_regs *regs)
else
oldset = &current->blocked;
/* Are we from a system call? */
if (regs->svcnr) {
continue_addr = regs->psw.addr;
restart_addr = continue_addr - regs->ilc;
retval = regs->gprs[2];
/* Prepare for system call restart. We do this here so that a
debugger will see the already changed PSW. */
switch (retval) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->gprs[2] = regs->orig_gpr2;
regs->psw.addr = restart_addr;
break;
case -ERESTART_RESTARTBLOCK:
regs->gprs[2] = -EINTR;
}
regs->svcnr = 0; /* Don't deal with this again. */
}
/* Get signal to deliver. When running under ptrace, at this point
the debugger may change all our registers ... */
/*
* Get signal to deliver. When running under ptrace, at this point
* the debugger may change all our registers, including the system
* call information.
*/
current_thread_info()->system_call = regs->svc_code;
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
/* Depending on the signal settings we may need to revert the
decision to restart the system call. */
if (signr > 0 && regs->psw.addr == restart_addr) {
if (retval == -ERESTARTNOHAND
|| (retval == -ERESTARTSYS
&& !(current->sighand->action[signr-1].sa.sa_flags
& SA_RESTART))) {
regs->gprs[2] = -EINTR;
regs->psw.addr = continue_addr;
}
}
regs->svc_code = current_thread_info()->system_call;
if (signr > 0) {
/* Whee! Actually deliver the signal. */
int ret;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
ret = handle_signal32(signr, &ka, &info, oldset, regs);
}
else
#endif
ret = handle_signal(signr, &ka, &info, oldset, regs);
if (!ret) {
if (regs->svc_code > 0) {
/* Check for system call restarting. */
switch (regs->gprs[2]) {
case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->gprs[2] = -EINTR;
break;
case -ERESTARTSYS:
if (!(ka.sa.sa_flags & SA_RESTART)) {
regs->gprs[2] = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->gprs[2] = regs->orig_gpr2;
regs->psw.addr = regs->psw.addr -
(regs->svc_code >> 16);
break;
}
/* No longer in a system call */
regs->svc_code = 0;
}
if ((is_compat_task() ?
handle_signal32(signr, &ka, &info, oldset, regs) :
handle_signal(signr, &ka, &info, oldset, regs)) == 0) {
/*
* A signal was successfully delivered; the saved
* sigmask will have been stored in the signal frame,
@@ -482,11 +471,28 @@ void do_signal(struct pt_regs *regs)
* Let tracing know that we've done the handler setup.
*/
tracehook_signal_handler(signr, &info, &ka, regs,
test_thread_flag(TIF_SINGLE_STEP));
test_thread_flag(TIF_SINGLE_STEP));
}
return;
}
/* No handlers present - check for system call restart */
if (regs->svc_code > 0) {
switch (regs->gprs[2]) {
case -ERESTART_RESTARTBLOCK:
/* Restart with sys_restart_syscall */
regs->svc_code = __NR_restart_syscall;
/* fallthrough */
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
/* Restart system call with magic TIF bit. */
regs->gprs[2] = regs->orig_gpr2;
set_thread_flag(TIF_RESTART_SVC);
break;
}
}
/*
* If there's no signal to deliver, we just put the saved sigmask back.
*/
@@ -494,13 +500,6 @@ void do_signal(struct pt_regs *regs)
clear_thread_flag(TIF_RESTORE_SIGMASK);
sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
}
/* Restart a different system call. */
if (retval == -ERESTART_RESTARTBLOCK
&& regs->psw.addr == continue_addr) {
regs->gprs[2] = __NR_restart_syscall;
set_thread_flag(TIF_RESTART_SVC);
}
}
void do_notify_resume(struct pt_regs *regs)