14

I just found out that someone is calling - from a signal handler - a definitely not async-signal-safe function that I wrote.

So, now I'm curious: how to circumvent this situation from happening again? I'd like to be able to easily determine if my code is running in signal handler context (language is C, but wouldn't the solution apply to any language?):

int myfunc( void ) {
    if( in_signal_handler_context() ) { return(-1) }
    // rest of function goes here
    return( 0 );
}

This is under Linux. Hope this isn't an easy answer, or else I'll feel like an idiot.

AkaZecik
  • 980
  • 2
  • 11
  • 16
smcdow
  • 699
  • 1
  • 7
  • 12

6 Answers6

9

Apparently, newer Linux/x86 (probably since some 2.6.x kernel) calls signal handlers from the vdso. You could use this fact to inflict the following horrible hack upon the unsuspecting world:

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

#include <unistd.h>

uintmax_t vdso_start = 0;
uintmax_t vdso_end = 0;             /* actually, next byte */

int check_stack_for_vdso(uint32_t *esp, size_t len)
{
    size_t i;

    for (i = 0; i < len; i++, esp++)
            if (*esp >= vdso_start && *esp < vdso_end)
                    return 1;

    return 0;
}

void handler(int signo)
{
    uint32_t *esp;

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
    /* XXX only for demonstration, don't call printf from a signal handler */
    printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
}

void parse_maps()
{
    FILE *maps;
    char buf[256];
    char path[7];
    uintmax_t start, end, offset, inode;
    char r, w, x, p;
    unsigned major, minor;

    maps = fopen("/proc/self/maps", "rt");
    if (maps == NULL)
            return;

    while (!feof(maps) && !ferror(maps)) {
            if (fgets(buf, 256, maps) != NULL) {
                    if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
                                    &start, &end, &r, &w, &x, &p, &offset,
                                    &major, &minor, &inode, path) == 11) {
                            if (!strcmp(path, "[vdso]")) {
                                    vdso_start = start;
                                    vdso_end = end;
                                    break;
                            }
                    }
            }
    }

    fclose(maps);

    printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
}

int main()
{
    struct sigaction sa;
    uint32_t *esp;

    parse_maps();
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = handler;
    sa.sa_flags = SA_RESTART;

    if (sigaction(SIGUSR1, &sa, NULL) < 0) {
            perror("sigaction");
            exit(1);
    }

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
    printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));

    kill(getpid(), SIGUSR1);

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
    printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));

    return 0;
}

SCNR.

ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • I wasn't aware that signal handlers were called from the vdso. Could you point to a reference? At any rate, I like this hack. A lot. It'd be easy enough to roll this up into a opaque library. The trick would be making sure that parse_maps() was called before any signal handlers. – smcdow Jan 28 '11 at 22:40
  • The best reference I can find is http://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L310 – ninjalj Jan 28 '11 at 23:10
  • But the comment at line 320 looks interesting: http://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L320 – ninjalj Jan 28 '11 at 23:11
  • We're running on stock RHEL-5 distros -- Linux-2.6.18, so maybe I'll be able to use this. I'll write up a test case next week (which really means that I'll copy-and-paste your code to see what it does :-). Thanks. – smcdow Jan 29 '11 at 16:47
4

If we can assume your application doesn't manually block signals using sigprocmask() or pthread_sigmask(), then this is pretty simple: get your current thread ID (tid). Open /proc/tid/status and get the values for SigBlk and SigCgt. AND those two values. If the result of that AND is non-zero, then that thread is currently running from inside a signal handler. I've tested this myself and it works.

David Yeager
  • 596
  • 5
  • 9
  • You'd need the process ID (PID), not the thread ID. And doing this will involve calling non-async-signal-safe functions, barring writing your own. – mgarey Mar 08 '18 at 21:55
  • 1
    @mgarey good point, make sure you do this only using async-signal safe functions such as the open() and read() system calls which is definitely doable. However if the program is multithreaded then you MUST use tid which will differ from pid. Being inside a signal handler is a thread specific state. – David Yeager May 06 '18 at 21:56
0

There are two proper ways to deal with this:

  • Have your co-workers stop doing the wrong thing. Good luck pulling this off with the boss, though...

  • Make your function re-entrant and async-safe. If necessary, provide a function with a different signature (e.g. using the widely-used *_r naming convention) with the additional arguments that are necessary for state preservation.

As for the non-proper way to do this, on Linux with GNU libc you can use backtrace() and friends to go through the caller list of your function. It's not easy to get right, safe or portable, but it might do for a while:

/*
 * *** Warning ***
 *
 * Black, fragile and unportable magic ahead
 *
 * Do not use this, lest the daemons of hell be unleashed upon you
 */
int in_signal_handler_context() {
        int i, n;
        void *bt[1000];
        char **bts = NULL;

        n = backtrace(bt, 1000);
        bts = backtrace_symbols(bt, n);

        for (i = 0; i < n; ++i)
                printf("%i - %s\n", i, bts[i]);

        /* Have a look at the caller chain */
        for (i = 0; i < n; ++i) {
                /* Far more checks are needed here to avoid misfires */
                if (strstr(bts[i], "(__libc_start_main+") != NULL)
                        return 0;
                if (strstr(bts[i], "libc.so.6(+") != NULL)
                        return 1;
        }

        return 0;
}


