selftests/x86: Add a test for syscall restart under ptrace
This catches a regression from the compat syscall rework. The 32-bit variant of this test currently fails. The issue is that, for a 32-bit tracer and a 32-bit tracee, GETREGS+SETREGS with no changes should be a no-op. It currently isn't a no-op if RAX indicates signal restart, because the high bits get cleared and the kernel loses track of the restart state. Reported-by: Robert O'Callahan <robert@ocallahan.org> Signed-off-by: Andy Lutomirski <luto@kernel.org> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Shuah Khan <shuahkh@osg.samsung.com> Cc: Thomas Gleixner <tglx@linutronix.de> Link: http://lkml.kernel.org/r/c4040b40b5b4a37ed31375a69b683f753ec6788a.1455142412.git.luto@kernel.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
parent
adcfd23ead
commit
4036134322
|
@ -103,6 +103,17 @@ static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
|
|||
err(1, "sigaction");
|
||||
}
|
||||
|
||||
static void setsigign(int sig, int flags)
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = (void *)SIG_IGN;
|
||||
sa.sa_flags = flags;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(sig, &sa, 0))
|
||||
err(1, "sigaction");
|
||||
}
|
||||
|
||||
static void clearhandler(int sig)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
@ -277,6 +288,119 @@ static void test_ptrace_syscall_restart(void)
|
|||
}
|
||||
}
|
||||
|
||||
static void test_restart_under_ptrace(void)
|
||||
{
|
||||
printf("[RUN]\tkernel syscall restart under ptrace\n");
|
||||
pid_t chld = fork();
|
||||
if (chld < 0)
|
||||
err(1, "fork");
|
||||
|
||||
if (chld == 0) {
|
||||
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
|
||||
err(1, "PTRACE_TRACEME");
|
||||
|
||||
printf("\tChild will take a nap until signaled\n");
|
||||
setsigign(SIGUSR1, SA_RESTART);
|
||||
raise(SIGSTOP);
|
||||
|
||||
syscall(SYS_pause, 0, 0, 0, 0, 0, 0);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
int status;
|
||||
|
||||
/* Wait for SIGSTOP. */
|
||||
if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
|
||||
err(1, "waitpid");
|
||||
|
||||
struct user_regs_struct regs;
|
||||
|
||||
printf("[RUN]\tSYSCALL\n");
|
||||
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
|
||||
err(1, "PTRACE_SYSCALL");
|
||||
wait_trap(chld);
|
||||
|
||||
/* We should be stopped at pause(2) entry. */
|
||||
|
||||
if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0)
|
||||
err(1, "PTRACE_GETREGS");
|
||||
|
||||
if (regs.user_syscall_nr != SYS_pause ||
|
||||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
|
||||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
|
||||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
|
||||
printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
|
||||
nerrs++;
|
||||
} else {
|
||||
printf("[OK]\tInitial nr and args are correct\n");
|
||||
}
|
||||
|
||||
/* Interrupt it. */
|
||||
kill(chld, SIGUSR1);
|
||||
|
||||
/* Advance. We should be stopped at exit. */
|
||||
printf("[RUN]\tSYSCALL\n");
|
||||
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
|
||||
err(1, "PTRACE_SYSCALL");
|
||||
wait_trap(chld);
|
||||
|
||||
if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0)
|
||||
err(1, "PTRACE_GETREGS");
|
||||
|
||||
if (regs.user_syscall_nr != SYS_pause ||
|
||||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
|
||||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
|
||||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
|
||||
printf("[FAIL]\tArgs after SIGUSR1 are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
|
||||
nerrs++;
|
||||
} else {
|
||||
printf("[OK]\tArgs after SIGUSR1 are correct (ax = %ld)\n",
|
||||
(long)regs.user_ax);
|
||||
}
|
||||
|
||||
/* Poke the regs back in. This must not break anything. */
|
||||
if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0)
|
||||
err(1, "PTRACE_SETREGS");
|
||||
|
||||
/* Catch the (ignored) SIGUSR1. */
|
||||
if (ptrace(PTRACE_CONT, chld, 0, 0) != 0)
|
||||
err(1, "PTRACE_CONT");
|
||||
if (waitpid(chld, &status, 0) != chld)
|
||||
err(1, "waitpid");
|
||||
if (!WIFSTOPPED(status)) {
|
||||
printf("[FAIL]\tChild was stopped for SIGUSR1 (status = 0x%x)\n", status);
|
||||
nerrs++;
|
||||
} else {
|
||||
printf("[OK]\tChild got SIGUSR1\n");
|
||||
}
|
||||
|
||||
/* The next event should be pause(2) again. */
|
||||
printf("[RUN]\tStep again\n");
|
||||
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
|
||||
err(1, "PTRACE_SYSCALL");
|
||||
wait_trap(chld);
|
||||
|
||||
/* We should be stopped at pause(2) entry. */
|
||||
|
||||
if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0)
|
||||
err(1, "PTRACE_GETREGS");
|
||||
|
||||
if (regs.user_syscall_nr != SYS_pause ||
|
||||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
|
||||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
|
||||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
|
||||
printf("[FAIL]\tpause did not restart (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
|
||||
nerrs++;
|
||||
} else {
|
||||
printf("[OK]\tpause(2) restarted correctly\n");
|
||||
}
|
||||
|
||||
/* Kill it. */
|
||||
kill(chld, SIGKILL);
|
||||
if (waitpid(chld, &status, 0) != chld)
|
||||
err(1, "waitpid");
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("[RUN]\tCheck int80 return regs\n");
|
||||
|
@ -290,5 +414,7 @@ int main()
|
|||
|
||||
test_ptrace_syscall_restart();
|
||||
|
||||
test_restart_under_ptrace();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue