1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// 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");
}
|