With one exception, accessing or modifying shared objects in signal handlers can lead to race conditions, opening up security holes.
According to the "Signals and Interrupts" section of the C99 Rationale, 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.
The C99 standard dictates the use of volatile sig_atomic_t
. The type of sig_atomic_t
is implementation-defined, although there are bounding constraints. Only assign integer values from 0
through 127
to a variable of type sig_atomic_t
to be fully portable.
The signal handler may also call the abort()
function, the _Exit()
, function, or the signal()
function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. This may be necessary to ensure that the handler persists (see SIG01-A. Understand implementation-specific details regarding signal handler persistence).
Non-Compliant Code Example
In this non-compliant code example, err_msg
is updated to indicate that the SIGINT
signal that was delivered. Undefined behavior will occur 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; }
Compliant Solution
Portably, signal handlers can only unconditionally 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; void handler(int signum) { e_flag = 1; } 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; }
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 06]].
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
SIG31-C |
3 (high) |
3 (likely) |
1 (high) |
P9 |
L2 |
Automated Detection
The tool Compass Rose can detect violations of the recommendation for single-file programs.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[Dowd 06]] Chapter 13, Synchronization and State
[[ISO/IEC 03]] "Signals and Interrupts"
[[Open Group 04]] longjmp
[OpenBSD] signal()
Man Page
[Zalewski] http://lcamtuf.coredump.cx/signals.txt
12. Signals (SIG) SIG32-C. Do not call longjmp() from inside a signal handler