Signal handlers can be interrupted by signals, including their own. If a signal is not reset before its handler is called, the handler can interrupt its own execution. A handler that always successfully executes its code despite interrupting itself or being interrupted is asynchronous-safe.
Some platforms provide the ability to mask signals while a signal handler is being processed. If a signal is masked while its own handler is processed, the handler is un-interruptible, and need not be asynchronous-safe.
Vulnerabilities can arise if a non-asynchronous-safe signal handler is interrupted with any unmasked signal, including its own, especially if it manipulates globally-accessible data.
Non-Compliant Code Example
This non-compliant code example registers a single signal handler to process both SIGUSR1
and SIGUSR2
. The variable sig2
should be set to one if one or more SIGUSR1
signals are followed by SIGUSR2
. This code essentially implements a finite state machine within the signal handler.
Code Block | ||
---|---|---|
| ||
#include <signal.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1) { sig1 = 1; } else if (sig1) { sig2 = 1; } } int main(void) { signal(SIGUSR1, handler); signal(SIGUSR2, handler); while (sig2 == 0) { /* do nothing or give up CPU for a while */ } /* ... */ return 0; } |
Unfortunately, there is a race condition in the implementation of handler()
. If handler()
is called to handle SIGUSR1
and is interrupted to handle SIGUSR2
, it is possible that sig2
will not be set.
This non-compliant code example also violates SIG31-C. Do not access or modify shared objects in signal handlers.
Non-Compliant Code Example (External finite state machine)
This non-compliant code example moves the finite state machine out of the signal handler, making the handler asynchronous-safe.
Code Block | ||
---|---|---|
| ||
#include <signal.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1) { sig1 = 1; } else if (signum == SIGUSR2) { sig2 = 1; } } int main(void) { int state = 0; signal(SIGUSR1, handler); signal(SIGUSR2, handler); while (state != 2) { /* do nothing or give up CPU for a while */ if (state == 0 && sig1) { state = 1; sig2 = 0; } if (state == 1 && sig2) { state = 2; } } /* ... */ return 0; } |
There is still a race condition in this code where a SIGUSR2
sent immediately after a SIGUSR1
is ignored. This is because the SIGUSR2
is processed before the while loop sets the state to 1 and sig2
to 0, which erases the evidence of SIGUSR2
. To eliminate this race condition, the OS must queue subsequent signals while one signal is being handled, and the finite state machine must be handled by the signal handler.
Compliant Solution (POSIX)
The POSIX sigaction()
function assigns handlers to signals in a similar manner to the C99 signal()
function, but also allows signal masks to be set explicitly. Consequently, sigaction()
can be used to prevent a signal handler from interrupting itself.
Code Block | ||
---|---|---|
| ||
#include <signal.h> #include <stdio.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1) { sig1 = 1; } else if (sig1) { sig2 = 1; } } int main(void) { struct sigaction act; act.sa_handler = &handler; act.sa_flags = 0; if (sigemptyset(&act.sa_mask) != 0) { /* handle error */ } if (sigaddset(&act.sa_mask, SIGUSR1)) { /* handle error */ } if (sigaddset(&act.sa_mask, SIGUSR2)) { /* handle error */ } if (sigaction(SIGUSR1, &act, NULL) != 0) { /* handle error */ } if (sigaction(SIGUSR2, &act, NULL) != 0) { /* handle error */ } while (sig2 == 0) { /* do nothing or give up CPU for a while */ } /* ... */ return 0; } |
POSIX recommends sigaction()
and deprecates signal()
. Unfortunately, sigaction()
is not defined in C99 and is consequently not as portable a solution.
Risk Assessment
Wiki Markup |
---|
Interrupting a non-interruptible signal handler can result in a variety of vulnerabilities \[[Zalewski 01|AA. C References#Zalewski 01]\]. |
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SIG00-A | 3 (high) | 3 (likely) | 1 (high) | P9 | L2 |
Automated Detection
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[Dowd 06 | AA. C References#Dowd 06]\] Chapter 13, "Synchronization and State" (Signal Interruption and Repetition) \[[ISO/IEC 03|AA. C References#ISO/IEC 03]\] Section 5.2.3, "Signals and interrupts" \[[Open Group 04|AA. C References#Open Group 04]\] [longjmp|http://www.opengroup.org/onlinepubs/000095399/functions/longjmp.html] \[OpenBSD\] [{{signal()}} Man Page|http://www.openbsd.org/cgi-bin/man.cgi?query=signal] \[[Zalewski 01|AA. C References#Zalewski 01]\] |
12. Signals (SIG) 12. Signals (SIG) SIG01-A. Understand implementation-specific details regarding signal handler persistence