3

I have written a program to prevent segfault using setjmp() and longjmp(), but the program that I have written prevents segfault from happening only one time (I'm running my code inside a while loop).

Here is my code:

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

jmp_buf buf;

void my_sig_handler(int sig)
{
    if( sig )
    {
        printf("Received SIGSEGV signl \n");
        longjmp(buf,2);
    }
}

int main()
{
    while( 1)
    {
        switch( setjmp(buf) )                       // Save the program counter
        {
        case 0:
            signal(SIGSEGV, my_sig_handler);        // Register SIGSEGV signal handler function
            printf("Inside 0 statement \n");
            int *ptr = NULL;
            printf("ptr is  %d ", *ptr);            // SEG fault will happen here
            break;
        case 2:
            printf("Inside 2 statement \n");        // In case of SEG fault, program should execute this statement
            break;
        default:
            printf("Inside default statement \n");
            break;
        }
    }
    return 0;
}

Output:

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
Inside 0 statement 
Segmentation fault

Expected Output:

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
.
.(Infinite times)
.
Inside 0 statement 
Received SIGSEGV signal
Inside 2 statement 

Can someone please explain why this is only running as expected the first time only? Also, what I am missing here to run my code as expected?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Prateek Joshi
  • 3,929
  • 3
  • 41
  • 51
  • 1
    http://man7.org/linux/man-pages/man2/signal.2.html#NOTES – melpomene Oct 01 '19 at 08:08
  • Did you debug it? Why does the segmentation fault occur? – the busybee Oct 01 '19 at 09:48
  • 2
    Why would you like to repeat? A segmentation fault is commonly a missed chance to write correct code. – the busybee Oct 01 '19 at 09:49
  • @thebusybee SEG fault will occur when printf() is executed. But the whole point of question is the control should go to case 2 of switch (as I am calling longjmp() when I get SIGSEGV signal) instead of SEG fault. – Prateek Joshi Oct 01 '19 at 09:51
  • Well, did you read the notes at the URL provided by @melpomene? After the first segfault the behaviour of your program is **undefined**. You best bet is to quit the program. – the busybee Oct 01 '19 at 09:54
  • @thebusybee The program is not ignoring the signal. You should refer to the *Portability* section instead. – melpomene Oct 01 '19 at 09:56
  • `printf("Received SIGSEGV signl \n");`? In a signal handler? You can't safely call `printf()` from within a signal handler. Per the [C standard](https://port70.net/~nsz/c/c11/n1570.html#7.1.4p4): "4 The functions in the standard library are not guaranteed to be reentrant and may modify objects with static or thread storage duration." [The related footnote](https://port70.net/~nsz/c/c11/n1570.html#note188): "Thus, a signal handler cannot, in general, call standard library functions." POSIX allows for the calling of async-signal-safe functions. `printf()` is not async-signal-safe. – Andrew Henle Oct 01 '19 at 10:57
  • *SEG fault will occur when printf() is executed.* And that leaves the `stdout` stream locked on POSIX systems. [POSIX imposes the following requirement](https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html): "All functions that reference (`FILE *`) objects, except those with names ending in `_unlocked`, shall behave as if they use `flockfile()` and `funlockfile()` internally to obtain ownership of these (`FILE *`) objects." So jumping out of `printf()` leaves `stdout` locked. What you are trying to do is in general fundamentally broken. – Andrew Henle Oct 01 '19 at 11:19
  • @AndrewHenle Thanks for the info related to printf. I will keep that on mind. – Prateek Joshi Oct 01 '19 at 11:40

1 Answers1

4

Long story short: longjump is (obviously) not an async-signal-safe function, and printf too. Therefore calling those functions from a signal handler will cause undefined behavior. Refer to man 7 signal-safety for more information and a list of async-signal-safe functions.

What's most probably happening is that the longjump(buf, 2) is causing the program to "escape" the signal handler abnormally, and this causes another segmentation fault after executing the second switch case. Since another segmentation fault occurs, the signal handler is called again, and you do another longjump(buf, 2), getting back where you was, causing another segfault, and so on and so forth... indefinitely.


EDIT: as suggested by Andrew Henle in the comments below, there also are the two POSIX functions sigsetjmp() and siglongjmp(). I however prefer the approach described below since it looks cleaner to me and safely returns from the signal handler leaving the dirty work to the kernel.

If you want your code to run as expected, you can have your signal receive information about the context at the moment of the segfault:

static void signal_handler(int sig, siginfo_t *info, void *ucontext) {
    /* Assuming your architecture is Intel x86_64. */
    ucontext_t *uc = (ucontext_t *)ucontext;
    greg_t *rip = &uc->uc_mcontext.gregs[REG_RIP];

    /* Assign a new value to *rip somehow, which will be where the
       execution will continue after the signal handler returns. */
}

int main(void) {
    struct sigaction sa;
    int err;

    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = signal_handler;

    err = sigemptyset(&sa.sa_mask);
    if (err)
        return 1;

    err = sigaddset(&sa.sa_mask, SIGSEGV);
    if (err)
        return 1;

    err = sigaction(SIGSEGV, &sa, NULL);
    if (err)
        return 1;

    /* ... */

    return 0;
}

This will allow you to resume execution basically anywhere you want, provided that you actually know where to exactly resume. To set rip to the right value though, you will probably have to use a global label defined with inline asm or some other dirty trick.

Something like this should work (tested on my machine):

/* In main, where you want to retums after SIGSEGV: */
asm voaltile ("checkpoint: .global checkpoint" : );

/* In your signal handler: */
asm volatile (
    "movabs $checkpoint, %0"
    : "=r" (*rip)
);

If you are wondering about why this isn't that easy that's because it shouldn't even be done in the first place, it's basically an abomination that serves no purpose other than maybe having fun discovering how stuff can be broken in the most absurd ways.

You will need at least the following headers and feature test macros for the above to work:

#define _GNU_SOURCE
#define __USE_GNU
#include <signal.h>
#include <ucontext.h>

Note that this is (of course) both architecture and platform dependent.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • POSIX provides [`sigsetjmp()`](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/sigsetjmp.html) and [`siglongjmp()`](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/siglongjmp.html) that tend to work "better" in the OP's intended use. But even that is all fundamentally broken as jumping out of the signal handler in any way is going to lead to undefined behavior - for example, any locks protecting the heap from concurrent modification will be left hanging if a process gets `SIGSEGV` calling `free()` after something corrupted the heap. – Andrew Henle Oct 01 '19 at 11:04
  • @AndrewHenle ah, thanks for the info, I did not know that. That would indeed be useful in this case. – Marco Bonelli Oct 01 '19 at 11:20
  • @AndrewHenle: `siglongjmp` is no a "better `longjmp`" that wotks in signal handlers. Rather, `sigsetjmp`/`siglongjmp` just save/restore the signal mask; otherwise if you jump out of a signal handler, any signal masked by virtue of being in the handler (which depends on how the handler was setup) will remain masked. Often that's okay, and it's *safer* to save and restore signal mask yourself since most `siglongjmp` implementations do it in the [*wrong order*](https://git.musl-libc.org/cgit/musl/commit?id=583e55122e767b1586286a0d9c35e2a4027998ab). – R.. GitHub STOP HELPING ICE Oct 01 '19 at 12:45
  • @MarcoBonelli: Either `longjmp` or `siglongjmp` can be used from a signal handler, but **only if** the signal handler did not interrupt an async-signal-unsafe function (and neither can be used if it did). In general it's hard to know that, but you can arrange to ensure it with careful masking/unmasking of signals. For synchronous signals like `SIGSEGV`, it's generally easier (assuming nobody sends you one asynchronously with `kill`; you can avoid that by checking the `siginfo_t` from the handler). – R.. GitHub STOP HELPING ICE Oct 01 '19 at 12:48
  • @R.. *otherwise if you jump out of a signal handler, any signal masked by virtue of being in the handler (which depends on how the handler was setup) will remain masked.* Well, that's "better", isn't it? ;-) – Andrew Henle Oct 01 '19 at 12:53
  • @R.. the more I learn... thanks, will add to the answer as soon as I can. – Marco Bonelli Oct 01 '19 at 13:09