X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=fishdescriptor%2Fpy%2Ffishdescriptor%2Ffish.py;h=15ad7660faa3e19861a8d08cb2dde2106348cdf2;hb=a974c5238f3a4134295a0e3f5ae5c10fbe8a62ff;hp=a363c5e5318518b2eaeefdeeecbd8396eab2b82f;hpb=e9a5383b6cd14094b162b9ccec9ab28d2085e726;p=chiark-utils.git diff --git a/fishdescriptor/py/fishdescriptor/fish.py b/fishdescriptor/py/fishdescriptor/fish.py index a363c5e..15ad766 100644 --- a/fishdescriptor/py/fishdescriptor/fish.py +++ b/fishdescriptor/py/fishdescriptor/fish.py @@ -1,10 +1,141 @@ # python 3 only import socket +import subprocess +import os +import pwd +import struct -def fish(pid, fds): - # -> list of fds in our process - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +def _shuffle_fd3(): + os.dup2(1,3) + os.dup2(2,1) -def deliver(pid, fds, path): - gdb -batch -p %d -ex ' +class Donor(): + def __init__(d, pid): + d.pid = pid + d._sp = subprocess.Popen( + preexec_fn = _shuffle_fd3, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + close_fds = False, + args = ['gdb', '-p', str(pid), '-batch', '-ex', + 'python import fishdescriptor.indonor as id;'+ + ' id.DonorImplementation().eval_loop()' + ] + ) + + def _eval_integer(d, expr): + l = d._sp.stdout.readline() + if l != b'!\n': raise RuntimeError("indonor said %s" % repr(l)) + d._sp.stdin.write(expr.encode('utf-8') + b'\n') + d._sp.stdin.flush() + l = d._sp.stdout.readline().rstrip(b'\n') + return int(l) + + def _eval_success(d, expr): + r = d._eval_integer(expr) + if r != 1: raise RuntimeError("eval of %s gave %d" % (expr, r)) + + def _geteuid(d): + return d._eval_integer('di.geteuid()') + + def _ancilmsg(d, fds): + perl_script = ''' + use strict; + use Socket; + use Socket::MsgHdr; + my $fds = pack "i*", @ARGV; + my $m = Socket::MsgHdr::pack_cmsghdr SOL_SOCKET, SCM_RIGHTS, $fds; + print join ", ", unpack "C*", $m; + ''' + ap = subprocess.Popen( + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + args = ['perl','-we',perl_script] + [str(x) for x in fds] + ) + (output, dummy) = ap.communicate() + return output.decode('utf-8') + + def donate(d, path, fds): + ancil = d._ancilmsg(fds) + d._eval_success('di.donate(%s, [ %s ])' + % (repr(path), ancil)) + return len(ancil.split(',')) + + def mkdir(d, path): + d._eval_integer('di.mkdir(%s)' + % (repr(path))) + + def _exists(d, path): + try: + os.stat(path) + return True + except OSError as oe: + if oe.errno != os.errno.ENOENT: raise oe + return False + + def _sock_dir(d, target_euid): + run_dir = '/run/user/%d' % target_euid + if d._exists(run_dir): + return run_dir + '/fishdescriptor' + + try: + pw = pwd.getpwuid(target_euid) + return pw.pw_dir + '/.fishdescriptor' + except KeyError: + pass + + raise RuntimeError( + 'cannot find good socket path - no /run/user/UID nor pw entry for target process euid %d' + % target_euid + ) + + def fish(d, fds): + # -> list of fds in our process + + euid = d._geteuid() + sockdir = d._sock_dir(euid) + d.mkdir(sockdir) + + sockname = '%s/%s,%d' % (sockdir, os.uname().nodename, d.pid) + + target_root = '/proc/%d/root' % d.pid + if not d._exists(target_root): + target_root = '' + + our_sockname = target_root + sockname + + s = None + s2 = None + + try: + try: os.remove(our_sockname) + except FileNotFoundError: pass + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.bind(our_sockname) + s.listen(1) + + ancil_len = d.donate(our_sockname, fds) + (s2, dummy) = s.accept() + (msg, ancil, flags, sender) = s2.recvmsg(1, ancil_len) + + got_fds = None + unpack_fmt = '%di' % len(fds) + + for clvl, ctype, cdata in ancil: + if clvl == socket.SOL_SOCKET and ctype == socket.SCM_RIGHTS: + assert(got_fds is None) + got_fds = struct.unpack_from(unpack_fmt, cdata) + + finally: + if s is not None: s.close() + if s2 is not None: s2.close() + + try: os.remove(our_sockname) + except FileNotFoundError: pass + + return list(got_fds) + + def detach(d): + d._sp.stdin.close()