ZetCode

Python os.setreuid Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.setreuid function, which sets real and effective user IDs. We'll cover Unix user privileges, security implications, and practical privilege management examples.

Basic Definitions

The os.setreuid function sets both real and effective user IDs for the current process. It's available on Unix-like systems for privilege management.

Key parameters: ruid (real user ID), euid (effective user ID). Either can be -1 to leave unchanged. Requires appropriate privileges to change IDs.

Basic Usage of setreuid

This example demonstrates the basic usage of os.setreuid to switch between user IDs. Note this requires appropriate privileges.

basic_setreuid.py
import os

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

print("Original IDs:")
show_ids()

# Change both real and effective UID to 1000
try:
    os.setreuid(1000, 1000)
    print("\nAfter setreuid(1000, 1000):")
    show_ids()
except PermissionError as e:
    print(f"\nFailed to change UIDs: {e}")

# Restore original IDs (requires root)
try:
    os.setreuid(0, 0)
    print("\nAfter restoring root:")
    show_ids()
except PermissionError as e:
    print(f"\nFailed to restore root: {e}")

This script attempts to change both real and effective UIDs to 1000 (typical regular user). It then tries to restore root privileges (UID 0).

Note that changing UIDs typically requires root privileges or appropriate capabilities like CAP_SETUID.

Changing Only Real UID

You can change just the real UID by passing -1 for the effective UID parameter. This affects process accounting but not permissions.

real_uid_only.py
import os

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

print("Original IDs:")
show_ids()

# Change only real UID to 1000
try:
    os.setreuid(1000, -1)
    print("\nAfter setreuid(1000, -1):")
    show_ids()
except PermissionError as e:
    print(f"\nFailed to change real UID: {e}")

# Verify file access behavior
file_path = "/etc/shadow"
print(f"\nAccess to {file_path}:")
print(f"os.access: {os.access(file_path, os.R_OK)}")
try:
    with open(file_path) as f:
        print("File opened successfully")
except IOError as e:
    print(f"Open failed: {e}")

This changes only the real UID while keeping the effective UID unchanged. File access checks use the effective UID, so permissions remain unchanged.

The example shows how os.access and actual file operations depend on the effective UID, not the real UID.

Changing Only Effective UID

You can change just the effective UID by passing -1 for the real UID parameter. This affects permissions but not process accounting.

effective_uid_only.py
import os

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

print("Original IDs:")
show_ids()

# Change only effective UID to 1000
try:
    os.setreuid(-1, 1000)
    print("\nAfter setreuid(-1, 1000):")
    show_ids()
except PermissionError as e:
    print(f"\nFailed to change effective UID: {e}")

# Verify privilege change
print("\nTrying privileged operation:")
try:
    os.mkdir("/system_dir")
    print("Created /system_dir successfully")
except PermissionError as e:
    print(f"Failed to create directory: {e}")

This changes only the effective UID while keeping the real UID unchanged. The process loses privileges for operations requiring the original effective UID.

The example demonstrates how changing effective UID affects permission to perform privileged operations like creating system directories.

Temporary Privilege Drop

A common security pattern is to drop privileges temporarily and restore them later. This example shows how to implement this with setreuid.

temp_privilege_drop.py
import os

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

def drop_privileges():
    """Drop privileges to regular user"""
    if os.geteuid() == 0:  # root
        regular_uid = 1000  # typical regular user
        os.setreuid(-1, regular_uid)
        print("Dropped privileges to regular user")

def restore_privileges():
    """Restore original privileges"""
    if os.geteuid() != 0 and os.getuid() == 0:
        os.setreuid(-1, 0)
        print("Restored root privileges")

print("Starting as root:")
show_ids()

# Perform privileged operation
print("\nCreating system file:")
try:
    with open("/etc/test_file", "w") as f:
        f.write("test")
    print("Created file successfully")
except PermissionError as e:
    print(f"Failed: {e}")

# Drop privileges
drop_privileges()
show_ids()

# Try privileged operation
print("\nAttempting privileged operation:")
try:
    with open("/etc/test_file", "a") as f:
        f.write("more data")
    print("Modified file successfully")
except PermissionError as e:
    print(f"Failed: {e}")

# Restore privileges
restore_privileges()
show_ids()

This demonstrates a secure pattern of dropping privileges for most operations and only elevating when necessary. The original privileges can be restored.

Note that complete privilege separation requires managing saved-set UID as well, which isn't covered by setreuid alone.

Switching Between Multiple Users

This example shows how to switch between multiple user contexts using setreuid, which can be useful in privilege-separated applications.

user_switching.py
import os

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

def run_as_user(ruid, euid):
    """Run code with specified user IDs"""
    print(f"\nSwitching to UIDs (real: {ruid}, effective: {euid})")
    try:
        os.setreuid(ruid, euid)
        show_ids()
        
        # Demonstrate user-specific behavior
        home_dir = os.path.expanduser("~")
        print(f"Home directory: {home_dir}")
        print(f"Can access /tmp: {os.access('/tmp', os.R_OK | os.W_OK)}")
        
    except PermissionError as e:
        print(f"Failed to switch users: {e}")
    finally:
        # Restore original IDs
        os.setreuid(0, 0)

