CWE-273: Improper Check for Dropped Privileges
Overview
Improper privilege drop checking occurs when code attempts to drop privileges (setuid, setgid, seteuid) but doesn't verify the operation succeeded, continuing execution with elevated privileges if the drop fails. This creates a critical vulnerability where code intended to run unprivileged continues as root.
Risk
High: Unchecked privilege drops mean code runs as root when it should be unprivileged, amplifying any vulnerability to full system compromise, enabling privilege escalation, bypassing intended security boundaries, and creating a false sense of security.
Remediation Steps
Core principle: Verify privilege dropping actually happened and cannot be bypassed; fail closed if privilege state is not as expected.
See also CWE-272 for least privilege configuration.
Locate Unchecked Privilege Drops
When reviewing security scan results:
- Find setuid/setgid calls: Look for privilege dropping without return value checks
- Check seteuid/setegid: Identify effective UID/GID changes without verification
- Review privilege initialization: Find startup code that should drop privileges
- Check final UID/GID: Look for missing getuid()/geteuid() verification
- Review error handling: Find privilege drops without failure handling
Vulnerable patterns:
// Privilege drop without checking!
setuid(1000); // Return value ignored - could fail!
setgid(1000); // Return value ignored!
// No verification after drop
seteuid(unprivileged_uid);
// Still could be running as root if seteuid failed!
Always Verify Privilege Drop (Primary Defense)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
// VULNERABLE - setuid return value not checked
void drop_privileges_bad() {
uid_t target_uid = 1000;
gid_t target_gid = 1000;
// BUG: Return values ignored!
setgid(target_gid); // Could fail!
setuid(target_uid); // Could fail!
// Attack: If setuid fails, still running as root!
// Continues with elevated privileges
run_untrusted_code();
}
// SECURE - check all return values and verify final state
void drop_privileges_safe() {
uid_t target_uid = 1000;
gid_t target_gid = 1000;
printf("Dropping privileges from UID %d to %d\n", getuid(), target_uid);
// Step 1: Drop supplementary groups
if (setgroups(0, NULL) != 0) {
perror("setgroups failed");
fprintf(stderr, "CRITICAL: Failed to drop supplementary groups\n");
exit(1); // Fail securely - don't continue as root!
}
// Step 2: Set GID (do before setuid!)
if (setgid(target_gid) != 0) {
perror("setgid failed");
fprintf(stderr, "CRITICAL: Failed to set GID to %d\n", target_gid);
exit(1);
}
// Step 3: Set UID (do this last!)
if (setuid(target_uid) != 0) {
perror("setuid failed");
fprintf(stderr, "CRITICAL: Failed to set UID to %d\n", target_uid);
exit(1);
}
// Step 4: VERIFY the privilege drop succeeded
if (getuid() != target_uid) {
fprintf(stderr, "CRITICAL: Real UID is %d, expected %d\n",
getuid(), target_uid);
exit(1);
}
if (geteuid() != target_uid) {
fprintf(stderr, "CRITICAL: Effective UID is %d, expected %d\n",
geteuid(), target_uid);
exit(1);
}
if (getgid() != target_gid) {
fprintf(stderr, "CRITICAL: Real GID is %d, expected %d\n",
getgid(), target_gid);
exit(1);
}
if (getegid() != target_gid) {
fprintf(stderr, "CRITICAL: Effective GID is %d, expected %d\n",
getegid(), target_gid);
exit(1);
}
// Step 5: Verify cannot regain root privileges
if (setuid(0) == 0) {
fprintf(stderr, "CRITICAL: Can regain root privileges!\n");
exit(1);
}
printf("Successfully dropped privileges to UID %d, GID %d\n",
getuid(), getgid());
// Safe to run untrusted code now
run_untrusted_code();
}
// Using getpwnam for user lookup
void drop_privileges_by_name(const char *username) {
// Look up user by name
struct passwd *pw = getpwnam(username);
if (pw == NULL) {
fprintf(stderr, "User '%s' not found\n", username);
exit(1);
}
// Drop to that user's UID/GID
if (setgroups(0, NULL) != 0) {
perror("setgroups");
exit(1);
}
if (setgid(pw->pw_gid) != 0) {
perror("setgid");
exit(1);
}
if (setuid(pw->pw_uid) != 0) {
perror("setuid");
exit(1);
}
// Verify
if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) {
fprintf(stderr, "Privilege drop verification failed\n");
exit(1);
}
printf("Dropped to user '%s' (UID %d)\n", username, pw->pw_uid);
}
Drop All Privilege Components
#include <sys/capability.h>
// VULNERABLE - incomplete privilege drop
void incomplete_drop_bad() {
// Only drops effective UID
seteuid(1000); // Saved UID still root!
// Can regain root!
seteuid(0); // Back to root - BAD!
}
// SECURE - permanent privilege drop
void complete_drop_safe() {
uid_t target_uid = 1000;
gid_t target_gid = 1000;
// 1. Drop supplementary groups
if (setgroups(0, NULL) != 0) {
perror("setgroups");
exit(1);
}
// 2. Drop GID (all: real, effective, saved)
if (setgid(target_gid) != 0) {
perror("setgid");
exit(1);
}
// 3. Drop UID (all: real, effective, saved)
if (setuid(target_uid) != 0) {
perror("setuid");
exit(1);
}
// 4. Clear Linux capabilities
cap_t caps = cap_init(); // Empty capability set
if (cap_set_proc(caps) != 0) {
perror("cap_set_proc");
cap_free(caps);
exit(1);
}
cap_free(caps);
// 5. Verify cannot regain any privilege
// Try to regain root
if (setuid(0) == 0) {
fprintf(stderr, "ERROR: Can regain UID 0!\n");
exit(1);
}
if (seteuid(0) == 0) {
fprintf(stderr, "ERROR: Can regain effective UID 0!\n");
exit(1);
}
if (setgid(0) == 0) {
fprintf(stderr, "ERROR: Can regain GID 0!\n");
exit(1);
}
// Verify capabilities cleared
cap_t current_caps = cap_get_proc();
cap_flag_value_t cap_value;
cap_get_flag(current_caps, CAP_SYS_ADMIN, CAP_EFFECTIVE, &cap_value);
if (cap_value == CAP_SET) {
fprintf(stderr, "ERROR: Still have capabilities!\n");
cap_free(current_caps);
exit(1);
}
cap_free(current_caps);
printf("✓ All privileges permanently dropped\n");
}
Handle Privilege Drop Failures
// Comprehensive privilege drop with error handling
int safe_privilege_drop(uid_t target_uid, gid_t target_gid) {
// Verify we're starting as root
if (geteuid() != 0) {
fprintf(stderr, "Warning: Not running as root, privilege drop unnecessary\n");
return 0;
}
// Drop supplementary groups
if (setgroups(0, NULL) != 0) {
int err = errno;
fprintf(stderr, "setgroups(0, NULL) failed: %s\n", strerror(err));
return -1;
}
// Set GID
if (setgid(target_gid) != 0) {
int err = errno;
fprintf(stderr, "setgid(%d) failed: %s\n", target_gid, strerror(err));
return -1;
}
// Verify GID changed
if (getgid() != target_gid || getegid() != target_gid) {
fprintf(stderr, "GID verification failed: real=%d effective=%d expected=%d\n",
getgid(), getegid(), target_gid);
return -1;
}
// Set UID
if (setuid(target_uid) != 0) {
int err = errno;
fprintf(stderr, "setuid(%d) failed: %s\n", target_uid, strerror(err));
return -1;
}
// Verify UID changed
if (getuid() != target_uid || geteuid() != target_uid) {
fprintf(stderr, "UID verification failed: real=%d effective=%d expected=%d\n",
getuid(), geteuid(), target_uid);
return -1;
}
// Verify cannot regain root
if (setuid(0) == 0 || seteuid(0) == 0 || setgid(0) == 0) {
fprintf(stderr, "ERROR: Can regain root privileges!\n");
return -1;
}
return 0; // Success
}
// Usage
int main(int argc, char **argv) {
// ... initialization that requires root ...
// Drop privileges
if (safe_privilege_drop(1000, 1000) != 0) {
fprintf(stderr, "FATAL: Privilege drop failed - cannot continue\n");
exit(1); // Fail securely
}
// ... continue as unprivileged user ...
return 0;
}
Test Privilege Drop During Development
#!/bin/bash
# Test privilege dropping
echo "Testing privilege drop..."
# Compile test program
gcc -o test_privdrop test_privdrop.c -lcap
# Test 1: Run as root, should drop to nobody
sudo ./test_privdrop
if [ $? -ne 0 ]; then
echo "✗ Privilege drop test failed"
exit 1
fi
echo "✓ Privilege drop succeeded"
# Test 2: Run as regular user, should handle gracefully
./test_privdrop
if [ $? -ne 0 ]; then
echo "✗ Non-root test failed"
exit 1
fi
echo "✓ Non-root handling OK"
# Test 3: Verify can't regain privileges
cat > test_regain.c << 'EOF'
#include <stdio.h>
#include <unistd.h>
int main() {
// After privilege drop
if (setuid(0) == 0) {
fprintf(stderr, "ERROR: Regained root!\n");
return 1;
}
printf("Cannot regain root - good!\n");
return 0;
}
EOF
gcc -o test_regain test_regain.c
sudo ./test_regain
echo "All privilege drop tests passed!"
Monitor Privilege Drop in Production
#include <syslog.h>
// Log privilege drops for security monitoring
void drop_privileges_logged(const char *username) {
openlog("myapp", LOG_PID | LOG_CONS, LOG_AUTH);
uid_t original_uid = getuid();
uid_t original_euid = geteuid();
syslog(LOG_INFO, "Attempting to drop privileges from UID %d (effective %d) to user %s",
original_uid, original_euid, username);
struct passwd *pw = getpwnam(username);
if (pw == NULL) {
syslog(LOG_ERR, "SECURITY: User %s not found, cannot drop privileges", username);
exit(1);
}
// Drop privileges with full checking
if (setgroups(0, NULL) != 0) {
syslog(LOG_ERR, "SECURITY: setgroups failed: %m");
exit(1);
}
if (setgid(pw->pw_gid) != 0) {
syslog(LOG_ERR, "SECURITY: setgid to %d failed: %m", pw->pw_gid);
exit(1);
}
if (setuid(pw->pw_uid) != 0) {
syslog(LOG_ERR, "SECURITY: setuid to %d failed: %m", pw->pw_uid);
exit(1);
}
// Verify
if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) {
syslog(LOG_ERR, "SECURITY: Privilege drop verification failed");
exit(1);
}
// Success
syslog(LOG_INFO, "Successfully dropped privileges to user %s (UID %d)",
username, pw->pw_uid);
closelog();
}
Common Vulnerable Patterns
- Ignoring setuid() return value
- Not checking effective UID after drop
- Only dropping effective UID (saved UID still root)
- Assuming privilege drop always succeeds
- Not testing privilege drop with non-root user
Security Checklist
- All setuid/setgid/seteuid/setegid return values checked
- Final UID/GID verified with getuid/geteuid/getgid/getegid
- Supplementary groups dropped with setgroups(0, NULL)
- Cannot regain root (tested with setuid(0))
- Capabilities cleared on Linux
- Privilege drop failures cause program to exit (fail securely)
- Privilege drops logged for security monitoring
- Tests run during development verify proper dropping