A signal is a mechanism for transferring control that is typically used to notify a process that an event has occurred. That process can then respond to that event accordingly. C99 provides functions for sending and handling signals within a C program.
Signals are handled by a process by registering a signal handler using the signal()
function, which is specified as
void (*signal(int sig, void (*func)(int)))(int);
This is conceptually equivalent to
typedef void (*SighandlerType)(int signum); extern SighandlerType signal( int signum, SighandlerType handler );
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 non-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.
Noncompliant Code Example
This noncompliant 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
, essentially implementing a finite state machine within the signal handler.
#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) { if (signal(SIGUSR1, handler) == SIG_ERR) { /* handle error */ } if (signal(SIGUSR2, handler) == SIG_ERR) { /* handler error */ } 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.
Compliant Solution (POSIX)
The POSIX sigaction()
function assigns handlers to signals in a similar manner to the C99 signal()
function, but it also allows signal masks to be set explicitly. Consequently, sigaction()
can be used to prevent a signal handler from interrupting itself.
#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
Interrupting a non-interruptible signal handler can result in a variety of vulnerabilities [[Zalewski 01]].
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
SIG00-C |
high |
likely |
high |
P9 |
L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[Dowd 06 ]] Chapter 13, "Synchronization and State" (Signal Interruption and Repetition)
[[ISO/IEC 03]] Section 5.2.3, "Signals and interrupts"
[[Open Group 04]] longjmp
[[OpenBSD]] signal()
Man Page
[[Zalewski 01]]