Skip to content

VNX-831: Signal Handler Function Associated with Multiple Signals

Reports the registration lines that install one C or C++ signal handler for two or more signals when the handler does non-reentrant work (unsafe calls, directly or through a same-file helper) and no sa_mask blocks the other signals while it runs.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-831
Severity Medium
SARIF level warning
Kind sast
Languages C, C++
CWE CWE-831
CAPEC CAPEC-97
ATT&CK T1557
CWSS vector CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags signal-handler reentrancy cwe-831 c cpp
Source internal/sast/rules/vnx-831.rego

Overview

This rule reports the registration lines that install one handler for two or more different signals, in C and C++, when the handler does non-reentrant work and nothing blocks the other signals while it runs. A second signal can then interrupt the handler half way through its own operation; the CWE example is one handler for SIGHUP and SIGTERM that frees a global, which is freed twice when both arrive. This maps to CWE-831: Signal Handler Function Associated with Multiple Signals.

Registrations are signal()/std::signal/sigset/bsd_signal(SIGx, handler) (the handler plain or class-qualified, &Server::OnSignal) and sigaction(SIGx, &sa, ...) within 30 lines after sa.sa_handler/sa.sa_sigaction = handler or a designated initialiser (struct sigaction sa = { .sa_handler = handler }). The handler must be defined in the same file and call a function that is not async-signal-safe (stdio, malloc/free, syslog, exit, strtok, localtime, strerror, pthread locking, std::cout, new/delete), directly or through a function defined in the same file.

Not reported: a handler installed for a single signal, handlers that only set a flag or call async-signal-safe functions (write, _exit), a sigaction whose sa_mask is filled or extended (sigfillset/sigaddset), a crash handler shared only between synchronous fault signals (SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT, SIGTRAP, SIGSYS), SIG_IGN/SIG_DFL, handlers defined in another file, comments and string literals. This rule owns these registration lines (VNX-432 reported the same shape before and now reports only SA_NODEFER, which disables a handler’s blocking of its own signal); VNX-828 reports the unsafe calls themselves.

Why This Matters

When the same signal handler is registered for multiple signals, it’s easy to assume incorrectly that the handler body knows which signal triggered it, especially when the sig parameter is ignored. If signal A arrives while the handler is processing signal B, the shared state the handler updates may be corrupted. Separate handlers that set distinct flags are clearer, safer, and easier to reason about.

What Gets Flagged

static void handler(int sig)
{
    syslog(LOG_NOTICE, "caught signal %d", sig);
    free(global_buffer);
}

signal(SIGHUP, handler);    /* FLAGGED */
signal(SIGTERM, handler);   /* FLAGGED */
sa.sa_handler = on_shutdown;          /* on_shutdown() calls fclose() */
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);         /* FLAGGED */
sigaction(SIGTERM, &sa, NULL);        /* FLAGGED */

Remediation

// SAFE: separate handlers for each signal
static volatile sig_atomic_t got_sigint = 0;
static volatile sig_atomic_t got_sigterm = 0;

void sigint_handler(int sig) {
    (void)sig;
    got_sigint = 1;
}

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

int main(void) {
    signal(SIGINT, sigint_handler);
    signal(SIGTERM, sigterm_handler);
    
    while (1) {
        if (got_sigint) {
            printf("Interrupted by user\n");
            break;
        }
        if (got_sigterm) {
            printf("Termination requested\n");
            graceful_shutdown();
            break;
        }
        do_work();
    }
    return 0;
}
// SAFE: if sharing is necessary, use sigaction with SA_SIGINFO
// to get the full siginfo_t and distinguish signals properly
void shared_handler(int sig, siginfo_t *info, void *context) {
    if (sig == SIGUSR1) {
        got_sigusr1 = 1;
    } else if (sig == SIGUSR2) {
        got_sigusr2 = 1;
    }
}

struct sigaction sa = {
    .sa_sigaction = shared_handler,
    .sa_flags = SA_SIGINFO
};
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-831
// nosec vnx-831

Or record an ignore rule: vulnetix ignore add --rule vnx-831 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-831.rego, cli/internal/sast/docs/vnx-831.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.