print("Starting as root:")
show_ids()

# Switch to different user contexts
run_as_user(1000, 1000)  # Regular user
run_as_user(1001, 1001)  # Another user
run_as_user(0, 1000)     # Real root, effective user
run_as_user(1000, 0)     # Real user, effective root

This demonstrates switching between different user contexts to perform operations with different privilege levels. Each context has different permissions and environment.

The example shows how both real and effective UIDs affect various system interactions like home directory resolution and file access.

Error Handling with setreuid

This example focuses on proper error handling when working with setreuid, including permission checks and fallback behavior.

error_handling.py
import os
import sys

def show_ids():
    print(f"Real UID: {os.getuid()}, Effective UID: {os.geteuid()}")

def safely_drop_privileges(target_uid):
    """Attempt to drop privileges with proper error handling"""
    original_euid = os.geteuid()
    
    try:
        # First try to set both IDs
        os.setreuid(target_uid, target_uid)
        print(f"Successfully set both UIDs to {target_uid}")
        return True
        
    except PermissionError:
        print(f"Couldn't set both UIDs to {target_uid}, trying effective only")
        try:
            os.setreuid(-1, target_uid)
            print(f"Set effective UID to {target_uid}")
            return True
        except PermissionError:
            print(f"Failed to set any UIDs to {target_uid}")
            return False

print("Current IDs:")
show_ids()

# Try to drop privileges
if not safely_drop_privileges(1000):
    print("Falling back to restricted mode")
    try:
        # Try to at least give up root privileges
        if os.geteuid() == 0:
            os.setreuid(-1, os.getuid())
            print("Dropped effective root privileges")
    except PermissionError:
        print("Couldn't modify any UIDs - running with full privileges")

print("\nFinal IDs:")
show_ids()

# Verify we can still work
try:
    with open("user_file.txt", "w") as f:
        f.write("test")
    print("\nSuccessfully created user file")
except IOError as e:
    print(f"\nFile operation failed: {e}")
    sys.exit(1)

This demonstrates robust error handling when changing UIDs, with fallback strategies if full privilege drop isn't possible. It maintains functionality while maximizing security.

The example shows how to gracefully handle cases where only partial privilege reduction is possible while still maintaining application functionality.

Real-world Privilege Separation

This example shows a more complete privilege separation implementation using setreuid in a realistic scenario with multiple privilege levels.

privilege_separation.py
import os
import sys

class PrivilegeManager:
    def __init__(self):
        self.original_ruid = os.getuid()
        self.original_euid = os.geteuid()
        
    def drop_privileges(self):
        """Drop to unprivileged user"""
        if self.original_euid != 0:
            return True  # Already unprivileged
            
        try:
            # Try to set both IDs to original real UID
            os.setreuid(self.original_ruid, self.original_ruid)
            return True
        except PermissionError:
            try:
                # Fall back to just dropping effective UID
                os.setreuid(-1, self.original_ruid)
                return True
            except PermissionError:
                return False
                
    def restore_privileges(self):
        """Restore original privileges"""
        try:
            os.setreuid(self.original_ruid, self.original_euid)
            return True
        except PermissionError:
            return False
            
    def run_unprivileged(self, func, *args, **kwargs):
        """Run function with dropped privileges"""
        if not self.drop_privileges():
            raise RuntimeError("Failed to drop privileges")
        try:
            return func(*args, **kwargs)
        finally:
            if not self.restore_privileges():
                print("Warning: Failed to fully restore privileges",
                      file=sys.stderr)

def show_system_status():
    """Function to run with reduced privileges"""
    print("\nRunning with reduced privileges:")
    print(f"User IDs: {os.getuid()}/{os.geteuid()}")
    print(f"Process ID: {os.getpid()}")
    print("System time:", os.times())
    
    try:
        with open("/etc/passwd") as f:
            print("First 3 users:")
            for i, line in enumerate(f):
                if i >= 3: break
                print(line.strip())
    except IOError as e:
        print(f"Couldn't read passwd file: {e}")

# Main execution
if __name__ == "__main__":
    pm = PrivilegeManager()
    
    print("Starting with original privileges:")
    print(f"User IDs: {os.getuid()}/{os.geteuid()}")
    
    # Perform privileged operation
    try:
        with open("/root/test.log", "w") as f:
            f.write("Privileged operation\n")
        print("Performed privileged file operation")
    except IOError as e:
        print(f"Privileged operation failed: {e}")
    
    # Run unprivileged code
    pm.run_unprivileged(show_system_status)
    
    # Verify privileges restored
    print("\nAfter privilege restoration:")
    print(f"User IDs: {os.getuid()}/{os.geteuid()}")

This demonstrates a more complete privilege separation implementation with a PrivilegeManager class that handles the details of dropping and restoring privileges safely.

The example shows how to structure code to run specific operations with reduced privileges while maintaining the ability to perform privileged operations when needed.

Security Considerations

Best Practices