Created
August 16, 2023 04:42
-
-
Save brenns10/94167174cf32d6009518e1e241960e27 to your computer and use it in GitHub Desktop.
Open a file with root privileges via sudo, a helper, and unix sockets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Open a file via sudo and unix socket | |
If you only need root privileges to open one file, then you can spawn a helper | |
program with sudo to open the file, then pass the file descriptor back to your | |
original process via a Unix socket. This module imports a drop-in replacement | |
for os.open (except for the dir_fd argument) based on this idea: | |
stephen at wrath in ~/repos/opener | |
$ ls -l secret.txt | |
-rw------- 1 root root 24 Aug 15 21:11 secret.txt | |
stephen at wrath in ~/repos/opener | |
$ python | |
Python 3.11.3 (main, Jun 5 2023, 09:32:32) [GCC 13.1.1 20230429] on linux | |
Type "help", "copyright", "credits" or "license" for more information. | |
>>> import os, opener | |
>>> fd = opener.open_via_sudo("secret.txt") | |
[sudo] password for stephen: | |
>>> os.fdopen(fd, "rt").read() | |
os.fdopen(fd, "rt").read() | |
'the very secret contents' | |
>>> | |
There's some funny business I have yet to unwind with the terminal settings. | |
Sudo seems to change a number of settings around, and I had to run "stty sane" | |
after invoking sudo. It doesn't completely resolve the issue: see the extra copy | |
of the line reading "os.fdopen(...)"? That shouldn't be there... | |
""" | |
import argparse | |
import array | |
import os | |
import socket | |
import subprocess | |
import sys | |
import tempfile | |
from pathlib import Path | |
from typing import Union | |
def recv_fd(sock: socket.socket) -> int: | |
"""Receive a file descripter on the socket""" | |
# Borrows heavily from the Python docs for sock.recvmsg() | |
fds = array.array("i") | |
msg, ancdata, flags, addr = sock.recvmsg(4096, socket.CMSG_SPACE(fds.itemsize)) | |
if msg == b"success": | |
level, typ, data = ancdata[0] | |
assert level == socket.SOL_SOCKET | |
assert typ == socket.SCM_RIGHTS | |
data = data[:fds.itemsize] | |
fds.frombytes(data) | |
return fds[0] | |
raise Exception(msg.decode()) | |
def send_fd(sock: socket.socket, fd: int) -> None: | |
"""Send a file descripter on the socket""" | |
# Borrows heavily from the Python docs for sock.sendmsg() | |
fds = array.array("i", [fd]) | |
sock.sendmsg( | |
[b"success"], | |
[(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)], | |
) | |
def open_via_sudo( | |
path: Union[Path, str], | |
flags: int = os.O_RDONLY, | |
mode: int = 0o777, | |
) -> int: | |
"""Implements os.open() using sudo to get permissions""" | |
# Currently does not support dir_fd argument | |
path = str(path) | |
with tempfile.TemporaryDirectory() as td: | |
sockpath = Path(td) / "sock" | |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) | |
sock.bind(str(sockpath)) | |
try: | |
proc = subprocess.Popen( | |
[ | |
"sudo", | |
sys.executable, | |
"-B", | |
__file__, | |
str(sockpath), | |
path, | |
str(flags), | |
str(mode), | |
], | |
) | |
# TODO: handle errors / timeout / wait | |
return recv_fd(sock) | |
finally: | |
# sudo tends to mess up the terminal, get it into a sane state | |
subprocess.run(["stty", "sane"]) | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"socket", | |
type=Path, | |
help="path to unix domain socket", | |
) | |
parser.add_argument( | |
"file", | |
type=Path, | |
help="filename to open", | |
) | |
parser.add_argument( | |
"flags", | |
type=int, | |
help="numeric flags", | |
) | |
parser.add_argument( | |
"mode", | |
type=int, | |
help="numeric mode", | |
) | |
args = parser.parse_args() | |
fd = os.open(args.file, args.flags, args.mode) | |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) | |
sock.connect(str(args.socket)) | |
send_fd(sock, fd) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment