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.
| 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
- CWE-831: Signal Handler Function Associated with Multiple Signals
- CERT SIG00-C: Mask signals handled by noninterruptible signal handlers
- CERT SIG30-C: Call only asynchronous-safe functions within signal handlers
- POSIX sigaction()
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.