Accessing or modifying shared objects in signal handlers can result in race conditions that can leave data in an inconsistent state. The exception to this rule is the ability to read and write to variables of volatile sig_atomic_t. The type of sig_atomic_t is implementation-defined, although there are bounding constraints. Only integer values from 0 through 127 can be assigned to a variable of type sig_atomic_t to be fully portable. The need for the volatile keyword is described in DCL34-C. Use volatile for data that cannot be cached.
According to the "Signals and Interrupts" section of the C99 Rationale \[[ISO/IEC 03|AA. C References#ISO/IEC 03]\], other than calling a limited, prescribed set of library functions, |
The C89 Committee concluded that about the only thing a strictly conforming program can do in a signal handler is to assign a value to a
volatile staticvariable which can be written uninterruptedly and promptly return.
However, this issue was discussed at the April 2008 meeting of ISO/IEC WG14, and it was agreed that there are no known implementations in which it would be an error to read a value from a volatile static variable, and the original intent of the committee was that both reading and writing variables of volatile sig_atomic_t would be strictly conforming.
The signal handler may also call a handful of functions, including abort(). (See SIG30-C. Call only asynchronous-safe functions within signal handlers for details of functions that can be safely called from within signal handlers.)
In this noncompliant code example, err_msg is updated to indicate that the SIGINT signal was delivered. Undefined behavior occurs if a SIGINT is generated before the allocation completes.
#include <signal.h>
#include <stdlib.h>
#include <string.h>
char *err_msg;
enum { MAX_MSG_SIZE = 24 };
void handler(int signum) {
strcpy(err_msg, "SIGINT encountered.");
}
int main(void) {
signal(SIGINT, handler);
err_msg = (char *)malloc(MAX_MSG_SIZE);
if (err_msg == NULL) {
/* Handle error condition */
}
strcpy(err_msg, "No errors yet.");
/* Main code loop */
return 0;
}
|
Portably, signal handlers can only unconditionally get or set a flag of type volatile sig_atomic_t and return.
#include <signal.h>
#include <stdlib.h>
#include <string.h>
volatile sig_atomic_t e_flag = 0;
volatile sig_atomic_t e_value = 1;
void handler(int signum) {
e_flag = e_value;
}
int main(void) {
char *err_msg;
enum { MAX_MSG_SIZE = 24 };
signal(SIGINT, handler);
err_msg = (char *)malloc(MAX_MSG_SIZE);
if (err_msg == NULL) {
/* Handle error condition */
}
strcpy(err_msg, "No errors yet.");
/* Main code loop */
if (e_flag) {
strcpy(err_msg, "SIGINT received.");
}
return 0;
}
|
Accessing or modifying shared objects in signal handlers can result in accessing data in an inconsistent state. Zalewski's paper "Delivering Signals for Fun and Profit" provides some examples of vulnerabilities that can result from violating this and other signal-handling rules \[[Zalewski 01|AA. C References#Zalewski 01]\]. |
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
|---|---|---|---|---|---|
SIG31-C |
high |
likely |
high |
P9 |
L2 |
Compass/ROSE can detect violations of this rule for single-file programs.
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
This rule appears in the C++ Secure Coding Standard as SIG31-CPP. Do not access or modify shared objects in signal handlers.
\[[Dowd 06|AA. C References#Dowd 06]\] Chapter 13, Synchronization and State
\[[ISO/IEC 03|AA. C References#ISO/IEC 03]\] "Signals and Interrupts"
\[[MITRE 07|AA. C References#MITRE 07]\] [CWE ID 662|http://cwe.mitre.org/data/definitions/662.html], "Insufficient Synchronization"
\[[Open Group 04|AA. C References#Open Group 04]\] [longjmp|http://www.opengroup.org/onlinepubs/000095399/functions/longjmp.html]
\[[OpenBSD|AA. C References#OpenBSD]\] [{{signal()}} Man Page|http://www.openbsd.org/cgi-bin/man.cgi?query=signal]
\[[Zalewski 01|AA. C References#Zalewski 01]\] |
11. Signals (SIG) SIG32-C. Do not call longjmp() from inside a signal handler