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. 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 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 noninterruptible and need not be async-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 signal handler that is not async-signal-safe 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 1
if one or more SIGUSR1
signals are followed by SIGUSR2
, essentially 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) == SIG_ERR) {
/* Handle error */
}
if (signal(SIGUSR2, handler) == SIG_ERR) {
/* Handler error */
}
while (sig2 == 0) {
/* Do nothing or give up CPU for a while */
}
/* ... |
No direct issues come from this, but you need to be careful that something doesn't generate two similar signals that call the same handler, and your code to deal with it get executed twice.
According to the "Signals and Interrupts" section of the C99 Rationale:
When a signal occurs, the normal flow of control of a program is interrupted. If a signal occurs that is being trapped by a signal handler, that handler is invoked. When it is finished, execution continues at the point at which the signal occurred. This arrangement could cause problems if the signal handler invokes a library function that was being executed at the time of the signal. Since library functions are not guaranteed to be reentrant, they should not be called from a signal handler that returns.
And according to the OpenBSD signal()
man page
The following functions are either reentrant or not interruptible by sig-
nals and are asyncronous-signal safe. Therefore applications may invoke
them, without restriction, from signal-catching functions:Base Interfaces:
_exit(), access(), alarm(), cfgetispeed(), cfgetospeed(), cfsetispeed(),
cfsetospeed(), chdir(), chmod(), chown(), close(), creat(), dup(),
dup2(), execle(), execve(), fcntl(), fork(), fpathconf(), fstat(),
fsync(), getegid(), geteuid(), getgid(), getgroups(), getpgrp(),
getpid(), getppid(), getuid(), kill(), link(), lseek(), mkdir(),
mkfifo(), open(), pathconf(), pause(), pipe(), raise(), read(), rename(),
rmdir(), setgid(), setpgid(), setsid(), setuid(), sigaction(),
sigaddset(), sigdelset(), sigemptyset(), sigfillset(), sigismember(),
signal(), sigpending(), sigprocmask(), sigsuspend(), sleep(), stat(),
sysconf(), tcdrain(), tcflow(), tcflush(), tcgetattr(), tcgetpgrp(),
tcsendbreak(), tcsetattr(), tcsetpgrp(), time(), times(), umask(),
uname(), unlink(), utime(), wait(), waitpid(), write().
Non-Compliant Coding Example
Code Block | ||
---|---|---|
| ||
#include <signal.h>
char *global_ptr;
void handler() {
free(global_ptr);
_exit(0);
}
int main() {
global_ptr = malloc(16);
signal(SIGINT, handler);
signal(SIGTERM, handler);
/* program code */
return 0;
}
|
Unfortunately, a race condition occurs 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 C signal()
function, but it also allows signal masks to be set explicitly. Consequently, sigaction()
can be used to prevent a signal handler from interrupting itselfSignal handlers should be as minimal as possible, only unconditionally setting a flag where appropriate, and returning.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <setjmp<signal.h> #include <signal<stdio.h> staticvolatile jmp_buf env; int interruptedsig_atomic_t sig1 = 0; volatile sig_atomic_t sig2 = 0; void int_handler(int signum) { if (signum == SIGUSR1) { interruptedsig1 = 1; } else if (sig1) { sig2 = 1; } } int main(void) { struct sigaction act; act.sa_handler = &handler; act.sa_flags char *foo; signal(SIGINT, int_handler); foo = malloc(15); foo = "Nothing yet."; /* main loop which displays foo */ if(interrupt == 1) { foo = "Signal caught.";= 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; } |
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 article.
POSIX recommends sigaction()
and deprecates the use of signal()
to register signal handlers. Unfortunately, sigaction()
is not defined in the C Standard and is consequently not as portable a solution.
Risk Assessment
Interrupting a noninterruptible signal handler can result in a variety of vulnerabilities [Zalewski 2001].
Recommendation |
---|
Severity | Likelihood | Remediation Cost | Priority | Level |
---|
SIG00-C |
3 (high)
3 (likely)
High | Likely | High | P9 | L2 |
References
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.
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" |
...
\[[ISO/IEC 03|AA. C References#ISO/IEC 03]\] "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\] [http://lcamtuf.coredump.cx/signals.txt] Wiki Markup