Skip to content

VNX-364: Signal Handler Race Condition

A function registered as a signal handler in the same C or C++ file (signal(SIG..., h), std::signal, sigset, or a struct sigaction whose sa_handler/sa_sigaction is h, or a captureless C++ lambda registered in place) writes to a file-scope variable that is not declared volatile sig_atomic_t or atomic (_Atomic, atomic_*, std::atomic): an assignment, increment, compound assignment, a write to a member or element of it, or free() of it. The signal can arrive while the interrupted code is half way through using the same variable, so the handler corrupts it (a pointer freed under the main loop and freed again, a counter update lost, a struct reset mid-read)

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-364
Severity Medium
SARIF level warning
Kind sast
Languages C, C++
CWE CWE-364
CAPEC CAPEC-66
ATT&CK T1190
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-race signal-handler cwe-364
Source internal/sast/rules/vnx-364.rego

Overview

VNX-364 maps to CWE-364: Signal Handler Race Condition. A function registered as a signal handler in the same C or C++ file (signal(SIG…, h), std::signal, sigset, or a struct sigaction whose sa_handler/sa_sigaction is h, or a captureless C++ lambda registered in place) writes to a file-scope variable that is not declared volatile sig_atomic_t or atomic (Atomic, atomic*, std::atomic): an assignment, increment, compound assignment, a write to a member or element of it, or free() of it. The signal can arrive while the interrupted code is half way through using the same variable, so the handler corrupts it (a pointer freed under the main loop and freed again, a counter update lost, a struct reset mid-read). Not reported: writes to volatile sig_atomic_t or atomic globals, locals of the handler, functions that are not registered as handlers (atexit callbacks, a function only called directly), SIG_IGN/SIG_DFL, and comments. Calls to async-signal-unsafe functions such as printf or malloc in a handler are VNX-479/VNX-828, not this rule.

Why This Matters

A signal can arrive at any instruction. A handler that frees, resets or updates a global the interrupted code is using corrupts it: a pointer freed under the main loop and freed again, a counter update lost, a struct reset half way through a read.

What Gets Flagged

static char *logbuf;

static void on_term(int sig)
{
	free(logbuf);      /* FLAGGED */
	logbuf = NULL;     /* FLAGGED */
	_exit(0);
}
...
signal(SIGTERM, on_term);

What Is Not Flagged

static volatile sig_atomic_t stop;

static void on_term(int sig) { stop = 1; }   /* not flagged */

Writes to volatile sig_atomic_t, _Atomic or std::atomic globals, handler locals and parameters (including one that shadows a global of the same name), functions that are never registered as handlers, SIG_IGN/SIG_DFL and comments are not reported. Async-signal-unsafe calls in a handler are VNX-479/VNX-828.

Remediation

  1. Make the handler set a volatile sig_atomic_t flag and do the real work in the main loop.
  2. Or block the signal (sigprocmask) around the code that uses the shared data, or handle signals synchronously with signalfd/sigwait.

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

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

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