Search code examples
csignalssegmentation-faultsetjmp

SIGSEGV cannot be caught twice with sigaction


The following code has segmentation fault at the second printf, while this is expected to be handled (setjmp). Note that each printf create segmentation fault due to wrong format string. The first segmentation fault is handled properly but not the second (running the code after commenting out either printf will not segfault).

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

static jmp_buf escapeCallJmpBuf;
static void sigsegv_handler(int signal, siginfo_t *info, void *context) {
  longjmp(escapeCallJmpBuf, 1);
}

int main(int argc, char **argv) {
  struct sigaction segvAction, segvActionOld;
  segvAction.sa_handler = 0;
  memset(&segvAction.sa_mask, 0, sizeof(segvAction.sa_mask));
  segvAction.sa_flags = SA_SIGINFO;
  segvAction.sa_sigaction = sigsegv_handler;
  sigaction(SIGSEGV, &segvAction, &segvActionOld);

  int res;

  // Catch first segmentation fault
  if (setjmp(escapeCallJmpBuf)) {
    res = 1;
  } else {
    printf ("%s\n", 2); // This will segfault
    res = 0;
  }

  // try to catch second segmentation fault
  if (setjmp(escapeCallJmpBuf)) {
    res = 2;
  } else {
    printf ("%s\n", 3); // This will segfault
    res = 0;
  }

  sigaction(SIGSEGV, &segvActionOld, 0);
  return res;
}

Solution

  • setjmp and longjmp don't necessarily restore the signal mask (depending on the implementation). What probably happens is after handling the first SIGSEGV, the signal mask is restored to the default. So the second SIGSEGV isn't caught i.e., default action takes place instead of yours, which is to exit the process.

    You should be using sigsetjmp and siglongjmp instead.

    POSIX states:

    It is unspecified whether longjmp() restores the signal mask, leaves the signal mask unchanged, or restores it to its value at the time setjmp() was called.

    And suggests:

    Applications whose behavior depends on the value of the signal mask should not use longjmp() and setjmp(), since their effect on the signal mask is unspecified, but should instead use the siglongjmp() and sigsetjmp() functions (which can save and restore the signal mask under application control).

    In addition, there are restrictions about jumping from signal handlers:

    It is recommended that applications do not call longjmp() or siglongjmp() from signal handlers. To avoid undefined behavior when calling these functions from a signal handler, the application needs to ensure one of the following two things:

    After the call to longjmp() or siglongjmp() the process only calls async-signal-safe functions and does not return from the initial call to main().

    Any signal whose handler calls longjmp() or siglongjmp() is blocked during every call to a non-async-signal-safe function, and no such calls are made after returning from the initial call to main().