A signal handler should not reassert its desire to handle its own signal. This is often done on nonpersistent platforms that is, platforms that, upon receiving a signal, unbind the signal to default behavior before calling the bound signal handler. See SIG01-C. Understand implementation-specific details regarding signal handler persistence.
A signal handler may call signal()
only if it does not need to be asynchronous-safe (in other words, all relevant signals are masked, so that the handler cannot be interrupted.)
Noncompliant Code Example
In this noncompliant code example, the signal handler handler()
is bound to signum
.
void handler(int signum) { if (signal(signum, handler) == SIG_ERR) { /* Handle error */ } /* Handle signal */ } /* ... */ if (signal(SIGUSR1, handler) == SIG_ERR) { /* Handle error */ }
On nonpersistent platforms, this solution contains a race window, starting when the host environment resets the signal and ending when the handler calls signal()
. During that time, a second signal sent to the program will trigger the default signal behavior, consequently defeating the persistent behavior implied by the call to signal()
from within the handler to reassert the binding.
If the environment is persistent (that is, it does not reset the handler when the signal is received), the signal()
call from within the handler()
function is redundant.
Compliant Solution
For persistent platforms, calling the signal()
function from within the signal handler is unnecessary.
void handler(int signum) { /* Handle signal */ } /* ... */ if (signal(SIGUSR1, handler) == SIG_ERR) { /* Handle error */ }
Compliant Solution (POSIX)
POSIX defines the sigaction()
function, which assigns handlers to signals like signal()
but also allows the caller to explicitly set persistence. Consequently, the sigaction()
function can be used to eliminate the race window on nonpersistent operating systems.
void handler(int signum) { /* Handle signal */ } /* ... */ struct sigaction act; act.sa_handler = handler; act.sa_flags = 0; if (sigemptyset( &act.sa_mask) != 0) { /* Handle error */ } if (sigaction(SIGUSR1, &act, NULL) != 0) { /* Handle error */ }
While the handler in this example does not call signal()
, it could do so safely because the signal is masked, and the handler cannot be interrupted. If the same handler is installed for more than one signal number, it would be necessary to mask the signals explicitly in act.sa_mask
to ensure that the handler cannot be interrupted, because the system masks only the signal being delivered.
POSIX recommends sigaction()
and deprecates signal()
. Unfortunately, sigaction()
is not C99-compliant and is not supported on some platforms, including Windows.
Compliant Solution (Windows)
There is no safe way to implement persistent signal handler behavior on Windows platforms, and it should not be attempted. In cases where a design depends on this behavior and the design cannot be altered, it may be necessary to claim a deviation from the rule after completing an appropriate risk analysis.
Exceptions
SIG34-EX1: On a machine with persistent signal handlers, it is safe for a handler to modify the behavior for its own signal. This would include having the signal be ignored, reset to default behavior, or handled by a different handler. A handler assigning itself to its own signal is also safe, as it is a no-op. Since multiple invocations of its signal will merely cause it to "interrupt itself", the handler is impervious to a race condition until it manages to reassign its signal.
The following code example resets a signal handler to the system's default behavior.
void handler(int signum) { #ifndef WINDOWS if (signal(signum, SIG_DFL) == SIG_ERR) { /* Handle error */ } #endif /* Handle signal */ } /* ... */ if (signal(SIGUSR1, handler) == SIG_ERR) { /* Handle error */ }
Risk Assessment
Two signals in quick succession can trigger the race condition on nonpersistent platforms, causing the signal's default behavior despite a handler's attempt to override it.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
SIG34-C |
low |
unlikely |
low |
P3 |
L3 |
Automated Detection
Compass/ROSE can detect violations of this rule. However, false positives may occur on systems with persistent handlers.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Other Languages
This rule appears in the C++ Secure Coding Standard as SIG34-CPP. Do not call signal() from within interruptible signal handlers.
References
[[ISO/IEC 9899-1999TR2]] Section 7.14.1.1, "The signal
function"
[[MITRE 07]] CWE ID 479, "Unsafe Function Call from a Signal Handler"
SIG33-C. Do not recursively invoke the raise() function 11. Signals (SIG) SIG35-C. Do not return from SIGSEGV, SIGILL, or SIGFPE signal handlers