Skip to content

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.

Updated 02 Nov 2026Checked against cli@de9b52a
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

  1. Write the report with write() and backtrace_symbols_fd() into a buffer prepared before the crash, never with stdio or malloc.
  2. Do not take locks in a crash handler; the faulting thread may already hold them.
  3. End with _exit(), or restore the default action and re-raise the signal so a core dump is still produced.

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-479
// nosec vnx-479

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

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