Commit 77da0802 authored by Mao Minkai's avatar Mao Minkai Committed by guzitao
Browse files

sw64: fix syscall restart

Sunway inclusion
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/I8CCR7



--------------------------------

If syscall has completed successfully but r0 happens to be equal to one
of ERESTART* codes, the syscall restart logic would be triggered
incorrectly. To fix this, use syscall_get_error() to make sure we are
using an errno, not return value.

SW64 has a equivalent of arm bug fixed by commit 653d48b2 ("arm: fix
really nasty sigreturn bug"). If signal gets caught by an interrupt that
hits when we have the right value in r0 (513) and r19 (1), *and* another
signal gets delivered upon sigreturn() or rt_sigreturn() (e.g. included
into the blocked mask for the first signal and posted while the handler
had been running), the syscall restart logic will see regs->orig_r0 not
equal to NO_SYSCALL (we are in a syscall, after all), and r0 and r19
already restored to its original value (513 and 1, which happens to
indicate -ERESTARTNOINTR) and assume that we need to apply the usual
syscall restart logics. To fix this, call
force_successful_syscall_return() in sigreturn() and rt_sigreturn() so
the syscall restart logic will not know we are in a syscall.

Signed-off-by: default avatarMao Minkai <maominkai@wxiat.com>
Reviewed-by: default avatarHe Sheng <hesheng@wxiat.com>
Signed-off-by: default avatarGu Zitao <guzitao@wxiat.com>
parent 54bcfb29
Loading
Loading
Loading
Loading
+15 −10
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <asm/uprobes.h>
#include <asm/vdso.h>
#include <asm/switch_to.h>
#include <asm/syscall.h>

#include "proto.h"

@@ -119,6 +120,8 @@ SYSCALL_DEFINE1(sigreturn, struct sigcontext __user *, sc)
	struct pt_regs *regs = current_pt_regs();
	sigset_t set;

	force_successful_syscall_return();

	/* Always make any pending restarted system calls return -EINTR */
	current->restart_block.fn = do_no_restart_syscall;

@@ -150,6 +153,8 @@ SYSCALL_DEFINE1(rt_sigreturn, struct rt_sigframe __user *, frame)
	struct pt_regs *regs = current_pt_regs();
	sigset_t set;

	force_successful_syscall_return();

	/* Always make any pending restarted system calls return -EINTR */
	current->restart_block.fn = do_no_restart_syscall;

@@ -295,21 +300,21 @@ do_signal(struct pt_regs *regs)
		single_stepping |= ptrace_cancel_bpt(current);
		/* Whee!  Actually deliver the signal.  */
		if (regs->orig_r0 != NO_SYSCALL) {
			switch (regs->regs[0]) {
			case ERESTARTSYS:
			switch (syscall_get_error(current, regs)) {
			case -ERESTARTSYS:
				if (!(ksig.ka.sa.sa_flags & SA_RESTART)) {
					regs->regs[0] = EINTR;
					break;
				}
				fallthrough;
			case ERESTARTNOINTR:
			case -ERESTARTNOINTR:
				/* reset v0 and a3 and replay syscall */
				regs->regs[0] = regs->orig_r0;
				regs->regs[19] = regs->orig_r19;
				regs->pc -= 4;
				break;
			case ERESTARTNOHAND:
			case ERESTART_RESTARTBLOCK:
			case -ERESTARTNOHAND:
			case -ERESTART_RESTARTBLOCK:
				regs->regs[0] = EINTR;
				break;
			}
@@ -319,16 +324,16 @@ do_signal(struct pt_regs *regs)
	} else {
		single_stepping |= ptrace_cancel_bpt(current);
		if (regs->orig_r0 != NO_SYSCALL) {
			switch (regs->regs[0]) {
			case ERESTARTSYS:
			case ERESTARTNOINTR:
			case ERESTARTNOHAND:
			switch (syscall_get_error(current, regs)) {
			case -ERESTARTSYS:
			case -ERESTARTNOINTR:
			case -ERESTARTNOHAND:
				/* Reset v0 and a3 and replay syscall.  */
				regs->regs[0] = regs->orig_r0;
				regs->regs[19] = regs->orig_r19;
				regs->pc -= 4;
				break;
			case ERESTART_RESTARTBLOCK:
			case -ERESTART_RESTARTBLOCK:
				/* Set v0 to the restart_syscall and replay */
				regs->regs[0] = __NR_restart_syscall;
				regs->pc -= 4;