# class for use inside gdb which is debugging the donor process
import gdb
+import copy
import os
-
-try:
- rtld_now = os.RTLD_NOW
-except AttributeError:
- try:
- import dl
- rtld_now = dl.RTLD_NOW
- except ImportError:
- # some installations lack dl, it seems
- # https://bugs.launchpad.net/ubuntu/+source/python2.7/+bug/1721840
- # bodge:
- rtld_now = 2
+import sys
def _string_escape_for_c(s):
- if not isinstance(s, bytes):
- s = s.encode('utf-8') # sigh, python 2/3 compat
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 out
-class DonorImplementation(preloaded=False):
- def __init__(self):
- # works on the current gdb.Inferior
- # ideally should be reused if the same process is targetd
- self._open = None
- self._sym = None
- if preloaded:
- self._sym = 'fishdescriptor_donate'
+# constructing values
+
+def _lit_integer(v):
+ return '%d' % v
+
+def _lit_aggregate_uncasted(val_lit_strs):
+ return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
+
+def _lit_string_uncasted(s):
+ if not isinstance(s, bytes):
+ s = s.encode('utf-8') # sigh, python 2/3 compat
+ b = bytearray(s)
+ return _lit_aggregate_uncasted(map(_lit_integer, b) + [ '0' ])
+
+def _lit_array(elemtype, val_lit_strs):
+ return (
+ '((%s[%d])%s)' %
+ (elem_type, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
+ )
+
+def _lit_addressof(v):
+ return '&(%s)' % v
- def _func(self, functype, funcname, realargs):
+def _make_lit(v):
+ if isinstance(v, int):
+ return _lit_integer(v)
+ else:
+ return v # should already be an integer
+
+def parse_eval(expr):
+ sys.stderr.write("## EVAL %s\n" % repr(expr))
+ x = gdb.parse_and_eval(expr)
+ sys.stderr.write('## => %s\n' % x)
+ sys.stderr.flush()
+ return x
+
+class DonorStructLayout():
+ def __init__(l, typename):
+ x = parse_eval('(%s){ }' % typename)
+ l._typename = typename
+ l._template = [ ]
+ l._posns = { }
+ for f in x.type.fields():
+ l._posns[f.name] = len(l._template)
+ try: f.type.fields(); blank = '{ }'
+ except AttributeError: blank = '0'
+ l._template.append(blank)
+
+ def substitute(l, values):
+ build = copy.deepcopy(l._template)
+ for (k,v) in values.items():
+ build[ l._posns[k] ] = _make_lit(v)
+ return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
+
+class DonorImplementation():
+ 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 _find_fields(typename):
+ try:
+ fields = di._structs[typename]
+ except AttributeError:
+ fields = DonorStructLayout(typename)
+ di._structs[typename] = fields
+ return fields
+
+ def _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(di, functype, funcname, realargs):
expr = '((%s) %s) %s' % (functype, funcname, realargs)
- return gdb.parse_and_eval(expr)
-
- def _dlfunc(self, functype, funcname, realargs):
- r = self._func(functype,funcname,realargs)
- if not r:
- err = self._func('char* (*)(void)', 'dlerror', '()')
- if not err:
- err = 'dlerror said NULL!'
- else:
- err = err.string()
- raise RuntimeError("%s failed: %s" % (funcname, err))
- return r
-
- def _dlopen(self):
- if self._open is not None: return
- o = self._dlfunc('void* (*)(const char*, int)',
- 'dlopen',
- '("libfishdescriptor-donate.so.1.0", %s)' % rtld_now)
- self._open = '((void*)%s)' % o
-
- def _dlsym(self):
- if self._sym is not None: return
- self._dlopen()
- self._sym = self._dlfunc('void* (*)(void*, const char*)'
- 'dlsym',
- '(%s, "fishdescriptor_donate")' % self._open)
-
- def donate(self, path, fds):
- self._dlsym()
- r = self._func('int (*)(const char*, const int*)',
- self._sym,
- '("%s", (int[%d]){ %s, -1 })'
- % (_string_escape_for_c(path),
- len(fds) + 1,
- ', '.join(["%d" % fd for fd in fds])))
- if r:
- err = self._func('char* (*)(int)',
- strerror,
- r)
- if not err:
- err = 'strerror failed'
- else:
- err = err.string()
- raise RuntimeError("donate failed: %s" % err)
+ return parse_eval(expr)
+
+ def _must_func(di, functype, funcname, realargs):
+ retval = di._func(functype, funcname, realargs)
+ if retval < 0:
+ errnoval = parse_eval('errno')
+ raise RuntimeError("%s gave errno=%d `%s'" %
+ (funcname, errnoval, os.strerror(errnoval)))
+ return retval
+
+ # wrappers for the syscalls that do what we want
+
+ def _sendmsg(di, carrier, control_msg):
+ iov_base = _lit_array('int', map(str,fds))
+ iov = di._make('struct iovec', {
+ 'iov_base': iov_base,
+ 'iov_len' : len(fds),
+ })
+
+ 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),
+ })
+
+ di._must_func(
+ 'ssize_t (*)(int, const struct msghdr*, int flags)',
+ 'sendmsg',
+ '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
+ )
+
+ def _socket(di):
+ return di._must_func(
+ 'int (*)(int, int, int)',
+ 'socket',
+ '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
+ )
+
+ def _connect(di, fd, path):
+ addr = di._make('struct sockaddr_un', {
+ 'sun_family' : _lit_integer(socket.AF_UNIX),
+ 'sun_path' : _lit_string_uncasted(path),
+ })
+
+ 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(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 = parse_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 = parse_eval('errno')
+
+ def _errno_restore(di):
+ to_restore = di._saved_errno
+ di._saved_errno = None
+ if to_restore is not None:
+ parse_eval('errno = %d' % to_restore)
+
+ def _result(di, output):
+ sys.stderr.write("#> %s" % output)
+ di._result_stream.write(output)
+ di._result_stream.flush()
+
+ # main entrypoints
+
+ 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
+ try:
+ 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: di._close(carrier)
+ except Exception: pass
+ 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, int('0700', 8))
+ finally:
+ di._errno_restore()
+
+ di._result('%d\n' % val)
+
+ def _protocol_read(di):
+ input = sys.stdin.readline().rstrip('\n')
+ sys.stderr.write("#< %s\n" % input)
+ return input
+
+ def eval_loop(di):
+ while True:
+ di._result('!\n')
+ cmd = di._protocol_read()
+ eval(cmd)