signal() doubly considered harmful

When you want to set signal handlers on UNIX systems, the typical choice is to use signal (specified in C89, C99 and POSIX.1-2001) or sigaction (specified in POSIX.1-2001 and System V r4).

Quoting the signal manual page:

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.

POSIX.1 solved the portability mess by specifying sigaction(2), which provides explicit control of the semantics when a signal handler is invoked; use that interface instead of signal().

Then it goes on about the UNIX vs BSD semantics, and how they affect signal delivery, which essentially is the main reason why one would want to stop using signal and use sigaction instead, with specifically chosen flags.

But this is not really what I wanted to talk about here.

One of the uses of signal or sigaction is to temporarily set a signal handler and restore the old signal handler once the job is done. Notwithstanding the fact that it's a pretty horrible thing to do in a multi-threaded program, it's also a horrible thing to do at all with signal if sigaction is used.

The core of the problem is the following: the information you get from signal() about the old signal handler is missing all the important pieces about it if it was originally set with sigaction(), namely, flags, masks and restorer.

So if you do use signal() to temporarily set a signal handler and then restore the previous signal handler, you risk resetting flags, masks and restorer. The first awful thing this means is the previous signal handler might be expecting three arguments, only one of which will be valid when it's invoked. Unexpected things can also happen with the lack of expected flags or masks. This is why you'll see horrible workarounds like this or that.

In short, if you do use signal() to temporarily set a signal handler and then restore the previous signal handler, you're doing it wrong. And if you do that in a system library or driver, thank you for screwing things up. I'm looking at you libsc-a3xx.so.

2013-05-27 17:15:13+0900

p.d.o, p.m.o

Responses are currently closed, but you can trackback from your own site.

2 Responses to “signal() doubly considered harmful”

  1. Anonymous Says:

    This is the kind of thing that glibc could easily warn about. Remember the type of signal handler set for each signal (signal-style or sigaction-style). When someone calls signal on a signal that currently has a sigaction-style handler, go ahead and return the previous handler, but remember it, and optionally warn about it (that’s the aggressive version, since it assumes the sighandler_t might be called directly). Then, if the program ever calls signal to restore that handler, scream about it (that’s the less aggressive version, since it’s *definitely* broken).

  2. Anonymous Says:

    Adding to that: glibc could also grow a new sigaction flag which says “if anyone ever calls signal and gets this handler back, please create a thunk function for me that makes sure I get called with sigaction semantics”.