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
def _string_escape_for_c(s):
out = ''
for c in bytearray(s): # gets us bytes in py2 and py3
- if c == ord('\\') or c == ord('=') or c < 32 or c > 126:
+ if c == ord('\\') or c == ord('"') or c < 32 or c > 126:
out += '\\x%02x' % c
else:
out += chr(c)
return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
class DonorImplementation():
- def __init__(d):
- d._structs = { }
+ def __init__(di):
+ di._structs = { }
+ di._saved_errno = None
+ di._result_stream = os.fdopen(3, 'w')
# assembling structs
# sigh, we have to record the order of the arguments!
- def d._find_fields(typename):
+ def di._find_fields(typename):
try:
- fields = d._structs[typename]
+ fields = di._structs[typename]
except AttributeError:
fields = DonorStructLayout(typename)
- d._structs[typename] = fields
+ di._structs[typename] = fields
return fields
- def d._make(typename, values):
- fields = d._find_fields(typename)
- return fieldd.substitute(values)
+ def di._make(typename, values):
+ fields = di._find_fields(typename)
+ return fields.substitute(values)
# calling functions (need to cast the function name to the right
# type in case maybe gdb doesn't know the type)
- def _func(d, functype, funcname, realargs):
+ def _func(di, functype, funcname, realargs):
expr = '((%s) %s) %s' % (functype, funcname, realargs)
return gdb.parse_and_eval(expr)
- def _must_func(d, functype, funcname, realargs):
- retval = d._func(functype, funcname, realargs)
+ def _must_func(di, functype, funcname, realargs):
+ retval = di._func(functype, funcname, realargs)
if retval < 0:
errnoval = gdb.parse_and_eval('errno')
raise RuntimeError("%s gave errno=%d `%s'" %
- (funcname, errnoval, od.strerror(errnoval)))
+ (funcname, errnoval, os.strerror(errnoval)))
# wrappers for the syscalls that do what we want
- def _sendmsg(d, carrier, control_msg):
+ def _sendmsg(di, carrier, control_msg):
iov_base = _lit_array('int', map(str,fds))
- iov = d._make('struct iovec', {
+ iov = di._make('struct iovec', {
'iov_base': iov_base,
'iov_len' : len(fds),
})
- msg = d._make('struct msghdr', {
+ msg = di._make('struct msghdr', {
'msg_iov' : _lit_addressof(iov),
'msg_iovlen' : 1,
'msg_control' : _lit_array('char', control_msg),
'msg_controllen': len(control_msg),
})
- d._must_func(
+ di._must_func(
'ssize_t (*)(int, const struct msghdr*, int flags)',
'sendmsg',
'(%s, %s, 0)' % (carrier, _lit_addressof(msg))
)
- def _socket(d):
- return d._must_func(
+ def _socket(di):
+ return di._must_func(
'int (*)(int, int, int)',
'socket',
'(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
)
- def _connect(d, fd, path):
- addr = d._make('struct sockaddr_un', {
+ def _connect(di, fd, path):
+ addr = di._make('struct sockaddr_un', {
'sun_family' : _lit_integer(socket.AF_UNIX),
'sun_path' : _lit_string_uncasted(path),
})
- d._must_func(
+ di._must_func(
'int (*)(int, const struct sockaddr*, socklen_t)',
'connect',
'(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
% (fd, _lit_addressof(addr)
)
- def _close(d, fd):
- d._must_func('int (*)(int)', 'close', '(%d)' % fd)
+ def _close(di, fd):
+ di._must_func('int (*)(int)', 'close', '(%d)' % fd)
+
+ def _mkdir(di, path, mode):
+ r = di._func(
+ 'int (*)(const char*, mode_t)',
+ 'mkdir',
+ '("%s", %d)' % (_string_escape_for_c(path), mode)
+ )
+ if r < 0:
+ errnoval = gdb.parse_and_eval('errno')
+ if errnoval != os.errno.EEXIST:
+ raise RuntimeError("mkdir %s failed: `%s'" %
+ (repr(path), os.strerror(errnoval)))
+ return 0
+ return 1
+
+ def _errno_save(di):
+ di._saved_errno = gdb.parse_and_eval('errno')
+
+ def _errno_restore(di):
+ to_restore = di._saved_errno
+ di._saved_errno = None
+ if to_restore is not None:
+ gdb.parse_and_eval('errno = %d' % to_restore))
# main entrypoints
- def donate(d, path, control_msg):
+ def result(di, output):
+ di._result_stram.write(output)
+ di._result_stram.flush()
+
+ def donate(di, path, control_msg):
# control_msg is an array of integers being the ancillary data
# array ("control") for sendmsg, and hence specifies which fds
# to pass
carrier = None
- errnoval = None
try:
- errnoval = gdb.parse_and_eval('errno')
- carrier = d._socket()
- d._connect(carrier, path)
- d._sendmsg(carrier, control_msg)
- d._close(carrier)
+ di._errno_save()
+ carrier = di._socket()
+ di._connect(carrier, path)
+ di._sendmsg(carrier, control_msg)
+ di._close(carrier)
carrier = None
finally:
if carrier is not None:
- try: d._close(carrier)
+ try: di._close(carrier)
except Exception: pass
- if errnoval is not None:
- gdb.parse_and_eval('errno = %d' % errnoval)
+ di._errno_restore()
+
+ di._result('1\n')
+
+ def geteuid(di):
+ try:
+ di._errno_save()
+ val = di._must_func('uid_t (*)(void)', 'geteuid', '()')
+ finally:
+ di._errno_restore()
+
+ di._result('%d\n' % val)
+
+ def mkdir(di, path):
+ try:
+ di._errno_save()
+ val = di._mkdir(path, '0700')
+ finally:
+ di._errno_restore()
+
+ di._result('%d\n' % val)
+
+ def _protocol_read(di):
+ return sys.stdin.readline().rstrip('\n')
+
+ def eval_loop(di):
+ while True:
+ di._result('!\n')
+ cmd = di._protocol_read()
+ eval(cmd)