Skip to content

VNX-272: Least Privilege Violation

Detects root privilege kept after the privileged operation that needed it: a chroot in a project that never drops its identity afterwards, and a return to root (seteuid(0), setuid(0) in a setuid helper, os.seteuid(0)) that is never lowered again. C, C++, Python and Go.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-272
Severity Medium
SARIF level warning
Kind sast
Languages C, C++, Python, Go
CWE CWE-272
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 least-privilege chroot privilege-drop cwe-272
Source internal/sast/rules/vnx-272.rego

Overview

VNX-272 maps to CWE-272: Least Privilege Violation. Some operations need root: chroot(), binding a port below 1024, opening a protected device. The privilege should be given up as soon as that operation is done. This rule reports two concrete ways it is not:

  1. A chroot with no privilege drop. The file calls chroot() (C/C++), os.chroot(), a chroot() imported from os or called on an import os as alias (Python), syscall.Chroot()/unix.Chroot() or sets SysProcAttr{Chroot: ...} (Go, inline or on its own line), and nothing in the project’s code for that language ever changes identity: no setuid/setgid/setresuid/setgroups/initgroups, no drop_privileges()/drop_root()/lower_euid()/priv_drop()/switch_user()-style wrapper, no SysProcAttr.Credential (in the literal or assigned afterwards). A process that is still root inside a jail can leave it, and every file it opens is opened as root.
  2. An elevation that is never lowered. seteuid(0), setegid(0), setuid(0)/setgid(0) (the classic setuid-root wrapper that then runs system()), setreuid(-1, 0), setresuid(-1, 0, -1) or os.seteuid(0)/os.setuid(0) with no later call in the file that sets the effective identity back, directly or through such a wrapper.

What Gets Flagged

// FLAGGED: still root after entering the jail
chroot(APP_HOME);
chdir("/");
FILE *data = fopen(argv[1], "r+");
# FLAGGED: raised to root, never lowered
def read_shadow_entry(user):
    os.seteuid(0)
    with open("/etc/shadow") as shadow:
        ...
// FLAGGED: the child is chrooted but runs as root
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootfs, Setpgid: true}

What Is Not Flagged

  • A chroot followed by setgroups/setgid/setuid (or os.setgid/os.setuid, a Credential in SysProcAttr), including when the drop lives in another file of the project, such as a privsep module.
  • seteuid(0) followed later by seteuid(saved) or a drop_root()-style call (a finally: block counts), and an elevation inside a function whose name says it regains privileges (privs_regain(), restore_root()): its caller lowers them.
  • A check that root can no longer be regained after a drop: if (setuid(0) != -1) abort();, if (!seteuid(0)), or a Python try: os.seteuid(0) whose else: raises.
  • A Chroot: field of a struct that is not SysProcAttr, such as an SFTP server’s configuration.
  • Test code, member calls such as ops->chroot(dir), and the names in comments or string literals.

Whether a privileged section is longer than it needs to be cannot be decided line by line and is not judged.

Remediation

Drop supplementary groups, then the group, then the user, immediately after the privileged step, and check every call:

if (chroot(dir) != 0 || chdir("/") != 0)
    return -1;
if (setgroups(0, NULL) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0)
    return -1;

For a temporary elevation, restore the saved identity on every path:

unprivileged = os.geteuid()
os.seteuid(0)
try:
    return open(path).read()
finally:
    os.seteuid(unprivileged)

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

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

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