VNX-273: Improper Check for Dropped Privileges
Detects privilege drops whose result is thrown away: setuid/setgid/setresuid/setgroups/initgroups and the Windows impersonation calls as bare or (void)-cast statements in C and C++, syscall/unix Setuid-family calls in Go (also deferred), libc:: and nix calls in Rust, and posix_setuid-family calls in PHP. A failed drop leaves the program running with the identity it meant to give up.
| Field | Value |
|---|---|
| Rule ID | VNX-273 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | C, C++, Go, Rust, PHP |
| CWE | CWE-273 |
| 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 | dropped-privileges setuid unchecked-return impersonation cwe-273 |
| Source | internal/sast/rules/vnx-273.rego |
Overview
VNX-273 maps to CWE-273: Improper Check for Dropped Privileges. Dropping privileges can fail: setuid() fails with EAGAIN when the target user is at RLIMIT_NPROC, a call made without CAP_SETUID fails with EPERM, and ImpersonateNamedPipeClient fails when the client has not written to the pipe yet. A program that does not check carries on as root, or as the server account, believing it is unprivileged.
The rule reports a drop that is used as a bare statement, so its result is discarded:
- C and C++:
setuid,setgid,seteuid,setegid,setreuid,setregid,setresuid,setresgid,setgroups,initgroups(also::setuid), including behind a(void)or(void)!cast,static_cast<void>(...)orstd::ignore =, which only silencewarn_unused_result, or as the body of a braceless one-lineif (getuid() == 0) setuid(uid);or} else setgid(gid);; andImpersonateNamedPipeClient,ImpersonateLoggedOnUser,ImpersonateSelf,ImpersonateAnonymousToken,RpcImpersonateClient,CoImpersonateClient,SetThreadToken. - Go:
syscall/unixSetuid,Setgid,Seteuid,Setegid,Setreuid,Setresuid,Setresgid,Setgroupsas a statement, assigned to_, or deferred (defer syscall.Seteuid(uid)drops the error of the restore). - Rust: the same
libc::calls ended with;or bound tolet _ =, and thenixcrate’sunistd::setuidand friends as a bare statement, with.ok();orlet _ =. - PHP:
posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_initgroupsas a statement. They returnfalseon failure and raise nothing.
Python, Ruby and Node are not covered because their calls raise on failure.
What Gets Flagged
// FLAGGED: if either call fails the job runs as root
setgid(pw->pw_gid);
setuid(pw->pw_uid);
return execv(argv[0], argv);
// FLAGGED: on failure DeleteFileA runs with the server's privileges
ImpersonateNamedPipeClient(hPipe);
DeleteFileA(path);
// FLAGGED
_ = unix.Setgid(gid)
syscall.Setuid(uid)
defer syscall.Seteuid(uid)
// FLAGGED
let _ = setgid(Gid::from_raw(gid));
setuid(Uid::from_raw(uid)).ok();
// FLAGGED
posix_setgid($info['gid']);
\posix_setuid($info['uid']);
What Is Not Flagged
- A result that is compared, assigned, returned, asserted or passed on:
if (setuid(uid) != 0),rc = seteuid(uid);,return setuid(uid);,if err := unix.Setgid(gid); err != nil,setgid(gid)?;,.expect("setuid"),posix_setuid($uid) or die(...). - A drop followed within a few lines by a verification on
getuid()/geteuid()/getegid(),errnoorGetLastError(), or by a checked attempt to regain root (if (setuid(0) != -1) abort();, CERT POS37-C). - A raise back to root such as
seteuid(0): if it fails, the program is left less privileged, not more. - Prototypes and definitions (also BSD style, with
inton the line abovesetresuid(uid_t r, uid_t e, uid_t s)in a compatibility shim), macros, member calls (ops->setuid(uid),my_setuid(uid)) and the names in comments or strings.
Remediation
if (setgroups(0, NULL) != 0 || setgid(gid) != 0 || setuid(uid) != 0)
abort();
/* paranoid: make sure root cannot be regained */
if (setuid(0) != -1 || getuid() != uid || geteuid() != uid)
abort();
if (!ImpersonateNamedPipeClient(hPipe))
return FALSE;
References
- CWE-273: Improper Check for Dropped Privileges
- Setuid Demystified (Chen, Wagner, Dean)
- LWN: setuid() and RLIMIT_NPROC
- ImpersonateNamedPipeClient
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-273
// nosec vnx-273
Or record an ignore rule: vulnetix ignore add --rule vnx-273 --reason "...". See Suppressing findings.