Pass root privilege to "os" commands in Python

1.3k views Asked by At

I am adding functionality to a PyQt5 application. This new functionality involves copying, linking and removing files (and links) that may be in protected directories, so commands like os.symlink or shutil.copyfile would fail.

Of course the main application is not run with root privileges (and asking users to do so is out of question), so I need a workaround.

First thing is of course to wrap the critical code in try/except blocks and investigate any exceptions. If it turns out missing root privileges are the issue I would ask for the password in a dialog (presumably storing the password for as long as the current dialog is alive).

But I'm not sure how I can repeat the step with the root password. I would strongly prefer doing it in the Python domain (or does Qt provide some support for file operations? I'd bet it does but I couldn't find it). I think it should be possible by doing the file operations in a shell command and somehow pass the password to that, but since Python and PyQt were designed to shield the programmer from the intricacies of OS differences I would prefer to avoid that route.

Some pseudocode should give a clear idea of the question:

def my_copy(source, dest):
    try:
        os.path.symlink(source, dest)
    except: # check for permission problem:
        # use dialog to ask for password
        # repeat the symlink procedure with password
2

There are 2 answers

1
abarnert On BEST ANSWER

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.

1
John Gordon On

Wrap the try/except code in a loop with a counter:

def my_copy(source, dest):
    for attempts in [1, 2]:
        try:
            os.path.symlink(source, dest)
            # we succeeded, so don't try any more
            break
        except: # check for permission problem:
            if attempts == 1:
                # use dialog to ask for password
                # repeat the symlink procedure with password
            else:
                 # we already tried as root, and failed again
                 break