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)
| 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
- Make the handler set a
volatile sig_atomic_tflag and do the real work in the main loop. - Or block the signal (
sigprocmask) around the code that uses the shared data, or handle signals synchronously withsignalfd/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.