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

ImpactDetails
IntegrityScope: 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

  1. MITRE Corporation. "CWE-432: Dangerous Signal Handler not Disabled During Sensitive Operations." https://cwe.mitre.org/data/definitions/432.html
  2. 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