Skip to content

Instantly share code, notes, and snippets.

@ellieayla
Created February 6, 2018 03:14
Show Gist options
  • Save ellieayla/69148d471bdd1b60ba66d17b26a02afd to your computer and use it in GitHub Desktop.
Save ellieayla/69148d471bdd1b60ba66d17b26a02afd to your computer and use it in GitHub Desktop.
#!/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"})
@ellieayla
Copy link
Author

Example output

sudo python3.6 send-fd-example.py /tmp/socket /etc/sudoers
2018-02-05 22:07:48,647 pid=3586 main: Starting
2018-02-05 22:07:48,665 pid=3586 <== RECV: Starting privledges: 0/0 = root/wheel
2018-02-05 22:07:48,667 pid=3586 <== RECV: Dropped privledges: currently 4294967294/-1 = nobody/nogroup
2018-02-05 22:07:48,668 pid=3586 <== RECV: Proof that this process cannot open '/etc/sudoers': [Errno 13] Permission denied: '/etc/sudoers'
2018-02-05 22:07:48,668 pid=3586 <== RECV: Receiver delaying creation of socket to demonstrate sender retries...
2018-02-05 22:07:48,666 pid=3587 SEND ==>: Connecting to socket file '/tmp/socket'
2018-02-05 22:07:48,669 pid=3587 SEND ==>: Socket file '/tmp/socket' not available yet, try 0/10 ([Errno 2] No such file or directory)
2018-02-05 22:07:49,171 pid=3587 SEND ==>: Socket file '/tmp/socket' not available yet, try 1/10 ([Errno 2] No such file or directory)
2018-02-05 22:07:49,673 pid=3587 SEND ==>: Socket file '/tmp/socket' not available yet, try 2/10 ([Errno 2] No such file or directory)
2018-02-05 22:07:50,174 pid=3587 SEND ==>: Socket file '/tmp/socket' not available yet, try 3/10 ([Errno 2] No such file or directory)
2018-02-05 22:07:50,669 pid=3586 <== RECV: Binding to (and creating) AF_UNIX socket socket file '/tmp/socket'
2018-02-05 22:07:50,670 pid=3586 <== RECV: Socket listening <socket.socket fd=4, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=/tmp/socket>
2018-02-05 22:07:50,676 pid=3586 <== RECV: Connected, client=<socket.socket fd=6, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=/tmp/socket>
2018-02-05 22:07:50,676 pid=3586 <== RECV: Blocking until message is received
2018-02-05 22:07:50,676 pid=3587 SEND ==>: Connected
2018-02-05 22:07:50,677 pid=3587 SEND ==>: Sender delaying to demonstrate a blocking receiver...
2018-02-05 22:07:54,679 pid=3587 SEND ==>: Opened source file /etc/sudoers
2018-02-05 22:07:54,679 pid=3587 SEND ==>: Sending file descriptors 6
2018-02-05 22:07:54,681 pid=3586 <== RECV: Received message msg=b'some payload' fds=[7]
2018-02-05 22:07:54,682 pid=3586 <== RECV: Opened fd 7 => <_io.TextIOWrapper name=7 mode='r' encoding='UTF-8'>
2018-02-05 22:07:54,682 pid=3586 <== RECV: Printing first 5 lines of file content
2018-02-05 22:07:54,682 pid=3586 <== RECV: 0 ... #
2018-02-05 22:07:54,683 pid=3586 <== RECV: 1 ... # Sample /etc/sudoers file.
2018-02-05 22:07:54,683 pid=3586 <== RECV: 2 ... #
2018-02-05 22:07:54,683 pid=3586 <== RECV: 3 ... # This file MUST be edited with the 'visudo' command as root.
2018-02-05 22:07:54,683 pid=3586 <== RECV: 4 ... #
2018-02-05 22:07:54,685 pid=3586 main: Deleting socket file '/tmp/socket'
2018-02-05 22:07:54,685 pid=3586 main: Done

@ellieayla
Copy link
Author

@GreatBahram
Copy link

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