// SPDX-License-Identifier: GPL-2.0-or-later /* * Test that a syscall does not get restarted twice, handled by trap_norestart() * * Based on Al's description, and a test for the bug fixed in this commit: * * commit 9a81c16b527528ad307843be5571111aa8d35a80 * Author: Al Viro <viro@zeniv.linux.org.uk> * Date: Mon Sep 20 21:48:57 2010 +0100 * * powerpc: fix double syscall restarts * * Make sigreturn zero regs->trap, make do_signal() do the same on all * paths. As it is, signal interrupting e.g. read() from fd 512 (== * ERESTARTSYS) with another signal getting unblocked when the first * handler finishes will lead to restart one insn earlier than it ought * to. Same for multiple signals with in-kernel handlers interrupting * that sucker at the same time. Same for multiple signals of any kind * interrupting that sucker on 64bit... */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/wait.h> #include <sys/syscall.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include "utils.h" static void SIGUSR1_handler(int sig) { kill(getpid(), SIGUSR2); /* * SIGUSR2 is blocked until the handler exits, at which point it will * be raised again and think there is a restart to be done because the * pending restarted syscall has 512 (ERESTARTSYS) in r3. The second * restart will retreat NIP another 4 bytes to fail case branch. */ } static void SIGUSR2_handler(int sig) { } static ssize_t raw_read(int fd, void *buf, size_t count) { register long nr asm("r0") = __NR_read; register long _fd asm("r3") = fd; register void *_buf asm("r4") = buf; register size_t _count asm("r5") = count; asm volatile( " b 0f \n" " b 1f \n" " 0: sc 0 \n" " bns 2f \n" " neg %0,%0 \n" " b 2f \n" " 1: \n" " li %0,%4 \n" " 2: \n" : "+r"(_fd), "+r"(nr), "+r"(_buf), "+r"(_count) : "i"(-ENOANO) : "memory", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "ctr", "cr0"); if (_fd < 0) { errno = -_fd; _fd = -1; } return _fd; } #define DATA "test 123" #define DLEN (strlen(DATA)+1) int test_restart(void) { int pipefd[2]; pid_t pid; char buf[512]; if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { /* Child reads from pipe */ struct sigaction act; int fd; memset(&act, 0, sizeof(act)); sigaddset(&act.sa_mask, SIGUSR2); act.sa_handler = SIGUSR1_handler; act.sa_flags = SA_RESTART; if (sigaction(SIGUSR1, &act, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); } memset(&act, 0, sizeof(act)); act.sa_handler = SIGUSR2_handler; act.sa_flags = SA_RESTART; if (sigaction(SIGUSR2, &act, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); } /* Let's get ERESTARTSYS into r3 */ while ((fd = dup(pipefd[0])) != 512) { if (fd == -1) { perror("dup"); exit(EXIT_FAILURE); } } if (raw_read(fd, buf, 512) == -1) { if (errno == ENOANO) { fprintf(stderr, "Double restart moved restart before sc instruction.\n"); _exit(EXIT_FAILURE); } perror("read"); exit(EXIT_FAILURE); } if (strncmp(buf, DATA, DLEN)) { fprintf(stderr, "bad test string %s\n", buf); exit(EXIT_FAILURE); } return 0; } else { int wstatus; usleep(100000); /* Hack to get reader waiting */ kill(pid, SIGUSR1); usleep(100000); if (write(pipefd[1], DATA, DLEN) != DLEN) { perror("write"); exit(EXIT_FAILURE); } close(pipefd[0]); close(pipefd[1]); if (wait(&wstatus) == -1) { perror("wait"); exit(EXIT_FAILURE); } if (!WIFEXITED(wstatus)) { fprintf(stderr, "child exited abnormally\n"); exit(EXIT_FAILURE); } FAIL_IF(WEXITSTATUS(wstatus) != EXIT_SUCCESS); return 0; } } int main(void) { test_harness_set_timeout(10); return test_harness(test_restart, "sig sys restart"); }