Search code examples
clinuxsignalsposix

Reset mask of ignored signal in Linux


During exec() the mask of blocked signals (as set by sigprocmask()) is inherited by child processes.

But now I've noticed that under Linux, there is also mask of ignored signals (grep < /proc/self/status ^SigIgn), and it's also inherited by child processes. Since these are set by sigaction() with act.sa_handler = SIG_IGN, I would have expected that, as signal handlers, they are reset during exec. But they are not.

Consider example below: parent wants to ignore the SIGINT, but that also extends to the child, thus the sleep 120 can't be killed with kill -INT $(pidof sleep) anymore.

Questions:

  1. Is this POSIX compliant? Documentation of sigaction() explicitly states that effect of sigaction doesn't survive exec (Quote: "... until one of the exec functions is called"). Linux man page says "the dispositions of ignored signals are left unchanged" which I guess makes that a Linux specific feature.

  2. How to reset the ignore mask correctly & reliably? I have found no analog of sigprocmask() - manages blocked signals - to manage the mask of ignored signals. Should I simply iterate over all signals and reset them with sigaction()?

P.S. Tested with kernels v3.x & v4.9, same behavior.

#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
    struct sigaction act;
    sigset_t ss;

    system("grep -n < /proc/self/status ^SigIgn");

    memset(&act, 0, sizeof(act));
    act.sa_handler = SIG_IGN;
    sigaction( SIGINT, &act, NULL );

    system("grep -n < /proc/self/status ^SigIgn");

    sigemptyset(&ss);
    sigprocmask(SIG_SETMASK, &ss, NULL);

    system("grep -n < /proc/self/status ^SigIgn");

    system("sleep 120");

    return 0;
}

Solution

    1. Is this POSIX compliant?

    Yes, https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html specifies that:

    Signals set to the default action (SIG_DFL) in the calling process image shall be set to the default action in the new process image. Except for SIGCHLD, signals set to be ignored (SIG_IGN) by the calling process image shall be set to be ignored by the new process image. Signals set to be caught by the calling process image shall be set to the default action in the new process image (see ).

    (emphasis mine)

    Blocked-signals masks, default-signals mask, and ignored-signals masks are all inherited across exec* calls in POSIX, although POSIX doesn't really have a concept of the latter two masks (they're just special signal dispositions, set for each signal separately, unlike blocked-signals masks which can be modified en-masse).

    1. How to reset the ignore mask correctly & reliably? I have found no analog of sigprocmask() - manages blocked signals - to manage the mask of ignored signals. Should I simply iterate over all signals and reset them with sigaction()?

    Yes—iterate over the signals with sigaction or signal. (While sigaction should otherwise be preferred, for setting signals to SIG_IGN/SIG_DFL, signal is sufficient, portable, and more compact.). Ideally this should be done after fork() to prevent races with other threads (signal dispositions are shared global state).

    Even better, you can use posix_spawn/posix_spawnp with an attribute (posix_spawnattr_t) that specifies the set of signals that should be reset to default in the child process. The posix_spawnattr_setsigdefault attribute-setter can be used to specify this signal set.