Dropping Root Permissions in Python

Dropping Root Permissions In Python

You won't be able to open a server on port 80 without root privileges, this is a restriction on the OS level. So the only solution is to drop root privileges after you have opened the port.

Here is a possible solution to drop root privileges in Python: Dropping privileges in Python. This is a good solution in general, but you'll also have to add os.setgroups([]) to the function to ensure that the group membership of the root user is not retained.

I copied and cleaned up the code a little bit, and removed logging and the exception handlers so it is left up to you to handle OSError properly (it will be thrown when the process is not allowed to switch its effective UID or GID):

import os, pwd, grp

def drop_privileges(uid_name='nobody', gid_name='nogroup'):
if os.getuid() != 0:
# We're not root so, like, whatever dude
return

# Get the uid/gid from the name
running_uid = pwd.getpwnam(uid_name).pw_uid
running_gid = grp.getgrnam(gid_name).gr_gid

# Remove group privileges
os.setgroups([])

# Try setting the new uid/gid
os.setgid(running_gid)
os.setuid(running_uid)

# Ensure a very conservative umask
old_umask = os.umask(077)

Drop root privileges for certain operations in Python

You can switch between uid's using os.seteuid(). This differs from os.setuid() in that you can go back to getting root privileges when you need them.

For example, run the following as root:

import os

open('file1', 'wc')

# switch to userid 501
os.seteuid(501)
open('file2', 'wc')

# switch back to root
os.seteuid(0)
open('file3', 'wc')

This creates file1 and file3 as root, but file2 as the user with uid 501.

If you want to determine which user is calling your script, sudo sets two environment variables:

SUDO_USER
SUDO_UID

Respectively the username and the uid of the user who called sudo. So you could use int(os.environ['SUDO_UID']) to use with os.seteuid().

Drop/Enable Root Privilege Opening File in Python

You will probably need to change from root in a process that you spawn somehow, because, if you drop root, you can't get it back again. You could try using os.fork() for this.

import os

def drop_permissions():
os.setegid(int(os.getenv("SUDO_GID")))
os.seteuid(int(os.getenv("SUDO_UID")))

def call_without_permissions(func, *args, **kw):
in_parent = os.fork()
if not in_parent:
drop_permissions()
func(*args, **kw)
os._exit(0)
else:
os.waitpid(0)

Python Disable and Enable Root Privileges

Try the following:

import os
print "user who executed the code: %d" % os.getuid()
print "current effective user: %d" % os.geteuid()
if os.getuid() == 0:
os.seteuid(65534) # user id of the user "nobody"
print "current effective user: %d" % os.geteuid()
# do what you need to do with non-root privileges here, e.g. write to a file
print >> open("/tmp/foobar.txt", "w"), "hello world"
os.seteuid(0)
print "current effective user: %d" % os.geteuid()

Running this as root outputs:


user who executed the code: 0
current effective user: 0
current effective user: 65534
current effective user: 0

Root priv can't be dropped in python even after seteuid. A bug?

Manipulating process credentials on Unix systems is tricky. I highly recommend gaining a thorough understanding of how the Real, Effective, and Saved-Set user ids are interrelated. It's very easy to screw up "dropping privileges".

As to your specific observations... I'm wondering if there's a simple cause you may have overlooked. Your code is preforming a inconsistent tests and you've neglected to specify the exact file permissions on your /etc/sudoers and /etc/group- files. Your could would be expected to behave exactly as you describe if /etc/sudoers has permissions mode=440, uid=root, gid=root (which are the default permissions on my system) and if /etc/group- has mode=400.

You're not modifying the process's GID so if /etc/sudoers is group-readable, that would explain why it's always readable. fork() does not modify process credentials. However, it could appear to do so in your example code since you're checking different files in the parent and child. If /etc/group- does not have group read permissions where /etc/sudoers does, that would explain the apparent problem.

If all you're trying to do is "drop privileges", use the following code:

os.setgid( NEW_GID )
os.setuid( NEW_UID )

