...
The following code example shows this behavior:
Code Block |
---|
#include <stdio.h>
#include <signal.h>
volatile sig_atomic_t e_flag = 0;
void handler(int signum) {
e_flag = 1;
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
/* Handle error */
}
while (!e_flag) {}
puts("Escaped from first while ()");
e_flag = 0;
while (!e_flag) {}
puts("Escaped from second while ()");
return 0;
}
|
Many UNIX (and UNIX-like) systems automatically reinstall signal handlers upon handler execution, meaning that the signal handler defined by the user is left in place until it is explicitly removed. For example, when this code is compiled with GCC 3.4.4 and executed under Red Hat Linux, SIGINT
is captured both times by handler
.
Code Block |
---|
% ./test
^C
Escaped from first while ()
^C
Escaped from second while ()
%
|
When a signal handler is installed with the signal()
function in Windows and some UNIX systems, the default action is restored for that signal after the signal is triggered. This means that signal handlers are not automatically reinstalled. For example, when this code is compiled with Microsoft Visual Studio 2005, version 8.0, only the first SIGINT
is captured by handler
.
Code Block |
---|
> test.exe
^C
Escaped from first while ()
^C
>
|
...
Asynchronous signals may originate from malicious actors external to the process. Consequently, vulnerabilities may exist in cases where the if the signal-handler-persistence behavior is inconsistent with the developer's expectations, such as when the developer expects the signal handler to persist but it does not.
...
Code Block | ||||
---|---|---|---|---|
| ||||
void handler(int signum) {
/* Handle signal */
}
|
...
Code Block | ||||
---|---|---|---|---|
| ||||
void handler(int signum) {
if (signal(signum, handler) == SIG_ERR) {
/* Handle error */
}
/* Handle signal */
}
|
Unfortunately, this solution still 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, defeating the persistent behavior. (See rule SIG34-C. Do not call signal() from within interruptible signal handlers.)
...
The POSIX sigaction()
function assigns handlers to signals in a manner similar to the C99 C signal()
function but also allows signal-handler persistence to be controlled via the SA_RESETHAND
flag. (Leaving the flag clear makes the handler persistent.)
Code Block | ||||
---|---|---|---|---|
| ||||
/* Equivalent to signal(SIGUSR1, handler) but makes
* signal persistent */
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 */
}
|
POSIX recommends sigaction()
and deprecates signal()
. Unfortunately, sigaction()
is not defined in C99 and in the C standard and is consequently not as portable a solution.
...
Errors may also occur when the developer expects the default action to be restored for a signal but instead the signal handler persists instead.
Noncompliant Code Example (UNIX)
...
Code Block | ||||
---|---|---|---|---|
| ||||
void handler(int signum) {
/* Handle signal */
}
|
Compliant Solution (UNIX and Windows)
A C99C-compliant solution to reset the handler on a UNIX system is to rebind the signal to the default handler in the first line of the handler itself. Windows, however, automatically resets handlers to their default behavior.
Code Block | ||||
---|---|---|---|---|
| ||||
void handler(int signum) {
#ifndef WINDOWS
if (signal(signum, SIG_DFL) == SIG_ERR) {
/* Handler error */
}
#endif
/* Handle signal */
}
|
With the compliant solution for UNIX, there is no race condition occurs that can be exploited by an attacker in sending a second signal. This is because a second signal sent to the handler, before the latter calls signal(signum, SIG_DFL)
, will only cause the handler to restart and call signal()
anyway.
This solution is an exception to rule SIG34-C. Do not call signal() from within interruptible signal handlers.
...
The POSIX sigaction()
function assigns handlers to signals in a manner similar to the C99 C signal()
function but also allows signal-handler persistence to be controlled via the SA_RESETHAND
flag. (Setting the flag makes the handler nonpersistent.)
Code Block | ||||
---|---|---|---|---|
| ||||
/* Equivalent to signal(SIGUSR1, handler) but makes
* signal nonpersistent */
struct sigaction act;
act.sa_handler = handler;
act.sa_flags = SA_RESETHAND;
if (sigemptyset(&act.sa_mask) != 0) {
/* Handle error */
}
if (sigaction(SIGUSR1, &act, NULL) != 0) {
/* Handle error */
}
|
...
Failure to understand implementation-specific details regarding signal-handler persistence can lead to unexpected behavior.
...
Tool | Version | Checker | Description | section|
---|---|---|---|---|
Compass/ROSE |
|
| Section | Could detect possible violations by flagging any signal handler that calls |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
...
CERT C++ Secure Coding Standard: SIG01-CPP. Understand implementation-specific details regarding signal handler persistence
ISO/IEC 9899-1999TR2 Section :2011 Section 7.14.1.1, "The signal
function"
...