Dangerous Signal Handler not Disabled During Sensitive Operations
Description
Dangerous Signal Handler not Disabled During Sensitive Operations is a vulnerability where a product uses a signal handler that shares state with other signal handlers or the main program, but the product does not properly mask or prevent those signal handlers from being invoked while the original handler is still running. When a signal handler executes, another handler can interrupt it upon receiving a different signal. If both handlers access shared state like global variables, an attacker can corrupt this state by triggering a second signal before the first handler completes.
Risk
Unmasked signal handlers during sensitive operations create race conditions that enable data corruption and security bypasses. Attackers can trigger specific signals to interrupt handlers at critical moments, corrupting shared state or causing undefined behavior. In security-critical code, interrupted authentication or authorization handlers may leave systems in permissive states. Memory corruption through signal handler races can lead to arbitrary code execution. The asynchronous nature of signals makes these vulnerabilities difficult to detect through testing but reliably exploitable in production.
Solution
Turn off dangerous handlers when performing sensitive operations using signal masking functions (sigprocmask, pthread_sigmask). Use sigaction() with sa_mask to automatically block signals during handler execution. Avoid accessing shared state in signal handlers—use sig_atomic_t for simple flags if necessary. Consider using signalfd() or self-pipe tricks to handle signals synchronously. Design signal handlers to be as minimal as possible, deferring work to the main program. Use volatile sig_atomic_t for flags set by handlers and read by the main program.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Shared state corruption between handlers enables data modification. Race conditions in signal handlers can corrupt critical data structures. |
Example Code
Vulnerable Code
// Vulnerable: Signal handlers share state without masking
#include <signal.h>
#include <stdio.h>
volatile int counter = 0;
volatile int in_handler = 0;
// Vulnerable: Handlers can interrupt each other
void handler_sigusr1(int sig) {
in_handler = 1;
// Vulnerable: Another signal can interrupt here
counter++;
printf("SIGUSR1: counter = %d\n", counter);
// Vulnerable: Shared state may be corrupted
sleep(1); // Simulates work - VERY dangerous in signal handler
in_handler = 0;
}
void handler_sigusr2(int sig) {
// Vulnerable: Can execute while SIGUSR1 handler is running
in_handler = 1;
counter--; // Race condition with SIGUSR1 handler
printf("SIGUSR2: counter = %d\n", counter);
in_handler = 0;
}
int main() {
// Vulnerable: Simple signal() doesn't mask other signals
signal(SIGUSR1, handler_sigusr1);
signal(SIGUSR2, handler_sigusr2);
while(1) {
pause();
}
return 0;
}
// Attacker can send rapid SIGUSR1 and SIGUSR2 to corrupt counter
// Vulnerable: Sensitive operation interrupted by signal
#include <signal.h>
#include <string.h>
char password_buffer[256];
volatile int auth_in_progress = 0;
void cleanup_handler(int sig) {
// Vulnerable: May interrupt password validation
// Attempts to "clean up" but corrupts state
memset(password_buffer, 0, sizeof(password_buffer));
auth_in_progress = 0;
}
int validate_password(const char *provided, const char *expected) {
auth_in_progress = 1;
// Vulnerable: Signal can interrupt between these operations
strncpy(password_buffer, provided, sizeof(password_buffer) - 1);
// If SIGTERM arrives here, buffer is cleared but auth_in_progress
// may not reflect the actual state
int result = strcmp(password_buffer, expected) == 0;
auth_in_progress = 0;
return result;
}
int main() {
signal(SIGTERM, cleanup_handler); // Vulnerable: No masking
// ...
}
Fixed Code
// Fixed: Properly mask signals during handler execution
#include <signal.h>
#include <stdio.h>
volatile sig_atomic_t counter = 0; // Fixed: Use sig_atomic_t
void handler_sigusr1(int sig) {
// Fixed: Other signals masked via sa_mask (set below)
counter++;
}
void handler_sigusr2(int sig) {
counter--;
}
int main() {
struct sigaction sa1, sa2;
// Fixed: Configure SIGUSR1 handler with signal masking
sa1.sa_handler = handler_sigusr1;
sigemptyset(&sa1.sa_mask);
sigaddset(&sa1.sa_mask, SIGUSR2); // Fixed: Block SIGUSR2 during SIGUSR1
sa1.sa_flags = 0;
sigaction(SIGUSR1, &sa1, NULL);
// Fixed: Configure SIGUSR2 handler with signal masking
sa2.sa_handler = handler_sigusr2;
sigemptyset(&sa2.sa_mask);
sigaddset(&sa2.sa_mask, SIGUSR1); // Fixed: Block SIGUSR1 during SIGUSR2
sa2.sa_flags = 0;
sigaction(SIGUSR2, &sa2, NULL);
while(1) {
pause();
printf("counter = %d\n", counter);
}
return 0;
}
// Fixed: Mask signals during sensitive operations
#include <signal.h>
#include <string.h>
#include <pthread.h>
// Fixed: Use thread-local storage for sensitive data
static __thread char password_buffer[256];
volatile sig_atomic_t cleanup_requested = 0;
void cleanup_handler(int sig) {
// Fixed: Just set a flag, don't do actual cleanup
cleanup_requested = 1;
}
int validate_password(const char *provided, const char *expected) {
sigset_t block_set, old_set;
int result;
// Fixed: Block signals during sensitive operation
sigemptyset(&block_set);
sigaddset(&block_set, SIGTERM);
sigaddset(&block_set, SIGINT);
sigaddset(&block_set, SIGHUP);
// Fixed: Mask signals before sensitive operation
pthread_sigmask(SIG_BLOCK, &block_set, &old_set);
// Now safe to perform password validation
strncpy(password_buffer, provided, sizeof(password_buffer) - 1);
password_buffer[sizeof(password_buffer) - 1] = '\0';
result = strcmp(password_buffer, expected) == 0;
// Fixed: Securely clear password
explicit_bzero(password_buffer, sizeof(password_buffer));
// Fixed: Restore original signal mask
pthread_sigmask(SIG_SETMASK, &old_set, NULL);
// Fixed: Check if cleanup was requested during operation
if (cleanup_requested) {
// Handle deferred cleanup
perform_cleanup();
}
return result;
}
int main() {
struct sigaction sa;
sa.sa_handler = cleanup_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
// ...
}
// Fixed: Use self-pipe trick for synchronous signal handling
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
static int signal_pipe[2];
void signal_handler(int sig) {
// Fixed: Minimal handler - just write to pipe
int saved_errno = errno;
write(signal_pipe[1], &sig, sizeof(sig));
errno = saved_errno;
}
void setup_signal_pipe() {
pipe(signal_pipe);
// Make write end non-blocking
fcntl(signal_pipe[1], F_SETFL, O_NONBLOCK);
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
void process_signals() {
// Fixed: Handle signals synchronously in main loop
struct pollfd pfd = {signal_pipe[0], POLLIN, 0};
while (poll(&pfd, 1, 0) > 0) {
int sig;
if (read(signal_pipe[0], &sig, sizeof(sig)) == sizeof(sig)) {
// Fixed: Process signal synchronously
switch (sig) {
case SIGUSR1:
handle_usr1_safely();
break;
case SIGUSR2:
handle_usr2_safely();
break;
case SIGTERM:
handle_term_safely();
break;
}
}
}
}
CVE Examples
No specific CVEs are listed for this CWE in the MITRE database. The vulnerability pattern is documented in:
- CERT C Secure Coding Standard: SIG00-C — Mask signals handled by noninterruptible signal handlers
References
- MITRE Corporation. "CWE-432: Dangerous Signal Handler not Disabled During Sensitive Operations." https://cwe.mitre.org/data/definitions/432.html
- CERT C Coding Standard. "SIG00-C. Mask signals handled by noninterruptible signal handlers." https://wiki.sei.cmu.edu/confluence/display/c/SIG00-C.+Mask+signals+handled+by+noninterruptible+signal+handlers