Spotting async-unsafe usage

Over the years (now I sound like an old man) I've worked in various situations where the root cause of the issue turned out to be code that uses calls that are not async-signal-safe from within a signal handler. In case you don't know, an async-signal-safe call is any function that can be called from within a signal handler (i.e a function that catches signals). In fact the Single UNIX specification Version 3 defines any function that isn't explicitly listed to be async-signal-safe as one that is unsafe (i.e don't use it from a signal handler you bad man!). A basic list of async-signal-safe functions can be found in section 2.4.1 of the Single UNIX specification.

What makes a function unsafe is the fact that you can't reenter it. Think about malloc(3C) for a second ... we're half way through carrying out a malloc, just going about our own business manipulating some pointers and BANG, someone drops a signal on us. Off we trot to execute the handler for this signal. However, let's say the signal handler itself executes a malloc call. The code we've just jumped from hasn't finished the malloc and we have an indeterminate state. The signal handlers malloc will either go pear shaped now (if you're lucky) or someone will trip over the carnage in a while. The malloc() library call is not designed to be reentrant and so shouldn't be called from a signal handler unless you can guarantee that the signal that has the naughty handler attached can't be called when in any malloc (nigh on impossible to do).

Prior to DTrace it was very difficult indeed to look at what was being called from a signal handler. Now it's easier than childs play as there's less emotion involved! The D below just looks for either printf or malloc (two examples of async-signal-unsafe) when we are in the process of handling a signal (when libc's sigacthandler() has been called).
#!/usr/sbin/dtrace -s

#pragma D option quiet

/\* Just add your asynch unsafe signals below. Not fullproof as
the user could have maked signals whilst in their async-unsafe
calls but this is unlikely. We could check for this.

Usage: ./sigtrace.d 'pid' where 'pid' is the process under review \*/

BEGIN
{
        name = "";
        sigs[0] = "SIGEXIT"; sigs[1] = "SIGHUP"; sigs[2] = "SIGINT";
        sigs[3] = "SIGQUIT"; sigs[4] = "SIGILL"; sigs[5] = "SIGTRAP";
        sigs[6] = "SIGABRT"; sigs[7] = "SIGEMT"; sigs[8] = "SIGFPE";
        sigs[9] = "SIGKILL"; sigs[10] = "SIGBUS"; sigs[11] = "SIGSEGV";
        sigs[12] = "SIGSYS"; sigs[13] = "SIGPIPE"; sigs[14] = "SIGALRM";
        sigs[15] = "SIGTERM"; sigs[16] = "SIGUSR1"; sigs[17] = "SIGUSR2";
        sigs[18] = "SIGCLD"; sigs[19] = "SIGPWR"; sigs[20] = "SIGWINCH";
        sigs[21] = "SIGURG"; sigs[22] = "SIGPOLL"; sigs[23] = "SIGSTOP";
        sigs[24] = "SIGTSTP"; sigs[25] = "SIGCONT"; sigs[26] = "SIGTTIN";
        sigs[27] = "SIGTTOU"; sigs[28] = "SIGVTALRM"; sigs[29] = "SIGPROF";
        sigs[30] = "SIGXCPU"; sigs[31] = "SIGXFSZ"; sigs[32] = "SIGWAITING";
        sigs[33] = "SIGLWP"; sigs[34] = "SIGFREEZE"; sigs[35] = "SIGTHAW";
        sigs[36] = "SIGCANCEL"; sigs[37] = "SIGLOST"; sigs[38] = "SIGXRES";
        sigs[39] = "SIGJVM1"; sigs[40] = "SIGJVM2"; sigs[41] = "SIGRTMIN";
        sigs[42] = "SIGRTMIN+1"; sigs[43] = "SIGRTMIN+2"; sigs[44] = "SIGRTMIN+3
";
        sigs[45] = "SIGRTMAX-2"; sigs[46] = "SIGRTMAX-1"; sigs[47] = "SIGRTMAX";

}


pid$1:libc.so.1:sigacthandler:entry
{
        name = execname;
        self->inhandler=1;
        self->signo = arg0;
        printf("%s: %s being handled\\n", execname, sigs[self->signo]);
}

pid$1:libc.so.1:sigacthandler:return
{
        self->inhandler=0;
}

pid$1:libc.so.1:malloc:entry
/ self->inhandler /
{
        @a[sigs[self->signo], "malloc"] = count();
}

pid$1:libc.so.1:printf:entry
/ self->inhandler /
{
        @a[sigs[self->signo], "printf"] = count();
}

END
{
        printf("----------- %s --------------\\n", name);
        printa("%s handler called %s %@d times\\n", @a);
}

# ./sigtrace.d `pgrep inetd`
inetd: SIGHUP being handled
\^C
----------- inetd --------------
SIGHUP handler called malloc 2101 times

You can now go and tell your favourite ISV exactly what they shouldn't be doing when and where! You'll be glad to hear the age old code for reconfiguring inetd (demonstrated above with dropping a `kill -1` on inetd!) has been rewritten as part of the new Service Management Facility that will be coming your way in Septembers Solaris Express . Also check out Stephen Hahns blog for more details on smf.
Comments:

Post a Comment:
Comments are closed for this entry.
About

jonh

Search

Categories
Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today