Generally speaking, you'll only want to manipulate the effective user id if your process needs to toggle it's root permissions on and off over the life of the process. If you just need to do some setup operations with root permissions but will no longer require them after those setup operations are complete, just use the code above to irrevokably drop them.

Oh, and a useful debugging utility for process credential manipulation on Linux is to print the output of /proc/self/status, the Uid and Gid lines of this file display the real, effective, saved-set, and file ids held by the current process (in that order). The Python APIs can be used to retrieve the same information but you can consider the contents of this file as "truth data" and avoid any potential complications from Python's cross-platform APIs.

How do you temporary run your code as 'root'?

In order from the least dangerous to the most dangerous.

  1. You can try dropping permissions as John Zwinck suggested.
    Basically you would start the program with root level permissions,
    immediately do what you need to do, and then switch to a non-root
    user.

    From this StackOverflow.

    import os, pwd, grp

    def drop_privileges(uid_name='nobody', gid_name='nogroup'):
    if os.getuid() != 0:
    # We're not root so, like, whatever dude
    return

    # Get the uid/gid from the name
    running_uid = pwd.getpwnam(uid_name).pw_uid
    running_gid = grp.getgrnam(gid_name).gr_gid

    # Remove group privileges
    os.setgroups([])

    # Try setting the new uid/gid
    os.setgid(running_gid)
    os.setuid(running_uid)

    # Ensure a very conservative umask
    old_umask = os.umask(077)
  2. You could also require the credentials for the root user to be
    inputed into the script, and then only use them when they are
    required.

    subprocess.call("sudo python RunStuffWithElevatedPrivelages.py")
    #From here, the main script will continue to run without root permissions

    Or if you don't want the script to prompt the user for the password you can do

    subprocess.call("echo getRootCredentials() | sudo -S python RunStuffWithElevatedPrivelages.py")
  3. Or you could just run the entire program as a root user -- sudo python myScript.py.

As far as temporarily giving users root permission to /dev/shm only when they run your script, the only thing I could think of was having some script that runs in the background under the root user that can temporarily grant anyone who uses your script root privileges to /dev/shm. This could be done through using setuid to grant such permissions and then after a certain amount of time or if the script ends the privilege is taken away. My only concern would be if there is a way a user who has temporarily been given such permissions might be able to secure more permanent privileges.

Multiple python scripts and root permissions

One way of doing this is to start as root, fork all sub-processes and then drop your privileges in the (sub-)processes that don't need the privileges.

For an example, see here

There are some other suggestions as well in the same post.

Pass root privilege to os commands in Python

What you're trying to do here is basically impossible on most modern operating systems, and for good reason.

Imagine a typical macOS or Windows user who expects to be able to auth by using the fingerprint reader instead of typing their password. Or a blind user. Or someone who's justifiably paranoid about your app storing their password in plaintext in a Python string in non-kernel memory (not to mention in a Windows dialog box whose events can be hooked by any process). This is why modern platforms come with a framework like libPAM/XSSO, Authorization Services. etc.

And the way privilege escalation works is so different between Windows and POSIX, or even between macOS and Linux, not to mention so rapidly evolving, that, as far as I know, there's no cross-platform framework to do it.

In fact, most systems discourage app-driven privilege escalation in the first place. On Windows, you often ask the OS to run a helper app with elevated privileges (and the OS will then apply the appropriate policy and decide what to ask for). On macOS, you usually write a LaunchServices daemon that you get permission for at install time (using special installer APIs) rather than runtime. For traditional non-server-y POSIX apps, you'd usually do something similar, but with a setuid helper that you can create at install time just because the installation runs as root. For traditional POSIX servers, you often start as root then drop privs after forking and execing a similar helper daemon.

If all of this seems like way more work than you wanted to deal with… well, honestly, that was probably the intention. OS designers don't want app designers introducing security holes, so they make sure you have to understand what the platform wants and how to work with it rather than against it before you can even try to do things like moving around files you don't have permissions for.



Related Topics



Leave a reply



Submit