void unsafe() {
        if (in_signal_handler_context())
                printf("John, you know you are an idiot, right?\n");
}

In my opinion, it might just be better to quit rather than be forced to write code like this.

thkala
  • 84,049
  • 23
  • 157
  • 201
  • I've just tried `backtrace()`, and it just doesn't work: `__libc_start_main` is in the trace both in and out of signal handling context. – P Shved Jan 28 '11 at 21:42
  • As I mentioned, it's _not_ easy to get right. You have to find a difference in the backtrace between the two cases and use that. E.g for my test I assumed that no libc function would be in the backtrace before reaching `main()`, unless it's the signal handling code. What does your backtrace look like in each case? – thkala Jan 28 '11 at 21:45
  • Two comments: (1) I was afraid it might be something like this. (2) Not to be a snot, but printf(3) is not an async-signal-safe function. You'd have to use write(2). -- A list of async-signal-safe functions (at least, the list that I usually refer to) can be found here: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html – smcdow Jan 28 '11 at 21:49
  • This is why I avoided trying to give a direct answer at all. What you are asking for requires a hack as @thkala has proposed. Hence, it is probably better to find a diplomatic solution. – Judge Maygarden Jan 28 '11 at 21:51
  • @smcdow: I know quite well that `printf()` is not async safe - but neither is the function that's using it (i.e. your function) :-) – thkala Jan 28 '11 at 21:55
  • Forgot to read your comment above the code. The whole point of in_signal_handler_context() was to make the function reentrant :-). – smcdow Jan 28 '11 at 21:55
  • @smcdow: I thought that it was so that you could tell John that he was being an idiot and force him to change his code :-) – thkala Jan 28 '11 at 22:02
  • @thkala: Actually, I like that idea better. :-) – smcdow Jan 28 '11 at 22:16
  • are the backtrace and backtrace_symbols calls alowed inside Signal handler as they are not async_siganl safe? – sandeep Jul 05 '11 at 12:52
  • are the backtrace and backtrace_symbols calls alowed inside Signal handler as they are not async_siganl safe? in one of my program where i was using backtrace, when ever there was malloc / free / printf / etc.. calls on the callstack of a thread and my signal handler was doing backtrace of that particula thread, the program used to crash. when i debug it with gdb, i observered the RBP register link after Signal handler was broken .. (i.e. Current Frame RBP register points to prevoius frame RBP.. like wise we can navigate the stack). – sandeep Jul 05 '11 at 13:00
0

You could work out something using sigaltstack. Set up an alternative signal stack, get the stack pointer in some async-safe way, if within the alternative stack go on, otherwise abort().

Tobu
  • 24,771
  • 4
  • 91
  • 98
  • I thought about something like that, but I don't think I could guarantee that I'd have the alternate stack set up before my function was called from a signal handler. I should also reiterate that my function is NEVER supposed to be called from a signal handler, and the documentation says so. – smcdow Jan 28 '11 at 21:58
  • There's an easier way. `sigaltstack` is required to return an error if you're already running on the alternate stack and try to make changes to it, so you could just try calling it and see if the call fails. – R.. GitHub STOP HELPING ICE Mar 22 '11 at 20:40
0

I guess you need to do the following. This is a complex solution, which combines the best practices not only from coding, but from software engineering as well!

  1. Persuade your boss that naming convention on signal handlers is a good thing. Propose, for example, a Hungarian notation, and tell that it was used in Microsoft with great success. So, all signal handlers will start with sighnd, like sighndInterrupt.
  2. Your function that detects signal handling context would do the following:
    1. Get the backtrace().
    2. Look if any of the functions in it begin with sighnd.... If it does, then congratulations, you're inside a signal handler!
    3. Otherwise, you're not.
  3. Try to avoid working with Jimmy in the same company. "There can be only one", you know.
Community
  • 1
  • 1
P Shved
  • 96,026
  • 17
  • 121
  • 165
  • We're going to have a janitor make a pass over the code base and report back any and all functions being called from signal handlers. I'm cringing already. – smcdow Jan 28 '11 at 21:59
  • @smcdow, by the way, you could employ a static analysis tool for that. But then again, you'd have to annotate each signal handler, which makes the solution proposed not any more complex. :-) – P Shved Jan 28 '11 at 22:07
  • some time ago there was a Valgrind tool to look for signal handler problems, it was called crocus. – ninjalj Jan 28 '11 at 22:25
  • @ninjalj, Valgrind is a dynamic analysis tool, not a static one. Anyway, I've just devised a way to do it statically, but that's quite off-topic here. – P Shved Jan 28 '11 at 22:28
0

for code optimized at -O2 or better (istr) have found need to add -fno-omit-frame-pointer

else gcc will optimize out the stack context information

chaosless
  • 573
  • 5
  • 8