VNX-828 – Signal Handler with Functionality that is not Asynchronous-Signal-Safe

Overview

This rule flags C signal handler functions (registered via signal() or sigaction()) that contain calls to functions that are not async-signal-safe: printf, fprintf, malloc, free, syslog, exit, and similar. POSIX defines a specific list of async-signal-safe functions; calling anything outside this list from a signal handler causes undefined behavior. This maps to CWE-828: Signal Handler with Functionality that is not Asynchronous-Signal-Safe.

Severity: Medium | CWE: CWE-828

Why This Matters

Signal handlers interrupt the main program at an arbitrary point. If the signal arrives while the main thread is inside malloc (holding the heap lock), and the signal handler also calls malloc, a deadlock occurs. Similarly, printf uses FILE stream locks that may be held, causing a deadlock or corrupted output. These bugs cause random crashes, deadlocks, or data corruption that are extremely difficult to reproduce and diagnose.

What Gets Flagged

// FLAGGED: printf in signal handler
void sigint_handler(int sig) {
    printf("Caught signal %d\n", sig);  // not async-signal-safe
    exit(1);  // not async-signal-safe
}
signal(SIGINT, sigint_handler);
// FLAGGED: malloc/free in signal handler
void sigterm_handler(int sig) {
    char *msg = malloc(128);  // not async-signal-safe
    sprintf(msg, "terminating");
    free(msg);
}
sigaction(SIGTERM, &sa, NULL);
// FLAGGED: syslog in signal handler
void sigusr1_handler(int sig) {
    syslog(LOG_INFO, "received SIGUSR1");  // not async-signal-safe
}

Remediation

// SAFE: use volatile sig_atomic_t flag and handle in main loop
#include <signal.h>
#include <stdatomic.h>

static volatile sig_atomic_t got_sigint = 0;
static volatile sig_atomic_t got_sigterm = 0;

void sigint_handler(int sig) {
    got_sigint = 1;  // async-signal-safe: simple assignment
}

void sigterm_handler(int sig) {
    got_sigterm = 1;
}

int main(void) {
    signal(SIGINT, sigint_handler);
    signal(SIGTERM, sigterm_handler);
    
    while (1) {
        if (got_sigint) {
            printf("Caught SIGINT\n");  // safe: in main, not handler
            got_sigint = 0;
            break;
        }
        if (got_sigterm) {
            printf("Caught SIGTERM, cleaning up\n");
            got_sigterm = 0;
            cleanup();
            break;
        }
        // ... normal work
    }
    return 0;
}
// SAFE: use async-signal-safe write() instead of printf
void sigpipe_handler(int sig) {
    const char msg[] = "SIGPIPE received\n";
    write(STDERR_FILENO, msg, sizeof(msg) - 1);  // write() is async-signal-safe
}

The only async-signal-safe operations in a signal handler are: setting a volatile sig_atomic_t flag, calling _exit() (not exit()), using write() with a pre-allocated buffer, and other functions explicitly listed in POSIX as async-signal-safe.

References