Skip to content

VNX-368: Context Switching Race Condition

A temporary switch into the root context (seteuid(0), setegid(0), setreuid(-1, 0), setregid(-1, 0), setresuid(-1, 0, -1) or setresgid(-1, 0, -1), also ::-qualified) that is not an atomic, contained step, in C or C++

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-368
Severity Low
SARIF level note
Kind sast
Languages C, C++
CWE CWE-368
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 context-switch-race privilege cwe-368
Source internal/sast/rules/vnx-368.rego

Overview

VNX-368 maps to CWE-368: Context Switching Race Condition. A temporary switch into the root context (seteuid(0), setegid(0), setreuid(-1, 0), setregid(-1, 0), setresuid(-1, 0, -1) or setresgid(-1, 0, -1), also ::-qualified) that is not an atomic, contained step, in C or C++. Reported when the file starts threads (pthread_create, thrd_create, std::thread, std::jthread, CreateThread): the effective ID is process-wide, so every other thread runs as root for the whole switch; when the function returns or jumps out (return, goto) between the elevation and the call that switches back, leaving the process in the root context on that path; and when the function never switches back at all. Not reported: a failure exit that belongs to the elevation call itself (if (seteuid(0) != 0) return -1), an elevation that restores root after the same function lowered it, permanent drops (setuid, setgid, setresuid with three IDs), comments and string literals. The order of a permanent drop is VNX-269, and exec after a temporary drop is VNX-270.

Why This Matters

The effective user and group IDs belong to the whole process. While one thread has switched to root with seteuid(0), every other thread runs as root too, and a function that returns between the switch and the switch back leaves the process in the root context.

What Gets Flagged

int open_device(const char *dev, uid_t user)
{
	seteuid(0);            /* FLAGGED: returns below while still root */
	fd = open(dev, O_RDWR);
	if (fd < 0)
		return -1;
	seteuid(user);
	return fd;
}

What Is Not Flagged

if (seteuid(0) != 0)       /* the elevation's own failure exit */
	return -1;
fd = open(dev, O_RDWR);
seteuid(user);             /* restored on every path, single-threaded file */

A braced failure block after the elevation (if (seteuid(0) == -1) { perror(...); return -1; }), an elevation after fork() in the same function (the single-threaded child), permanent drops (setuid, setgid, three-ID setresuid), an elevation that restores root after the same function lowered it, comments and string literals are not reported. The order of a permanent drop is VNX-269 and exec after a temporary drop is VNX-270.

Remediation

  1. Do privileged work in a separate, single-threaded helper process instead of switching the effective ID of a threaded one.
  2. Restore the unprivileged ID on every path out of the function (a single exit, or a cleanup label).

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

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

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