X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=blobdiff_plain;f=fishdescriptor%2Fpy%2Ffishdescriptor%2Ffish.py;fp=fishdescriptor%2Fpy%2Ffishdescriptor%2Ffish.py;h=95694ff2eaceac3eb59c1b0d672eadf9405c0885;hp=101a461f7c1e52be2f1487be03a6061787e8d288;hb=60a7d2d5447823cab952d84559bc94371bd3176e;hpb=16d5c02a1c894a2fa2cb31c6e3c916418ce71747 diff --git a/fishdescriptor/py/fishdescriptor/fish.py b/fishdescriptor/py/fishdescriptor/fish.py index 101a461..95694ff 100644 --- a/fishdescriptor/py/fishdescriptor/fish.py +++ b/fishdescriptor/py/fishdescriptor/fish.py @@ -2,38 +2,132 @@ import socket import subprocess +import os +import pwd + +def _shuffle_fd3(): + os.dup(1,3) + os.dup(2,1) class Donor(): - def __init__(s): - pass + def __init__(d, pid): + d._pid = pid + d._sp = subprocess.Popen( + preexec_fn = _suffle_fd3, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + close_fds = False, + args = ['gdb', '-p', pid, '-batch', '-ex' + 'python import fishdescriptor.indonor as id;'+ + ' id.DonorImplementation().eval_loop()' + ] + ) - def _ancilmsg(fds): - ''' + def _eval_integer(d, expr): + l = d._sp.stdin.readline() + if l != '!\n': raise RuntimeError("indonor said %s" % repr(l)) + d._sp.stdout.write(expr + '\n') + d._sp.stdout.flush() + l = d._sp.stdin.readline().rstrip('\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 + print join ", ", unpack "C*", $m; ''' + ap = subprocess.Popen( + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + args = ['perl','-we',perl_script] + fds + ) + (output, dummy) = ap.communicate() + return output + + 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 = s.accept() + (msg, ancil, flags, sender) = s2.recvmsg(1, ancil_len) + + got_fds = [ ] + + for clvl, ctype, cdata in ancil: + if clvl == socket.SOL_SOCKET and ctype == socket.SCM_RIGHTS: + got_fds += cdata # need to trim any surplus, and unpack + + finally: + if s is not None: s.close() + if s2 is not None: s2.close() -def _geteuid(pid): - def _shuffle_fd3(): - dup(1,3) - dup(2,1) - sp = subprocess.Popen(preexec_fn = _suffle_fd3, - stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, - close_fds = False, - args = ['gdb', '-p', pid, '-batch', '-ex' - 'python import os; os.fdopen(30,"w").write("%d\n" % ' - +'gdb.parse_and_eval("(uid_t)geteuid()"))']) - (output, dummy) = sp.communicate() - -2009 - -def fish(pid, fds): - # -> list of fds in our process - sockname = '/run/user/' + - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - -def deliver(pid, fds, path): - gdb -batch -p %d -ex ' + try: os.remove(our_sockname) + except FileNotFoundError: pass