VNX-479: Signal Handler Use of a Non-reentrant Function
Detects a call to a function that is not async-signal-safe (stdio, malloc/free, syslog, exit, strtok, localtime, longjmp, new/delete, std::cout, mutex locks) inside a C/C++ crash handler, a handler the same file installs only for fault signals such as SIGSEGV.
| Field | Value |
|---|---|
| Rule ID | VNX-479 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | C, C++ |
| CWE | CWE-479 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | signal-handler reentrancy cwe-479 |
| Source | internal/sast/rules/vnx-479.rego |
Overview
VNX-479 maps to CWE-479: Signal Handler Use of a Non-reentrant Function. A fault signal (SIGSEGV, SIGBUS, SIGABRT, …) is often raised from inside malloc, printf or while a lock is held, because those are where memory corruption shows itself. A crash handler that calls the same function re-enters code whose internal state (the heap free list, a stdio buffer, a lock) is half updated. The process then deadlocks or corrupts the heap again instead of writing its crash report.
The rule finds crash handlers: functions the same file installs only for synchronous fault signals (SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT, SIGTRAP, SIGSYS), with signal(SIGx, handler) or with sa.sa_handler/sa.sa_sigaction = handler and sigaction(SIGx, &sa, ...) within thirty lines. It locates each one’s definition (void name(int ...), BSD style with the return type on the line above included) and reports every call in its body to printf/fprintf/sprintf/snprintf/puts/fputs/fwrite/fflush/fopen/fclose, malloc/calloc/realloc/free/strdup, syslog, exit, strtok, localtime/gmtime/ctime/asctime, perror, longjmp/siglongjmp, pthread_mutex_lock, std::lock_guard/unique_lock/scoped_lock, new/delete and std::cout/std::cerr.
Handlers installed for any asynchronous signal (SIGINT, SIGTERM, SIGHUP, SIGALRM, …), and handlers whose signals cannot be read from the file, are reported by VNX-828, so each unsafe call is reported once.
Not reported: async-signal-safe calls (write, _exit, backtrace_symbols_fd, storing to a volatile sig_atomic_t flag, signal, kill, raise, abort), SIG_IGN/SIG_DFL, handlers defined in another file, functions that are not installed as handlers, and comments (including a trailing comment that names a function).
What Gets Flagged
static void on_crash(int sig)
{
char *msg = malloc(256); /* FLAGGED */
snprintf(msg, 256, "crashed with %d\n", sig); /* FLAGGED */
fputs(msg, stderr); /* FLAGGED */
_exit(128 + sig);
}
...
signal(SIGSEGV, on_crash);
signal(SIGBUS, on_crash);
What Is Not Flagged
static void on_segv(int sig)
{
void *frames[64];
int n = backtrace(frames, 64);
write(STDERR_FILENO, msg, sizeof msg - 1);
backtrace_symbols_fd(frames, n, STDERR_FILENO);
signal(sig, SIG_DFL);
raise(sig);
}
Remediation
- Write the report with
write()andbacktrace_symbols_fd()into a buffer prepared before the crash, never with stdio ormalloc. - Do not take locks in a crash handler; the faulting thread may already hold them.
- End with
_exit(), or restore the default action and re-raise the signal so a core dump is still produced.
References
- CWE-479: Signal Handler Use of a Non-reentrant Function
- SEI CERT SIG30-C: Call only asynchronous-safe functions within signal handlers
- signal-safety(7)
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-479
// nosec vnx-479
Or record an ignore rule: vulnetix ignore add --rule vnx-479 --reason "...". See Suppressing findings.