How can I launch a child process that has root privileges?
I have a python program in MacOS that can do most of its operations as a normal user. But occasionally, triggered by some user interaction, it will need root permissions to preform a task.
For security reasons, I don't want the entire GUI app to be started and left running as root. I want only a child process with a very minimal subset of functions to run as root.
For UX reasons, I don't want to have to tell the user "Sorry, please restart this app as Administrator". I want to be able to have them stay in the GUI, get presented with a pop-up that says "Uh, you need root to do that. Please enter your password."
Of course, if my unprivileged python process attempts to become root with
setuid(0)
...then I just get a permissions error
PermissionError: [Errno 1] Operation not permitted
What can I use as an alternate to setuid()
so that I can launch a new child process on a MacOS system, after escalating privilege by getting authentication from the user in the GUI?
I want to be able to have them stay in the GUI, get presented with a pop-up that says "Uh, you need root to do that. Please enter your password."
This is exactly what the MacOS Security API's AuthorizationExecuteWithPrivileges()
function was created for.
You can call AuthorizationExecuteWithPrivileges()
directly with python's ctypes.
For example, consider your parent script running as your normal, non-root user. If you try to just run setuid(0)
, then it will fail with
PermissionError: [Errno 1] Operation not permitted
Instead, let's create another script named root_child.py
, which we'll execute as root with AuthorizationExecuteWithPrivileges()
#!/usr/bin/env python3
import os
if __name__ == "__main__":
try:
os.setuid(9)
print( "I am root!" )
except Exception as e:
print( "I am not root :'(" )
We can execute the above root_child.py
script as root from our non-root script spawn_root.py
:
import sys, ctypes, struct
import ctypes.util
from ctypes import byref
# import some C libraries for interacting via ctypes with the MacOS API
libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c"))
# https://developer.apple.com/documentation/security
sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
kAuthorizationFlagDefaults = 0
auth = ctypes.c_void_p()
r_auth = byref(auth)
sec.AuthorizationCreate(None,None,kAuthorizationFlagDefaults,r_auth)
exe = [sys.executable,"root_child.py"]
args = (ctypes.c_char_p * len(exe))()
for i,arg in enumerate(exe[1:]):
args[i] = arg.encode('utf8')
io = ctypes.c_void_p()
print( "running root_child.py")
err = sec.AuthorizationExecuteWithPrivileges(auth,exe[0].encode('utf8'),0,args,byref(io))
print( "err:|" +str(err)+ "|" )
print( "root_child.py executed!")
Note that, because the credential challenge for AuthorizationExecuteWithPrivileges()
comes via the GUI, you must execute this from within the GUI. If you attempt to execute the above scripts, for example, over a SSH in a tty, you'll get an error -60007
, which is errAuthorizationInteractionNotAllowed
and means:
The Security Server denied authorization because no user interaction is allowed.
user@host ~ % ./spawn_root.py
running root_child.py
err:|-60007|
root_child.py executed!
user@host ~ %
However, if executed from the Terminal app in the GUI, then it prompts the user for their password.
If the user successfully enters their credentials correctly, then the root_child.py
script is executed with root privileges.
user@host ~ % ./spawn_root.py
running root_child.py
err:|0|
root_child.py executed!
Note that AuthorizationExecuteWithPrivileges()
has been deprecated by apple in-favor of an alternatve that requires you to pay them money. Unfortunately, there's some misinformation out there that AuthorizationExecuteWithPrivileges()
is a huge security hole. While it's true that using AuthorizationExecuteWithPrivileges()
incorrectly can cause security issues, it is not inherently insecure to use it.
Obviously, any time you run something as root, you need to be very careful!
AuthorizationExecuteWithPrivileges()
is deprecated, but it can be used safely. But it can also be used unsafely!
It basically boils down to: do you actually know what you're running as root? If the script you're running as root is located in a Temp dir that has world-writeable permissions (as a lot of MacOS App installers have done historically), then any malicious process could gain root access.
To execute a process as root safely: