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 the event accordingly. The C Standard provides functions for sending and handling signals within a C program.
Processes handle signals by registering a signal handler using the signal()
function, which is specified as
Code Block |
---|
void (*signal(int sig, void (*func)(int)))(int);
|
This signal handler is conceptually equivalent to
Code Block |
---|
typedef void (*sighandler_t)(int signum);
extern sighandler_t signal(
int signum,
sighandler_t handler
);
|
Signal handlers can be interrupted by signals, including their own signals. If a signal is not reset before its handler is called, this means the handler can essentially 'interrupt itself'interrupt its own execution. A handler that always successfully executes its code despite interrupting itself is said to be re-entrantor being interrupted is async-signal-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, noninterruptible and need not be re-entrantasync-signal-safe. However, even when a signal is masked while its own handler is processed, the handler must still avoid invoking async-signal-safe unsafe functions because their execution may be (or have been) interrupted by another signal.
Vulnerabilities can arise if a non-re-entrant signal handler signal handler that is not async-signal-safe is interrupted with any unmasked signal, including its own signal, especially if it manipulates globally-accessible data.
This only applies to handlers for signals sent asynchronously (from outside the program). Synchronous signals do not cause race conditions.
Non-Compliant Coding Example
.
Noncompliant Code Example
This noncompliant code example This non-compliant program registers a single signal handler to process both SIGUSR1
and SIGUSR2
. The variable sig2
should be set to one 1
if one or more SIGUSR1
signals are followed by SIGUSR2
. This code , essentially implements implementing 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) { if (signal(SIGUSR1, handler); signal(SIGUSR2, handler); while (sig2 == 0SIG_ERR) { /* doHandle nothing or give up CPU for a while error */ } /* ... */ return 0; } |
The problem with this code is that 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 Coding Example (External finite state machine)
This example moves the finite state machine out of the signal handler, making the handler re-entrant.
Code Block | ||
---|---|---|
| ||
#include <signal.h> volatile sig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void handler(int signum) { if (signum == SIGUSR1if (signal(SIGUSR2, handler) == SIG_ERR) { /* sig1Handler =error 1;*/ } elsewhile if (signum == SIGUSR2) { sig2 = 1; } } int main(void) { int state = 0; signal(SIGUSR1, handler); signal(SIGUSR2, handler); while (state != 2) { /* doDo 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 Unfortunately, a race condition in this code where a SIGUSR2 sent immediately after a SIGUSR1 gets ignored. This is because the SIGUSR2 gets processed before the while loop sets the state to 1 and sig2 to 0, which erases the evidence of SIGUSR2. To completely 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 handleroccurs 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 defines the sigaction(2)
function , which assigns handlers to signals like in a similar manner to the C signal(2)
function, but it also allows one signal masks to be set explicitly set signal masks. One can thus use . Consequently, sigaction(2)
and 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) { /* handleHandle error */ } if (sigaddset( &act.sa_mask, SIGUSR1)) { /* handleHandle error */ } if (sigaddset( &act.sa_mask, SIGUSR2)) { /* handleHandle error */ } if (sigaction(SIGUSR1, &act, NULL) != 0) { /* handleHandle error */ } if (sigaction(SIGUSR2, &act, NULL) != 0) { /* handleHandle error */ } while (sig2 == 0) { /* doDo nothing or give up CPU for a while */ } /* ... */ return 0; } |
In fact, POSIX recommends sigaction(2)
and deprecates the use of signal(2)
to register signal handlers. Unfortunately, sigaction(2)
is not defined in the C Standard and is not C99-compliantconsequently not as portable a solution.
Risk Assessment
Depending on the code, this could lead to any number of attacks, many of which could give root access. For an overview of some software vulnerabilities, see Zalewski's signal articleInterrupting a noninterruptible signal handler can result in a variety of vulnerabilities [Zalewski 2001].
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SIG00- |
3 (high)
3 (likely)
C | High | Likely | High | P9 | L2 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| BADFUNC.SIGNAL | Use of signal | ||||||
Helix QAC |
| C5019 | |||||||
LDRA tool suite |
| 44 S | Enhanced enforcement | ||||||
Parasoft C/C++test |
| CERT_C-SIG00-a | The signal handling facilities of <signal.h> shall not be used | ||||||
PC-lint Plus |
| 586 | Assistance provided: reports use of the signal function |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
...
Related Guidelines
SEI CERT C++ Coding Standard | VOID SIG00-CPP. Mask signals handled by noninterruptible signal handlers |
MITRE CWE | CWE-662, Insufficient synchronization |
Bibliography
[C99 Rationale 2003] | Subclause |
...
5.2.3, |
...
"Signals |
...
and Interrupts" | |
[Dowd 2006] | Chapter 13, "Synchronization and State" ("Signal Interruption and Repetition") |
[IEEE Std 1003.1:2013] | XSH, System Interface, longjmp |
[OpenBSD] | signal() Man Page |
[Zalewski 2001] | "Delivering Signals for Fun and Profit" |
...
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\] [http://lcamtuf.coredump.cx/signals.txt] \[[Dowd 06 | AA. C References#Dowd 06]\] Chapter 13, "Synchronization and State" (Signal Interruption and Repetition)12. Signals (SIG) 12. Signals (SIG) SIG01-A. Understand implementation-specific details regarding signal handler persistence