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 lock-free atomic objects or write to variables of volatile sig_atomic_t
. The need for the volatile
keyword is described in DCL22-C. Use volatile for data that cannot be cached. It is important to note that the behavior of a program that accesses an object of any other type from a signal handler, including reading from a volatile sig_atomic_t
is undefined. (See undefined behavior 131 in Appendix J of the C Standard.)
The type sig_atomic_t
is the integer type of an object that can be accessed as an atomic entity even in the presence of asynchronous interrupts. The type of sig_atomic_t
is implementation-defined, though it provides some guarantees. Integer values ranging from SIG_ATOMIC_MIN
through SIG_ATOMIC_MAX
, inclusive, may be safely stored to a variable of the type. In addition, when sig_atomic_t
is a signed integer type, SIG_ATOMIC_MIN
must be no greater than −127 and SIG_ATOMIC_MAX
no less than 127. Otherwise, SIG_ATOMIC_MIN
must be 0
and SIG_ATOMIC_MAX
must be no less than 255. The macros SIG_ATOMIC_MIN
and SIG_ATOMIC_MAX
are defined in the header <stdint.h>
.
According to the C99 Rationale [C99 Rationale 2003], 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 static
variable 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.)
Noncompliant Code Example (Non-atomic Access)
In this noncompliant code example, err_msg
is updated to indicate that the SIGINT
signal was delivered.
#include <signal.h> #include <stdlib.h> #include <string.h> char *err_msg; void handler(int signum) { strcpy(err_msg, "SIGINT encountered."); } int main(void) { 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 */ return 0; }
Noncompliant Code Example (volatile
with the Wrong Type)
This noncompliant code example declares volatile
an object with static storage duration that is accessed in the signal handler. However, because the type of the object is not sig_atomic_t
, the behavior of the program is undefined.
#include <signal.h> #include <stdlib.h> #include <string.h> volatile char *err_msg; void handler(int signum) { strcpy(err_msg, "SIGINT encountered."); } int main(void) { enum { MAX_MSG_SIZE = 24 }; signal(SIGINT, handler); err_msg = (volatile char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* Handle error condition. */ } strcpy(err_msg, "No errors yet."); /* Main code loop */ return 0; }
Noncompliant Code Example (Reading volatile sig_atomic_t
)
In this noncompliant code example, a variable of type volatile sig_atomic_t
is read from and then written into before returning from the signal handler. Strictly conforming code cannot read from a volatile sig_atomic_t
, only write into it.
#include <signal.h> #include <stdlib.h> #include <string.h> volatile sig_atomic_t e_flag = 0; void handler(int signum) { if (0 == e_flag) { e_flag = 1; } } int main(void) { enum { MAX_MSG_SIZE = 24 }; char *err_msg = (char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* Handle error condition */ } signal(SIGINT, handler); strcpy(err_msg, "No errors yet."); /* Main code loop */ if (e_flag) { strcpy(err_msg, "SIGINT received."); } return 0; }
Compliant Solution (Writing volatile sig_atomic_t
)
Portably, signal handlers can only unconditionally set a variable of type volatile sig_atomic_t
and return:
#include <signal.h> #include <stdlib.h> #include <string.h> volatile sig_atomic_t e_flag = 0; void handler(int signum) { e_flag = 1; } int main(void) { enum { MAX_MSG_SIZE = 24 }; char *err_msg = (char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* Handle error condition */ } signal(SIGINT, handler); strcpy(err_msg, "No errors yet."); /* Main code loop */ if (e_flag) { strcpy(err_msg, "SIGINT received."); } return 0; }
Compliant Solution (Lock-free Atomic Access)
Signal handlers can refer to objects with static or thread storage duration that are lock-free atomic objects.
#include <signal.h> #include <stdlib.h> #include <string.h> #include <stdatomic.h> atomic_int e_flag = ATOMIC_VAR_INIT(0); void handler(int signum) { int old_flag = atomic_load(&e_flag); if (old_flag != 0) { int new_flag; do { new_flag = 1; } while (!atomic_compare_exchange_weak(&e_flag, &old_flag, new_flag)); } } int main(void) { enum { MAX_MSG_SIZE = 24 }; char *err_msg = (char *)malloc(MAX_MSG_SIZE); if (err_msg == NULL) { /* Handle error condition */ } signal(SIGINT, handler); strcpy(err_msg, "No errors yet."); /* Main code loop */ if (e_flag) { strcpy(err_msg, "SIGINT received."); } return 0; }
Exceptions
SIG31-EX1: Due to the consensus formed at the April 2008 meeting of ISO/IEC WG14, it is acceptable to read a variable of type volatile sig_atomic_t
as well as write it. This exception transforms the "Reading volatile sig_atomic_t
" noncompliant code example into a compliant solution.
Risk Assessment
Accessing or modifying shared objects in signal handlers can result in accessing data in an inconsistent state. Michal Zalewski's paper "Delivering Signals for Fun and Profit" [Zalewski 2001] provides some examples of vulnerabilities that can result from violating this and other signal-handling rules.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SIG31-C | High | Likely | High | P9 | L2 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
|
| Can detect violations of this rule for single-file programs | |
9.7.1 | 87 D | Fully implemented |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT C++ Secure Coding Standard | SIG31-CPP. Do not access shared objects in signal handlers |
ISO/IEC TS 17961 | Accessing shared objects in signal handlers [accsig] |
MITRE CWE | CWE-662, Insufficient synchronization |
Bibliography
[C99 Rationale 2003] | Subclause 5.2.3, "Signals and Interrupts" |