Safeguard
Application Security•

CWE-273 Improper Check for Dropped Privileges: Verify setuid

CWE-273 is dropping privileges without confirming it worked. Unchecked setuid return values leave programs running as root. See the pattern and fix.

Safeguard Security Team
4 min read

CWE-273, Improper Check for Dropped Privileges, is a product that tries to drop privileges but does not check, or checks incorrectly, whether the drop succeeded.

If the drop fails, MITRE says, the product keeps running with raised privileges, which may give extra access to unprivileged users. It applies to any C, C++, or other systems code that calls setuid, setgid, or similar functions, and to scripts and wrappers around them.

How it happens

Dropping privileges looks like a statement. It is actually a request that can be refused. The call returns an error if it fails, and a program that ignores the return value keeps its old, higher rights without knowing.

// Vulnerable: return value ignored
setuid(app_uid);
// code below assumes it is no longer root
handle_untrusted_input();

Why would the call fail? An attacker with local influence may be able to make it fail, for example by exhausting a resource limit before the program runs. MITRE's two observed examples describe exactly that outcome. In CVE-2006-4447 (Gold) and CVE-2006-2916 (Gold), the program does not check the return value when invoking the functions that drop privileges, which could leave users with higher privileges than expected by forcing those functions to fail.

The same trap exists in higher level languages:

import os
try:
    os.setuid(1001)
except OSError:
    pass            # swallowed: process is still root

What an attacker gains

MITRE lists two consequences:

  • Gaining privileges or assuming identity. If privileges are not dropped, neither are access rights. These rights often cannot be removed later.
  • Hiding activities. Actions may be recorded as the impersonated user rather than the impersonator.

The attacker does not need a memory corruption bug. They only need to make a security call fail and then find any ordinary flaw in the code that runs afterward.

How to find it

MITRE notes that static analysis can find some instances of this weakness. A manual pass is quick and productive:

  • Grep for the privilege functions and look at the line after each call.
grep -rnE 'set(e?u|e?g|res[ug])id\(|setgroups\(|initgroups\(' src/
  • Flag any call used as a bare statement, whose return value is discarded.
  • Look for try and except blocks that swallow errors from privilege calls.
  • Enable compiler warnings for unused results where your toolchain supports it, and mark the functions that matter.
  • Test failure paths: run the program under tight resource limits and confirm it exits instead of continuing.

How to fix it

Check the result of every privilege change, exit immediately on failure, and then confirm the new state independently.

if (setgroups(0, NULL) != 0 ||
    setgid(app_gid)    != 0 ||
    setuid(app_uid)    != 0) {
    perror("cannot drop privileges");
    _exit(EXIT_FAILURE);
}

// Verify: we must not be able to regain root
if (setuid(0) == 0) {
    fprintf(stderr, "privilege drop did not hold\n");
    _exit(EXIT_FAILURE);
}
if (getuid() == 0 || geteuid() == 0) {
    _exit(EXIT_FAILURE);
}

Python version:

import os, sys
try:
    os.setgroups([])
    os.setgid(1001)
    os.setuid(1001)
except OSError as e:
    sys.exit(f"cannot drop privileges: {e}")
if os.getuid() == 0 or os.geteuid() == 0:
    sys.exit("still root")

Three habits cover most of it: fail closed on any error, verify the end state instead of trusting the call, and drop before any untrusted input is touched. See also CWE-271 for what happens when the drop never occurs, and CWE-272 for keeping privilege only as long as needed.

Try it yourself

Click through the code scan demo below to see how static analysis flags a risky pattern in source code, then run the grep above on one of your own repositories and count how many privilege calls have an unchecked result.

Click through it at your own pace, or open it full screen.

Quick check

Why verify by trying to setuid back to zero?

If the drop truly worked, regaining root must fail. A successful setuid(0) proves the process still has the power you meant to remove.

Is swallowing the error acceptable if the program logs it?

No. Logging does not stop the process from continuing with raised rights. The program should exit when the drop fails.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.