|
# launches a root child process |
|
def spawn_root_child(self): |
|
msg = "DEBUG: Called spawn_root_child()" |
|
print( msg ); logger.debug( msg ) |
|
|
|
# SECURITY NOTE: |
|
# |
|
# Whenever you execute something as root, it's very important that you know |
|
# _what_ you're executing. For example, never execute a script as root that is |
|
# world-writeable. In general, assuming the script is named 'root_child.py': |
|
# |
|
# 1. Make sure root_child.py has permissions root:root 0500 (and therefore only |
|
# writeable and executable by root) |
|
# 2. Make sure I specify the absolute path to root_child.py, and that path |
|
# cannot be maliciously manipulated |
|
# 3. Make sure that root_child.py isn't actually a (sym)link |
|
# |
|
# See also: |
|
# * https://github.com/BusKill/buskill-app/issues/14#issuecomment-1272449172 |
|
|
|
if self.OS_NAME_SHORT == 'lin': |
|
msg = "ERROR: root_child_lin.py not yet implemented" |
|
print( msg ); logger.error( msg ) |
|
|
|
elif self.OS_NAME_SHORT == 'win': |
|
msg = "ERROR: root_child_win.py not yet implemented" |
|
print( msg ); logger.error( msg ) |
|
|
|
elif self.OS_NAME_SHORT == 'mac': |
|
|
|
# is the root child process already started? |
|
if self.root_child == None: |
|
# the root child process hasn't been started; start it |
|
msg = "DEBUG: No root_child detected. Attempting to spawn one." |
|
print( msg ); logger.debug( msg ) |
|
|
|
msg = "INFO: You have requested BusKill to do something that requires elevated privliges on your platform. If you'd like to proceed, please authorize BusKill to preform actions as Administrator. Your system may prompt you for your password to proceed." |
|
print( msg ); logger.info( msg ) |
|
|
|
# To spawn a child process as root in MacOS, we use |
|
# AuthorizationExecuteWithPrivileges(), which triggers the OS to |
|
# display an auth challenge in the GUI for the user. See also |
|
# * https://stackoverflow.com/a/74001980/1174102 |
|
# * https://stackoverflow.com/a/74083291/1174102 |
|
# * https://github.com/BusKill/buskill-app/issues/14 |
|
|
|
# was BusKill called as a binary or a script? |
|
if self.EXECUTED_AS_SCRIPT == False: |
|
# this execution was a binary |
|
|
|
# let's call the root child binary |
|
root_child_path = self.SRC_DIR +os.sep+ 'root_child_mac' |
|
|
|
# and we'll be calling the binary directly |
|
exe = [root_child_path, self.LOG_FILE_PATH] |
|
|
|
else: |
|
# this execution was a script; let's call the root child script |
|
root_child_path = self.SRC_DIR +os.sep+ 'packages' +os.sep+ 'buskill' +os.sep+ 'root_child_mac.py' |
|
|
|
# and we'll pass the script as an argument to the python |
|
# interpreter |
|
exe = [sys.executable, root_child_path, self.LOG_FILE_PATH] |
|
|
|
msg = "DEBUG: root_child_path:|" +str(root_child_path)+ "|" |
|
print( msg ); logger.debug( msg ) |
|
|
|
# SANITY CHECKS |
|
|
|
mode = oct(os.stat(root_child_path).st_mode)[-4:] |
|
owner = os.stat(root_child_path).st_uid |
|
group = os.stat(root_child_path).st_gid |
|
|
|
# verify the mode of the file is exactly 0500 (octal) |
|
if mode != '0500': |
|
msg = 'ERROR: Permissions on root_child are not 0500. Refusing to spawn script as root!' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
# unfortunaetly we can't package a .dmg with a file owned by root, so on |
|
# first run, we expect that the root child script will be owned by the |
|
# user that executed the BusKill app |
|
# https://github.com/BusKill/buskill-app/issues/14#issuecomment-1279975783 |
|
|
|
# verify the file is owned by user = root (or current user) |
|
if owner != 0 and owner != os.getuid(): |
|
msg = 'ERROR: root_child is not owned by root nor your user. Refusing to spawn script as root!' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
# verify the file is owned by group = root (or current group) |
|
if group != 0 and group != os.getgid(): |
|
msg = 'ERROR: root_child is not owned by gid=0 nor your group. Refusing to spawn script as root!' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
# verify the "file" isn't actually a symlink |
|
if os.path.islink( root_child_path ): |
|
msg = 'ERROR: root_child is a link. Refusing to spawn script as root!' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
# 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) |
|
|
|
args = (ctypes.c_char_p * len(exe))() |
|
for i,arg in enumerate(exe[1:]): |
|
args[i] = arg.encode('utf8') |
|
|
|
if self.root_child == None: |
|
self.root_child = dict() |
|
self.root_child['io'] = ctypes.c_void_p() |
|
|
|
msg = "DEBUG: Attempting to spawn root child (" +str(exe)+ ")" |
|
print( msg ); logger.debug( msg ) |
|
|
|
err = sec.AuthorizationExecuteWithPrivileges( |
|
auth, |
|
exe[0].encode('utf8'), |
|
0, |
|
args, |
|
byref(self.root_child['io']) |
|
) |
|
|
|
msg = "DEBUG: AuthorizationExecuteWithPrivileges.err:|" +str(err)+ "|" |
|
print( msg ); logger.debug( msg ) |
|
|
|
# did the attempt to spawn the child process return an error? |
|
if err == -60007: |
|
# https://developer.apple.com/documentation/security/1540004-authorization_services_result_co/errauthorizationinteractionnotallowed |
|
msg = 'ERROR: root_child spwan attempt returned errAuthorizationInteractionNotAllowed = -60007. Did you execute BusKill from a headless CLI? The credential challenge requires a GUI when launching a child process as root.' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
elif err == -60031: |
|
# https://developer.apple.com/documentation/security/1540004-authorization_services_result_co/errauthorizationtoolexecutefailure |
|
msg = 'ERROR: root_child spwan attempt returned errAuthorizationToolExecuteFailure = -60031. Is the root child binary executable? Check permissions.' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
elif err != 0: |
|
# catch all other errors |
|
msg = 'ERROR: root_child spawn attempt returned ' +str(err)+ '. Please see reference documentation for Apple Authorization Services Result Codes @ https://developer.apple.com/documentation/security/1540004-authorization_services_result_co' |
|
print( msg ); logger.error( msg ) |
|
return False |
|
|
|
msg = "DEBUG: Root child spawned successfully!" |
|
print( msg ); logger.debug( msg ) |
|
|
|
return True |
|
|
|
# this basically just re-implmenets python's readline().strip() but in C |
|
def read_from_root_child_mac(self): |
|
|
|
libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) |
|
|
|
# get the output from the child process character-by-character until we hit a new line |
|
buf = ctypes.create_string_buffer(1) |
|
result = '' |
|
for x in range(1,100): |
|
|
|
# read one byte from the child process' communication PIPE and store it to the buffer |
|
libc.fread( byref(buf), 1, 1, self.root_child['io'] ) |
|
|
|
# decode the byte stored to the buffer as ascii |
|
char = buf.raw[:1].decode('ascii') |
|
|
|
# is the character a newline? |
|
if char == "\n": |
|
# the character is a newline; stop reading |
|
break |
|
else: |
|
# the character is not a newline; append it to the string and continue reading |
|
result += char |
|
|
|
return result |
This ticket will track the effort to implement a self-destruct trigger for veracrypt.
Work was started on this by @jneplokh here:
But I believe they got stuck on privilege escalation in Windows. When developing the soft-shutdown trigger on MacOS, I also encountered issues running as a non-root user, so I wrote a simple wrapper to launch a child process as root. I believe this would need to be ported to Windows for this task:
Deliverables would be:
spawn_root_child()- A python function that, when executed as a non-root user, asks the user (via the official OS UAC prompt) for their password and then launches another python script (root_child_win.py) as child process with root privileges.root_child_win.shA python script that, when executed as root by wrapperspawn_root_child(), it loops and waits for a command sent over stdin. If sent averacrypt-self-destructcommand, then it calls a functiontrigger_veracrypt-self-destruct()trigger_veracrypt-self-destruct()that finds all veracrypt volumes, securely wipes the veracrypt header and footer, and initiates a hard shutdown[1] above would be similar to
spawn_root_child()insrc/packages/buskill/__init__.pybuskill-app/src/packages/buskill/__init__.py
Lines 567 to 751 in 52d699a
[2] would be similar to https://github.com/BusKill/buskill-app/blob/master/src/packages/buskill/root_child_mac.py
[3] above would be similar to https://github.com/BusKill/buskill-linux/blob/master/triggers/buskill-selfdestruct.sh
[4] above would be similar to https://www.buskill.in/luks-self-destruct/