Created
February 6, 2018 03:14
-
-
Save ellieayla/69148d471bdd1b60ba66d17b26a02afd to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#!/usr/bin/env python | |
# https://www.reddit.com/r/Python/comments/7v9kse/filesocket_descriptor_share_across_process/ | |
# >>> hi, can we send a file/socket descriptor (with access and permissions) from one process to another ? | |
# >>> I mean not by forking but when process are already created . | |
""" | |
Demonstrate sending an open file descriptor between a pair of processes, the recipient of | |
which will read the file contents, depite not having permission to open(..., 'r') the file. | |
""" | |
import socket | |
import array | |
import sys | |
import multiprocessing | |
import os | |
import time | |
import pwd | |
import grp | |
import logging | |
logger = logging.getLogger() | |
logging.basicConfig(format='%(asctime)-15s pid=%(process)d %(side)s: %(message)s', level=logging.INFO) | |
# Function from https://docs.python.org/3/library/socket.html#socket.socket.recvmsg | |
def recv_fds(sock, msglen, maxfds): | |
fds = array.array("i") # Array of ints | |
msg, ancdata, flags, addr = sock.recvmsg(msglen, socket.CMSG_LEN(maxfds * fds.itemsize)) | |
for cmsg_level, cmsg_type, cmsg_data in ancdata: | |
if (cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS): | |
# Append data, ignoring any truncated integers at the end. | |
fds.fromstring(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) | |
return msg, list(fds) | |
# Function from https://docs.python.org/3/library/socket.html#socket.socket.sendmsg | |
def send_fds(sock, msg, fds): | |
return sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", fds))]) | |
def receiver(socket_filename, source_filename): | |
""" | |
Drop permissions, open the socket file, block waiting for a file descriptor from socket, read from file descriptor. | |
""" | |
logger.info('Starting privledges: %d/%d = %s/%s' % \ | |
(os.getuid(), | |
os.getgid(), | |
pwd.getpwuid(os.getuid())[0], | |
grp.getgrgid(os.getgid())[0]), extra={"side": "<== RECV"}) | |
if os.getuid() == 0: | |
running_uid = pwd.getpwnam("nobody")[2] | |
running_gid = grp.getgrnam("nogroup")[2] | |
os.setgid(running_gid) | |
os.setuid(running_uid) | |
logger.info('Dropped privledges: currently %d/%d = %s/%s' % \ | |
(os.getuid(), | |
os.getgid(), | |
pwd.getpwuid(os.getuid())[0], | |
grp.getgrgid(os.getgid())[0]), extra={"side": "<== RECV"}) | |
try: | |
with open(source_filename): | |
logger.info("This process *does* have the ability to read the source file '%s' - it just won't." % source_filename, extra={"side": "<== RECV"}) | |
except Exception as e: | |
logger.info("Proof that this process cannot open '%s': %s" % (source_filename, e), extra={"side": "<== RECV"}) | |
logger.info("Receiver delaying creation of socket to demonstrate sender retries...", extra={"side": "<== RECV"}) | |
time.sleep(2) | |
logger.info("Binding to (and creating) AF_UNIX socket socket file '%s'" % socket_filename, extra={"side": "<== RECV"}) | |
sock = socket.socket(family=socket.AF_UNIX) | |
sock.bind(socket_filename) | |
sock.listen() | |
logger.info("Socket listening %s" % sock, extra={"side": "<== RECV"}) | |
# Waste a file descriptor, so the fd numbers on source and receive sides don't match (they'll be both 6 by default) | |
leak_a_file_descriptor = open("/etc/hosts") | |
if not hasattr(sock, "recvmsg"): | |
raise RuntimeError("We don't have a `Socket.recvmsg` in this implementation of python (eg, system python 2.7 on OSX") | |
# Accept exactly 1 connection | |
client, info = sock.accept() | |
logger.info("Connected, client=%s" % client, extra={"side": "<== RECV"}) | |
logger.info("Blocking until message is received", extra={"side": "<== RECV"}) | |
msg, fds = recv_fds(client, 100, 4) | |
logger.info("Received message msg=%s fds=%s" % (msg, fds), extra={"side": "<== RECV"}) | |
f = os.fdopen(fds[0]) | |
logger.info("Opened fd %d => %s" % (fds[0], f), extra={"side": "<== RECV"}) | |
logger.info("Printing first 5 lines of file content", extra={"side": "<== RECV"}) | |
for line, n in zip(f, range(5)): | |
logger.info("%d ... %s" % (n, line.strip()), extra={"side": "<== RECV"}) | |
f.close() | |
def sender(socket_filename, source_filename): | |
""" | |
Open socket file, open `source_filename`, send the open file descriptor to sibling process via socket. | |
""" | |
sock = socket.socket(family=socket.AF_UNIX) | |
logger.info("Connecting to socket file '%s'" % socket_filename, extra={"side": "SEND ==>"}) | |
# Simple retry; receiver may not have created the socket_filename yet. | |
e = None | |
for _ in range(10): | |
try: | |
sock.connect(socket_filename) | |
break | |
except OSError as e: | |
logger.error("Socket file '%s' not available yet, try %d/10 (%s)" % (socket_filename, _, e), extra={"side": "SEND ==>"}) | |
time.sleep(0.5) | |
pass | |
else: # nobreak | |
raise e | |
logger.info("Connected", extra={"side": "SEND ==>"}) | |
logger.info("Sender delaying to demonstrate a blocking receiver...", extra={"side": "SEND ==>"}) | |
time.sleep(4) | |
with open(source_filename, mode='r') as f: | |
logger.info("Opened source file %s" % source_filename, extra={"side": "SEND ==>"}) | |
file_descriptor_int = f.fileno() | |
logger.info("Sending file descriptors %d" % file_descriptor_int, extra={"side": "SEND ==>"}) | |
send_fds(sock, b"some payload", [file_descriptor_int]) | |
if __name__ == "__main__": | |
socket_filename = sys.argv[1] | |
source_filename = sys.argv[2] | |
logger.info("Starting", extra={"side": "main"}) | |
p_sender = multiprocessing.Process(target=sender, args=(socket_filename, source_filename)) | |
p_sender.start() | |
try: | |
receiver(socket_filename, source_filename) | |
p_sender.join() | |
finally: | |
p_sender.terminate() | |
logger.info("Deleting socket file '%s'" % socket_filename, extra={"side": "main"}) | |
os.unlink(socket_filename) | |
logger.info("Done", extra={"side": "main"}) |
you can also use multiprocessing.reduction.sendfds
function.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output