Accessing or modifying shared objects in signal handlers can result in race conditions that can leave data in an inconsistent state. The exception two exceptions (C Standard, 5.1.2.3, paragraph 5) to this rule is are the ability to read from and write to lock-free atomic objects and variables of type volatile sig_atomic_t. Accessing any other type of object from a signal handler is undefined behavior. (See undefined behavior 131.)
The need for the volatile keyword is described in DCL34DCL22-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 is undefined. (See undefined behavior 125 of 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 −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 "Signals and Interrupts" section of the C99 Rationale [ISO/IEC C99 Rationale 2003], other than calling a limited, prescribed set of library functions,
The 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 staticsig_atomic_t 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 handlersmore information.)
Noncompliant Code Example
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 The err_msg variable is a character pointer and not a variable of type volatile sig_atomic_t.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <signal.h> #include <stdlib.h> #include <string.h> char *err_msg; enum { MAX_MSG_SIZE = 24 }; char *err_msg; 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; } |
Compliant Solution
...
(Writing volatile sig_atomic_t)
For maximum portabilityPortably, signal handlers can should only unconditionally get or set a flag variable of type volatile sig_atomic_t and return., as in this compliant solution:
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <signal.h>
#include <stdlib.h>
#include <string.h>
enum { MAX_MSG_SIZE = 24 };
volatile sig_atomic_t e_flag = 0;
void handler(int signum) {
e_flag = 1;
}
int main(void) {
char *err_msg = (char *)malloc(MAX_MSG_SIZE);
if (err_msg == NULL) {
/* Handle error condition */
}
signal(SIGINT, handler);
strcpy strcpy(err_msg, "No errors yet.");
/* Main code loop */
if (e_flag) {
strcpy(err_msg, "SIGINT received.");
}
return return 0;
}
|
Noncompliant Code Example (volatile with the Wrong Type)
Compliant Solution (Lock-Free Atomic Access)
Signal handlers can refer to objects with static or thread storage durations that are lock-free atomic objects, as in this compliant solution:The noncompliant code example below declares volatile an object with static storage duration that is accessed in the signal handler. However, since the type of the object is not sig_atomic_t, the behavior of the program is undefined. Note that the behavior of the program is undefined also because the handler for the SIGFPE signal returns. See undefined behavior 123 of Appendix J of the C standard.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <signal.h> extern double compute_value(); static volatile double value; /* bug: not declared sig_atomic_t */ void sigfpe_#include <stdlib.h> #include <string.h> #include <stdatomic.h> #ifdef __STDC_NO_ATOMICS__ #error "Atomics are not supported" #elif ATOMIC_INT_LOCK_FREE == 0 #error "int is never lock-free" #endif atomic_int e_flag = ATOMIC_VAR_INIT(0); void handler(int signum) { if (0.0 == value) /* bug: accessing non-sig_atomic_t object */ value = 1.0; /* bug: SIGFPE handler returns */ } int main(void) { signal(SIGFPE, sigfpe_handler); value = compute_value(); return 0; } e_flag = 1; } int main(void) { enum { MAX_MSG_SIZE = 24 }; char err_msg[MAX_MSG_SIZE]; #if ATOMIC_INT_LOCK_FREE == 1 if (!atomic_is_lock_free(&e_flag)) { return EXIT_FAILURE; } #endif if (signal(SIGINT, handler) == SIG_ERR) { return EXIT_FAILURE; } strcpy(err_msg, "No errors yet."); /* Main code loop */ if (e_flag) { strcpy(err_msg, "SIGINT received."); } return EXIT_SUCCESS; } |
Exceptions
SIG31-C-EX1: The C Standard, 7.14.1.1 paragraph 5 [ISO/IEC 9899:2024], makes a special exception for errno when a valid call to the signal() function results in a SIG_ERR return, allowing errno to take an indeterminate value. (See ERR32-C. Do not rely on indeterminate values of errno.)
the signal function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to the signal function results in a SIG_ERR return, the object designated by errno has an indeterminate representation.
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 [Zalewski 2001].
Rule | Severity | Likelihood | Detectable |
|---|
Repairable | Priority | Level |
|---|---|---|
SIG31-C |
High | Likely |
Yes |
No |
P18 |
L1 |
Automated Detection
Tool | Version | Checker | Description |
|---|
| Astrée |
| signal-handler-shared-access | Partially checked | ||||||
| Axivion Bauhaus Suite |
|
|
|
| Section |
|---|
87 D |
| Section |
|---|
Fully Implemented |
| CertC-SIG31 | |||||||||
| CodeSonar |
| CONCURRENCY.DATARACE | Data race |
| Compass/ROSE |
Can detect violations of this rule for single-file programs | |||||||||
| Cppcheck Premium | 24.9.0 | premium-cert-sig31-c | |||||||
| Helix QAC |
| C2029, C2030 C++3854, C++3855 | |||||||
| LDRA tool suite |
| 87 D | Fully implemented | ||||||
| Parasoft C/C++test |
| CERT_C-SIG31-a | Properly define signal handlers | ||||||
| PC-lint Plus |
| 2765 | Fully supported | ||||||
| CERT C: Rule SIG31-C | Checks for shared data access within signal handler (rule partially covered) | |||||||
| RuleChecker |
| signal-handler-shared-access | Partially checked |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
...
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| ISO/IEC |
...
| TS 17961:2013 | Accessing shared objects in signal handlers [accsig] |
...
MITRE CWE: CWE ID 662, "Insufficient Synchronization"
Bibliography
...
| Prior to 2018-01-12: CERT: Unspecified Relationship | ||
| CWE 2.11 | CWE-662, Improper Synchronization | 2017-07-10: CERT: Rule subset of CWE |
| CWE 2.11 | CWE-828, Signal Handler with Functionality that is not Asynchronous-Safe | 2017-10-30:MITRE:Unspecified Relationship 2018-10-19:CERT:Rule subset of CWE |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-662 and SIG31-C
CWE-662 = Union( SIG31-C, list) where list =
- Improper synchronization of shared objects between threads
- Improper synchronization of files between programs (enabling TOCTOU race conditions
CWE-828 and SIG31-C
CWE-828 = SIG31-C + non-async-safe things besides shared objects.
Bibliography
| [C99 Rationale 2003] | 5.2.3, "Signals and Interrupts" |
| [ |
...
| ISO/IEC 9899:2024] | Subclause 7.14.1.1, "The signal Function" |
| [Zalewski 2001] |
...