0

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
uli_1973
  • 705
  • 1
  • 5
  • 22
  • Asking for the password in a dialog won’t help, because most OS’s won’t let you do anything useful with it, and smart users won’t want to give it to you to store in unprotected memory. You need to use libpam, Authorization Services, or the Windows API that I forget the name of to ask the user to authenticate—or you need to create a `suid` helper or a daemon or Windows service that you communicate with via some IPC, so the user can grant the rights at install time. – abarnert Jul 30 '18 at 17:36
  • Or, alternatively, you can have your app start as root, then fork off a non-root process to run the GUI, while you use the root process to do the root work. – abarnert Jul 30 '18 at 17:37
  • I don't think asking the user to start the whole program as root is an acceptable idea. Maybe I have to resort to simply inform the user about the permission problem and tell them how to solve the task manually, outside of the app. – uli_1973 Jul 30 '18 at 18:19

2 Answers2

1

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.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thanks, this is a very good explanation for *not* giving me the steps to achieve what I was asking for. I was vaguely aware of that (at least after not finding appropriate native tools for Python nor Qt), but this makes me more comfortable with just giving users the feedback that they have to do it externally. – uli_1973 Jul 30 '18 at 19:25
0

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
John Gordon
  • 29,573
  • 7
  • 33
  • 58
  • 1
    I'm sorry, but while this is a good example to part of the issue it's not what I was actually asking about (probably the question wasn't clear enough). My issue is noth the control structure but how to pass a password to the file operation. I upvote nevertheless since it will serve its purpose for people finding this page. – uli_1973 Jul 30 '18 at 18:15