...
In general, I/O functions are not safe to invoke inside signal handlers. Check your system's asynchronous-safe functions before using them in signal handlers.
Noncompliant Code Example
In this noncompliant code example, the program allocates a string on the heap and uses it to log messages in a loop. The program also registers the signal handler int_handler()
to handle the terminal interrupt signal SIGINT
. The int_handler()
function logs the last message, calls free()
, and exits.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
enum { MAXLINE = 1024 };
char *info = NULL;
void log_message(void) {
fprintf(stderr, info); /* violation */
}
void handler(int signum) {
log_message();
free(info); /* violation */
info = NULL;
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
/* Handle error */
}
info = (char*)malloc(MAXLINE);
if (info == NULL) {
/* Handle Error */
}
while (1) {
/* Main loop program code */
log_message();
/* More program code */
}
return 0;
}
|
This program's signal handler has four problems. The first is that it is unsafe to call the fprintf()
function from within a signal handler because the handler may be called when global data (such as stderr
) is in an inconsistent state. In general, it is not safe to invoke I/O functions within a signal handler.
The second problem is that the free()
function is also not asynchronous-safe, and its invocation from within a signal handler is also a violation of this rule. If an interrupt signal is received during the free()
call in handler()
, the heap may be corrupted.
The third problem is that if SIGINT
occurs after the call to free()
, the memory referenced by info
is freed twice. This is a violation of MEM31-C. Free dynamically allocated memory exactly once and SIG31-C. Do not access shared objects in signal handlers.
The fourth problem is that the signal handler reads the variable info
, which is not declared to be of type volatile sig_atomic_t
. This is a violation of SIG31-C. Do not access shared objects in signal handlers.
Furthermore, there are problems in the main()
function as well, such as the possibility that the signal handler might get invoked during the call to malloc()
in main()
.
Noncompliant Code Example
Invoking the longjmp()
function from within a signal handler can lead to undefined behavior if it results in the invocation of any non-asynchronous-safe functions, likely compromising the integrity of the program. Consequently, neither longjmp()
nor the POSIX siglongjmp()
should ever be called from within a signal handler.
This noncompliant code example is similar to a vulnerability in an old version of Sendmail [VU #834865]. The intent is to execute code in a main()
loop, which also logs some data. Upon receiving a SIGINT
, the program transfers out of the loop, logs the error, and terminates.
However, an attacker can exploit this noncompliant code example by generating a SIGINT
just before the second if
statement in log_message()
. This results in longjmp()
transferring control back to main()
, where log_message()
is called again. However, the first if
statement would not be executed this time (because buf
is not set to NULL
as a result of the interrupt), and the program would write to the invalid memory location referenced by buf0
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
enum { MAXLINE = 1024 };
static jmp_buf env;
void handler(int signum) {
longjmp(env, 1); /* violation */
}
void log_message(char *info1, char *info2) {
static char *buf = NULL;
static size_t bufsize;
char buf0[MAXLINE];
if (buf == NULL) {
buf = buf0;
bufsize = sizeof(buf0);
}
/*
* Try to fit a message into buf, else re-allocate
* it on the heap and then log the message.
*/
/*** VULNERABILITY IF SIGINT RAISED HERE ***/
if (buf == buf0) {
buf = NULL;
}
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
/* Handle error */
}
char *info1;
char *info2;
/* info1 and info2 are set by user input here */
if (setjmp(env) == 0) {
while (1) {
/* Main loop program code */
log_message(info1, info2);
/* More program code */
}
}
else {
log_message(info1, info2);
}
return 0;
}
|
Compliant Solution
In this compliant solution, the call to longjmp()
is removed; the signal handler sets an error flag of type volatile sig_atomic_t
instead.
Implementation Details
POSIX
The following table from the the Open Group Base Specifications [Open Group 2004] defines a set of functions that are asynchronous-signal-safe. Applications may invoke these functions, without restriction, from a signal handler.
Asynchronous-Signal-Safe Functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
All functions not listed in this table are considered to be unsafe with respect to signals. In the presence of signals, all functions defined by IEEE standard 1003.1-2001 behave as defined when called from or interrupted by a signal handler, with a single exception: when a signal interrupts an unsafe function and the signal handler calls an unsafe function, the behavior is undefined.
Note that although raise()
is on the list of asynchronous-safe functions, it should not be called within a signal handler if the signal occurs as a result of abort() or raise()
function.
Section 7.14.1.1, para. 4, of the C standard [ISO/IEC 9899:2011] states:
If the signal occurs as the result of calling the
abort
orraise
function, the signal handler shall not call theraise
function.
(See also undefined behavior 131 of Annex J.)
OpenBSD
The OpenBSD signal()
man page identifies functions that are asynchronous-signal safe. Applications may consequently invoke them, without restriction, from a signal handler.
The OpenBSD signal()
manual page lists a few additional functions that are asynchronous-safe in OpenBSD but "probably not on other systems," including snprintf()
, vsnprintf()
,and syslog_r()
( but only when the syslog_data struct
is initialized as a local variable).
Noncompliant Code Example
In this noncompliant example, the C Standard Library function fprintf()
is called from the signal handler handler via the function log_message. The function free()
is also not asynchronous-safe, and its invocation from within a signal handler is also a violation of this rule.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
enum { MAXLINE = 1024 };
char *info = NULL;
void log_message(void) {
fprintf(stderr, info); /* violation */
}
void handler(int signum) {
log_message();
free(info); /* violation */
info = NULL;
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
/* Handle error */
}
info = (char*)malloc(MAXLINE);
if (info == NULL) {
/* Handle Error */
}
while (1 | ||||
Code Block | ||||
| ||||
#include <signal.h> #include <stdlib.h> enum { MAXLINE = 1024 }; volatile sig_atomic_t eflag = 0; void handler(int signum) { eflag = 1; } void log_message(char *info1, char *info2) { static char *buf = NULL; static size_t bufsize; char buf0[MAXLINE]; if (buf == NULL) { buf = buf0; bufsize = sizeof(buf0); } /* * Try to fit a message into buf, else re-allocate * it on the heap and then log the message. */ if (buf == buf0) { buf = NULL; } } int main(void) { if (signal(SIGINT, handler) == SIG_ERR) { /* Handle errorMain loop program code */ } char *info1log_message(); char *info2; /* info1More andprogram info2code are*/ set by} user input here */ while (!eflag) { /* Main loop program code */ log_message(info1, info2); /* More program code */ } log_message(info1, info2); return 0; } |
Noncompliant Code Example
In this noncompliant code example, the int_handler()
function is used to carry out SIGINT
-specific tasks and then raises a SIGTERM
. However, there is a nested call to the raise()
function, which results in undefined behavior.
return 0;
}
|
Compliant Solution
Signal handlers should be as concise as possible, ideally, unconditionally setting a flag and returning. They may also call the _Exit()
function. Finally, they may call other functions provided that all implementations to which the code is ported guarantee that these functions are asynchronous-safe.
This example code achieves compliance with this rule by moving the final log_message()
and call to free()
outside the signal handler.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
enum { MAXLINE = 1024 };
volatile sig_atomic_t eflag = 0;
char *info = NULL;
void log_message(void) {
fprintf(stderr, info);
}
void | ||||
Code Block | ||||
| ||||
void term_handler(int signum) { /*eflag SIGTERM handling specific */= 1; } void int_handler(int signummain(void) { /* SIGINT handling specific */ if (raise(SIGTERMsignal(SIGINT, handler) !== 0SIG_ERR) { /* violation */ /* Handle error */ } } int main(void) { info = (char*)malloc(MAXLINE); if (signal(SIGTERM, term_handler)info == SIG_ERRNULL) { /* Handle error */ } if (signal(SIGINT, int_handler) == SIG_ERR) {while (!eflag) { /* Main loop program code */ log_message(); /* HandleMore program errorcode *// } log_message(); }free(info); info /* Program code */ if (raise(SIGINT) != 0) { /* Handle error */ } /* More code */ return EXIT_SUCCESS; } |
Compliant Solution
In this compliant solution, the call to the raise()
function inside handler()
has been replaced by a direct call to log_msg()
.
= NULL;
return 0;
}
|
Noncompliant Code Example
Invoking the longjmp()
function from within a signal handler can lead to undefined behavior if it results in the invocation of any non-asynchronous-safe functions, likely compromising the integrity of the program. Consequently, neither longjmp()
nor the POSIX siglongjmp()
should ever be called from within a signal handler.
This noncompliant code example is similar to a vulnerability in an old version of Sendmail [VU #834865]. The intent is to execute code in a main()
loop, which also logs some data. Upon receiving a SIGINT
, the program transfers out of the loop, logs the error, and terminates.
However, an attacker can exploit this noncompliant code example by generating a SIGINT
just before the second if
statement in log_message()
. This results in longjmp()
transferring control back to main()
, where log_message()
is called again. However, the first if
statement would not be executed this time (because buf
is not set to NULL
as a result of the interrupt), and the program would write to the invalid memory location referenced by buf0
.
Code Block | ||||
---|---|---|---|---|
| ||||
Code Block | ||||
| ||||
#include <setjmp.h> #include <signal.h> void log_msg(int signum) { /* Log error message in some asynchronous-safe manner */ } #include <stdlib.h> enum { MAXLINE = 1024 }; static jmp_buf env; void handler(int signum) { longjmp(env, 1); /* Do some handling specific to SIGINT violation */ log_msg(SIGUSR1); } intvoid main(void) { if (signal(SIGUSR1, log_msg) == SIG_ERRlog_message(char *info1, char *info2) { static char /* Handle error */ }*buf = NULL; static size_t bufsize; char buf0[MAXLINE]; if (signal(SIGINT, handler)buf == SIG_ERRNULL) { /* handle error */buf = buf0; bufsize = sizeof(buf0); } /* program code */ if (raise(SIGINT) != 0) { /* Handle error */ } /* More code */ return 0; } |
Compliant Solution (POSIX)
If a signal handler is assigned using the POSIX sigaction()
function, the signal handler may safely call raise()
.
The POSIX standard is contradictory regarding raise()
in signal handlers. The POSIX standard [Open Group 2004] prohibits signal handlers installed using signal()
from calling the raise()
function if the signal occurs as the result of calling the raise()
, kill()
, pthread_kill()
, or sigqueue()
functions. However, it also requires that the raise()
function may be safely called within any signal handler. Consequently, it is not clear whether it is safe for POSIX applications to call raise()
in signal handlers installed using signal()
, but it is safe to call raise()
in signal handlers installed using sigaction()
.
Try to fit a message into buf, else re-allocate
* it on the heap and then log the message.
*/
/*** VULNERABILITY IF SIGINT RAISED HERE ***/
if (buf == buf0) {
buf = NULL;
}
}
int main(void) {
if (signal(SIGINT, handler) == SIG_ERR) {
/* Handle error */
}
char *info1;
char *info2;
/* info1 and info2 are set by user input here */
if (setjmp(env) == 0) {
while (1) {
/* Main loop program code */
log_message(info1, info2);
/* More program code */
}
}
else {
log_message(info1, info2);
}
return 0;
}
|
Compliant Solution
In this compliant solution, the call to longjmp()
is removed; the signal handler sets an error flag of type volatile sig_atomic_t
instead.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
#include <stdlib.h>
enum { MAXLINE = 1024 };
volatile sig_atomic_t eflag = 0;
void handler(int signum) {
eflag = 1;
}
void log_message(char *info1, char *info2) {
static char *buf = NULL;
static size_t bufsize;
char buf0[MAXLINE];
if (buf == NULL) {
buf = buf0;
bufsize = sizeof(buf0); | ||||
Code Block | ||||
| ||||
#include <signal.h> void log_msg(int signum) { /* Log error message in some asynchronous-safe manner */ } void handler(int signum) { /* Do some handling specific to SIGINT */ if (raise(SIGUSR1) != 0) { /* Handle error */ } } int main(void) { struct sigaction act; act.sa_flags = 0; if (sigemptyset(&act.sa_mask) != 0) { /* Handle error */ } act.sa_handler = log_msg; if (sigaction(SIGUSR1, &act, NULL) != 0) { /* Handle error */ } act.sa_handler = handler; if (sigaction(SIGINT, &act, NULL) != 0) { /* Handle error */ } /* program code */ if (raise(SIGINT) != 0) { /* Handle error */ } /* More code */ return 0; } |
POSIX recommends sigaction()
and deprecates signal()
. Unfortunately, sigaction()
is not defined in the C standard and is consequently not as portable a solution.
Compliant Solution
Signal handlers should be as concise as possible, ideally, unconditionally setting a flag and returning. They may also call the _Exit()
function. Finally, they may call other functions provided that all implementations to which the code is ported guarantee that these functions are asynchronous-safe.
This example code achieves compliance with this rule by moving the final log message and call to free()
outside the signal handler.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h> #include <stdio.h> #include <stdlib.h> enum { MAXLINE = 1024 }; volatile sig_atomic_t eflag = 0; char *info = NULL; void log_message(void) { fprintf(stderr, info); } void handler(int signum) { eflag = 1; } int main(void) { if (signal(SIGINT, handler) == SIG_ERR) {Try to fit a message into buf, else re-allocate * it on the heap and then log the message. */ if (buf == buf0) { buf = NULL; } } int main(void) { if (signal(SIGINT, handler) == SIG_ERR) { /* Handle error */ } char *info1; char *info2; /* info1 and info2 are set by user input here */ while (!eflag) { /* Main loop program code */ log_message(info1, info2); /* HandleMore program errorcode */ } info = (char*)malloc(MAXLINE); if (info == NULL log_message(info1, info2); return 0; } |
Noncompliant Code Example
In this noncompliant code example, the int_handler()
function is used to carry out SIGINT
-specific tasks and then raises a SIGTERM
. However, there is a nested call to the raise()
function, which results in undefined behavior.
Code Block | ||||
---|---|---|---|---|
| ||||
void term_handler(int signum) { /* SIGTERM Handlehandling errorspecific */ } while (!eflagvoid int_handler(int signum) { /* MainSIGINT loophandling program codespecific */ log_message(); if (raise(SIGTERM) != 0) { /* Moreviolation program code */ } log_message(); free(info); info = NULL; return 0; } |
Implementation Details
POSIX
The following table from the the Open Group Base Specifications [Open Group 2004] defines a set of functions that are asynchronous-signal-safe. Applications may invoke these functions, without restriction, from a signal handler.
Asynchronous-Signal-Safe Functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
All functions not listed in this table are considered to be unsafe with respect to signals. In the presence of signals, all functions defined by IEEE standard 1003.1-2001 behave as defined when called from or interrupted by a signal handler, with a single exception: when a signal interrupts an unsafe function and the signal handler calls an unsafe function, the behavior is undefined.
Note that although raise()
is on the list of asynchronous-safe functions, it should not be called within a signal handler if the signal occurs as a result of abort() or raise()
function.
Section 7.14.1.1, para. 4, of the C standard [ISO/IEC 9899:2011] states:
If the signal occurs as the result of calling the
abort
orraise
function, the signal handler shall not call theraise
function.
(See also undefined behavior 131 of Annex J.)
OpenBSD
The OpenBSD signal()
man page identifies functions that are asynchronous-signal safe. Applications may consequently invoke them, without restriction, from a signal handler.
/* Handle error */
}
}
int main(void) {
if (signal(SIGTERM, term_handler) == SIG_ERR) {
/* Handle error */
}
if (signal(SIGINT, int_handler) == SIG_ERR) {
/* Handle error */
}
/* Program code */
if (raise(SIGINT) != 0) {
/* Handle error */
}
/* More code */
return EXIT_SUCCESS;
}
|
Compliant Solution
In this compliant solution, the call to the raise()
function inside handler()
has been replaced by a direct call to log_msg()
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
void log_msg(int signum) {
/* Log error message in some asynchronous-safe manner */
}
void handler(int signum) {
/* Do some handling specific to SIGINT */
log_msg(SIGUSR1);
}
int main(void) {
if (signal(SIGUSR1, log_msg) == SIG_ERR) {
/* Handle error */
}
if (signal(SIGINT, handler) == SIG_ERR) {
/* handle error */
}
/* program code */
if (raise(SIGINT) != 0) {
/* Handle error */
}
/* More code */
return 0;
}
|
Noncompliant Solution (POSIX)
The POSIX standard is contradictory regarding raise()
in signal handlers. The POSIX standard [Open Group 2004] prohibits signal handlers installed using signal()
from calling the raise()
function if the signal occurs as the result of calling the raise()
, kill()
, pthread_kill()
, or sigqueue()
functions. However, it also requires that the raise()
function may be safely called within any signal handler. Consequently, it is not clear whether it is safe for POSIX applications to call raise()
in signal handlers installed using signal()
, but it is safe to call raise()
in signal handlers installed using sigaction()
.
In this non-compliant soultion, the signal handlers are installed using signal()
and raise()
is called inside the signal handler.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
void log_msg(int signum) {
/* Log error message */
}
void handler(int signum) {
/* Do some handling specific to SIGINT */
if (raise(SIGUSR1) != 0) { /* violation */
/* Handle error */
}
}
int main(void) {
signal(SIGUSR1, log_msg);
signal(SIGINT, handler);
/* program code */
if (raise(SIGINT) != 0) {
/* Handle error */
}
/* More code */
return 0;
}
|
Compliant Solution (POSIX)
In this compliant solution, the signal handlers are installed using sigaction()
and so it is safe to use raise()
within the signal handler.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h>
void log_msg(int signum) {
/* Log error message in some asynchronous-safe manner */
}
void handler(int signum) {
/* Do some handling specific to SIGINT */
if (raise(SIGUSR1) != 0) {
/* Handle error */
}
}
int main(void) {
struct sigaction act;
act.sa_flags = 0;
if (sigemptyset(&act.sa_mask) != 0) {
/* Handle error */
}
act.sa_handler = log_msg;
if (sigaction(SIGUSR1, &act, NULL) != 0) {
/* Handle error */
}
act.sa_handler = handler;
if (sigaction(SIGINT, &act, NULL) != 0) {
/* Handle error */
}
/* program code */
if (raise(SIGINT) != 0) {
/* Handle error */
}
/* More code */
return 0;
}
|
POSIX recommends sigaction()
and deprecates signal()
. Unfortunately, sigaction()
is not defined in the C standard and is consequently not as portable a solutionThe OpenBSD signal()
manual page lists a few additional functions that are asynchronous-safe in OpenBSD but "probably not on other systems," including snprintf()
, vsnprintf()
, and syslog_r()
(but only when the syslog_data struct
is initialized as a local variable).
Risk Assessment
Invoking functions that are not asynchronous-safe from within a signal handler may result in privilege escalation and other attacks.
...