CWE-271, Privilege Dropping / Lowering Errors, is a product that does not drop privileges before passing control of a resource to an actor that does not have those privileges.
It concerns setuid programs, daemons that start as root, installers, and any component that hands a process, file, or connection to a less trusted party.
MITRE explains the situation well. A system running with elevated permissions sometimes hands off a process or file to another process or user. If the privileges of the entity are not reduced, elevated privileges spread through the system and possibly to an attacker.
How it happens
There is a moment in many programs where the job changes from "do the thing that needs power" to "do the thing that touches untrusted input." The code must lower its privileges at that moment. It often does not, for a few reasons:
- The privileged step happens in many places and one path forgets to drop.
- An error path skips the drop.
- The drop is partial. User ID is lowered but groups are not.
- The program calls another program while still privileged.
MITRE's observed examples cover each of these:
- CVE-2000-1213 (Gold): the program does not drop privileges after acquiring a raw socket.
- CVE-2001-0559 (Gold): a setuid program does not drop privileges after a parsing error occurs, then calls another program to handle the error.
- CVE-2001-0787 (Gold) and CVE-2002-0080 (Gold): privileges in related groups are not dropped when lowering privileges.
Here is the raw socket case in sketch form:
// Vulnerable pattern
int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // needs root
// ... program continues as root, then parses user supplied arguments
parse_args(argc, argv);
run_helper(argv[1]); // child inherits root
The raw socket is the only reason for root. Everything after it should run without it.
What an attacker gains
MITRE lists two consequences:
- Gaining privileges. If privileges are not dropped, neither are the access rights of the user. Any bug in the later code runs with elevated rights.
- Hiding activity. In some cases the system may record actions as the user who is being impersonated rather than the impersonator, which muddies the audit trail.
How to find it
- Identify every program that starts with elevated rights: setuid and setgid binaries, services launched as root, containers run as the root user.
- For each, find the first line after which no privileged operation is needed and check whether anything drops rights there.
- Check all exit paths from the privileged block, including error and exception paths.
- Check both user and group IDs, including supplementary groups.
- Inspect a running process and see what it runs as.
ps -o user,group,supgrp,pid,cmd -C myservice
- Look at what child processes inherit by checking their user after they start.
How to fix it
Do the privileged work, then drop rights fully and for good, before any untrusted data is handled or any other program is launched.
// Safer pattern: acquire, then drop everything
int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (s < 0) { perror("socket"); exit(1); }
// clear supplementary groups first, then group, then user
if (setgroups(0, NULL) != 0 ||
setgid(app_gid) != 0 ||
setuid(app_uid) != 0) {
perror("drop privileges");
exit(1);
}
parse_args(argc, argv);
Points that matter:
- Order. Clear supplementary groups and set the group before the user, since changing groups requires privilege.
- Completeness. Handle all three of groups, group ID, and user ID.
- Verification. Check every return value. That failure mode has its own entry, CWE-273.
- Before handoff. Drop before executing another program or passing a descriptor to an untrusted actor.
- Prefer capabilities. On systems that support them, retain only the single capability you need rather than full root.
- Consider separation. A small privileged helper that does one thing, talking to an unprivileged main process, keeps the powerful code tiny.
Try it yourself
Take a service you maintain that starts as root. Write down the single operation that needs root. Then add a log line after the point where it should have dropped, printing the effective user ID and groups. Run it and read the output. If it still says root, you have a CWE-271 finding.
Quick check
Why does it matter whether supplementary groups are cleared?
A process can keep access through group membership even after the user ID has been lowered. CVE-2001-0787 and CVE-2002-0080 are examples of programs that did not drop privileges in related groups.
What does the audit trail problem look like?
If privileges are not dropped, the system may record the actions as coming from the identity being impersonated rather than from the actual actor, so investigators see the wrong name.
Sources
- MITRE CWE-271: https://cwe.mitre.org/data/definitions/271.html
- Safeguard Gold entry: https://gold.safeguard.sh/cwe/CWE-271