/*-1* Copyright (c) 2024 Klara, Inc.2*3* SPDX-License-Identifier: BSD-2-Clause4*5* These tests demonstrate a bug in ppoll() and pselect() where a blocked6* signal can fire after the timer runs out but before the signal mask is7* restored. To do this, we fork a child process which installs a SIGINT8* handler and repeatedly calls either ppoll() or pselect() with a 1 ms9* timeout, while the parent repeatedly sends SIGINT to the child at10* intervals that start out at 1100 us and gradually decrease to 900 us.11* Each SIGINT resynchronizes parent and child, and sooner or later the12* parent hits the sweet spot and the SIGINT arrives at just the right13* time to demonstrate the bug.14*/1516#include <sys/select.h>17#include <sys/wait.h>1819#include <err.h>20#include <errno.h>21#include <poll.h>22#include <signal.h>23#include <stdbool.h>24#include <stdlib.h>25#include <unistd.h>2627#include <atf-c.h>2829static volatile sig_atomic_t caught[NSIG];3031static void32handler(int signo)33{34caught[signo]++;35}3637static void38child(int rd, bool poll)39{40struct timespec timeout = { .tv_nsec = 1000000 };41sigset_t set0, set1;42int ret;4344/* empty mask for ppoll() / pselect() */45sigemptyset(&set0);4647/* block SIGINT, then install a handler for it */48sigemptyset(&set1);49sigaddset(&set1, SIGINT);50sigprocmask(SIG_BLOCK, &set1, NULL);51signal(SIGINT, handler);5253/* signal parent that we are ready */54close(rd);55for (;;) {56/* sleep for 1 ms with signals unblocked */57ret = poll ? ppoll(NULL, 0, &timeout, &set0) :58pselect(0, NULL, NULL, NULL, &timeout, &set0);59/*60* At this point, either ret == 0 (timer ran out) errno ==61* EINTR (a signal was received). Any other outcome is62* abnormal.63*/64if (ret != 0 && errno != EINTR)65err(1, "p%s()", poll ? "poll" : "select");66/* if ret == 0, we should not have caught any signals */67if (ret == 0 && caught[SIGINT]) {68/*69* We successfully demonstrated the race. Restore70* the default action and re-raise SIGINT.71*/72signal(SIGINT, SIG_DFL);73raise(SIGINT);74/* Not reached */75}76/* reset for next attempt */77caught[SIGINT] = 0;78}79/* Not reached */80}8182static void83prace(bool poll)84{85int pd[2], status;86pid_t pid;8788/* fork child process */89if (pipe(pd) != 0)90err(1, "pipe()");91if ((pid = fork()) < 0)92err(1, "fork()");93if (pid == 0) {94close(pd[0]);95child(pd[1], poll);96/* Not reached */97}98close(pd[1]);99100/* wait for child to signal readiness */101(void)read(pd[0], &pd[0], sizeof(pd[0]));102close(pd[0]);103104/* repeatedly attempt to signal at just the right moment */105for (useconds_t timeout = 1100; timeout > 900; timeout--) {106usleep(timeout);107if (kill(pid, SIGINT) != 0) {108if (errno != ENOENT)109err(1, "kill()");110/* ENOENT means the child has terminated */111break;112}113}114115/* we're done, kill the child for sure */116(void)kill(pid, SIGKILL);117if (waitpid(pid, &status, 0) < 0)118err(1, "waitpid()");119120/* assert that the child died of SIGKILL */121ATF_REQUIRE(WIFSIGNALED(status));122ATF_REQUIRE_MSG(WTERMSIG(status) == SIGKILL,123"child caught SIG%s", sys_signame[WTERMSIG(status)]);124}125126ATF_TC_WITHOUT_HEAD(ppoll_race);127ATF_TC_BODY(ppoll_race, tc)128{129prace(true);130}131132ATF_TC_WITHOUT_HEAD(pselect_race);133ATF_TC_BODY(pselect_race, tc)134{135prace(false);136}137138ATF_TP_ADD_TCS(tp)139{140ATF_TP_ADD_TC(tp, ppoll_race);141ATF_TP_ADD_TC(tp, pselect_race);142return (atf_no_error());143}144145146