|
|
@ -71,6 +71,16 @@ from mitogen.core import IOLOG
|
|
|
|
|
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# #410: we must avoid the use of socketpairs if SELinux is enabled.
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
fp = open('/sys/fs/selinux/enforce', 'rb')
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
SELINUX_ENABLED = bool(int(fp.read()))
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
fp.close()
|
|
|
|
|
|
|
|
except IOError:
|
|
|
|
|
|
|
|
SELINUX_ENABLED = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
next
|
|
|
|
next
|
|
|
@ -278,6 +288,38 @@ def create_socketpair(size=None):
|
|
|
|
return parentfp, childfp
|
|
|
|
return parentfp, childfp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_best_pipe(escalates_privilege=False):
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
By default we prefer to communicate with children over a UNIX socket, as a
|
|
|
|
|
|
|
|
single file descriptor can represent bidirectional communication, and a
|
|
|
|
|
|
|
|
cross-platform API exists to align buffer sizes with the needs of the
|
|
|
|
|
|
|
|
library.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SELinux prevents us setting up a privileged process to inherit an AF_UNIX
|
|
|
|
|
|
|
|
socket, a facility explicitly designed as a better replacement for pipes,
|
|
|
|
|
|
|
|
because at some point in the mid 90s it might have been commonly possible
|
|
|
|
|
|
|
|
for AF_INET sockets to end up undesirably connected to a privileged
|
|
|
|
|
|
|
|
process, so let's make up arbitrary rules breaking all sockets instead.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If SELinux is detected, fall back to using pipes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:returns:
|
|
|
|
|
|
|
|
`(parent_rfp, child_wfp, child_rfp, parent_wfp)`
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if (not escalates_privilege) or (not SELINUX_ENABLED):
|
|
|
|
|
|
|
|
parentfp, childfp = create_socketpair()
|
|
|
|
|
|
|
|
return parentfp, childfp, childfp, parentfp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parent_rfp, child_wfp = mitogen.core.pipe()
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
child_rfp, parent_wfp = mitogen.core.pipe()
|
|
|
|
|
|
|
|
return parent_rfp, child_wfp, child_rfp, parent_wfp
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
parent_rfp.close()
|
|
|
|
|
|
|
|
child_wfp.close()
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def popen(**kwargs):
|
|
|
|
def popen(**kwargs):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Wrap :class:`subprocess.Popen` to ensure any global :data:`_preexec_hook`
|
|
|
|
Wrap :class:`subprocess.Popen` to ensure any global :data:`_preexec_hook`
|
|
|
@ -292,7 +334,8 @@ def popen(**kwargs):
|
|
|
|
return subprocess.Popen(preexec_fn=preexec_fn, **kwargs)
|
|
|
|
return subprocess.Popen(preexec_fn=preexec_fn, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None):
|
|
|
|
def create_child(args, merge_stdio=False, stderr_pipe=False,
|
|
|
|
|
|
|
|
escalates_privilege=False, preexec_fn=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Create a child process whose stdin/stdout is connected to a socket.
|
|
|
|
Create a child process whose stdin/stdout is connected to a socket.
|
|
|
|
|
|
|
|
|
|
|
@ -306,22 +349,27 @@ def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None):
|
|
|
|
:param bool stderr_pipe:
|
|
|
|
:param bool stderr_pipe:
|
|
|
|
If :data:`True` and `merge_stdio` is :data:`False`, arrange for
|
|
|
|
If :data:`True` and `merge_stdio` is :data:`False`, arrange for
|
|
|
|
`stderr` to be connected to a separate pipe, to allow any ongoing debug
|
|
|
|
`stderr` to be connected to a separate pipe, to allow any ongoing debug
|
|
|
|
logs generated by e.g. SSH to be outpu as the session progresses,
|
|
|
|
logs generated by e.g. SSH to be output as the session progresses,
|
|
|
|
without interfering with `stdout`.
|
|
|
|
without interfering with `stdout`.
|
|
|
|
|
|
|
|
:param bool escalates_privilege:
|
|
|
|
|
|
|
|
If :data:`True`, the target program may escalate privileges, causing
|
|
|
|
|
|
|
|
SELinux to disconnect AF_UNIX sockets, so avoid those.
|
|
|
|
|
|
|
|
:param function preexec_fn:
|
|
|
|
|
|
|
|
If not :data:`None`, a function to run within the post-fork child
|
|
|
|
|
|
|
|
before executing the target program.
|
|
|
|
:returns:
|
|
|
|
:returns:
|
|
|
|
:class:`Process` instance.
|
|
|
|
:class:`Process` instance.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe(
|
|
|
|
|
|
|
|
escalates_privilege=escalates_privilege
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
parentfp, childfp = create_socketpair()
|
|
|
|
parentfp, childfp = create_socketpair()
|
|
|
|
# When running under a monkey patches-enabled gevent, the socket module
|
|
|
|
|
|
|
|
# yields descriptors who already have O_NONBLOCK, which is persisted across
|
|
|
|
|
|
|
|
# fork, totally breaking Python. Therefore, drop O_NONBLOCK from Python's
|
|
|
|
|
|
|
|
# future stdin fd.
|
|
|
|
|
|
|
|
mitogen.core.set_block(childfp.fileno())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stderr = None
|
|
|
|
stderr = None
|
|
|
|
stderr_r = None
|
|
|
|
stderr_r = None
|
|
|
|
if merge_stdio:
|
|
|
|
if merge_stdio:
|
|
|
|
stderr = childfp
|
|
|
|
stderr = child_wfp
|
|
|
|
elif stderr_pipe:
|
|
|
|
elif stderr_pipe:
|
|
|
|
stderr_r, stderr = mitogen.core.pipe()
|
|
|
|
stderr_r, stderr = mitogen.core.pipe()
|
|
|
|
mitogen.core.set_cloexec(stderr_r.fileno())
|
|
|
|
mitogen.core.set_cloexec(stderr_r.fileno())
|
|
|
@ -329,27 +377,33 @@ def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
proc = popen(
|
|
|
|
proc = popen(
|
|
|
|
args=args,
|
|
|
|
args=args,
|
|
|
|
stdin=childfp,
|
|
|
|
stdin=child_rfp,
|
|
|
|
stdout=childfp,
|
|
|
|
stdout=child_wfp,
|
|
|
|
stderr=stderr,
|
|
|
|
stderr=stderr,
|
|
|
|
close_fds=True,
|
|
|
|
close_fds=True,
|
|
|
|
preexec_fn=preexec_fn,
|
|
|
|
preexec_fn=preexec_fn,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
except:
|
|
|
|
childfp.close()
|
|
|
|
child_rfp.close()
|
|
|
|
parentfp.close()
|
|
|
|
child_wfp.close()
|
|
|
|
|
|
|
|
parent_rfp.close()
|
|
|
|
|
|
|
|
parent_wfp.close()
|
|
|
|
if stderr_pipe:
|
|
|
|
if stderr_pipe:
|
|
|
|
stderr.close()
|
|
|
|
stderr.close()
|
|
|
|
stderr_r.close()
|
|
|
|
stderr_r.close()
|
|
|
|
raise
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
childfp.close()
|
|
|
|
child_rfp.close()
|
|
|
|
|
|
|
|
child_wfp.close()
|
|
|
|
if stderr_pipe:
|
|
|
|
if stderr_pipe:
|
|
|
|
stderr.close()
|
|
|
|
stderr.close()
|
|
|
|
|
|
|
|
|
|
|
|
LOG.debug('create_child() child %d fd %d, parent %d, cmd: %s',
|
|
|
|
return PopenProcess(
|
|
|
|
proc.pid, parentfp.fileno(), os.getpid(), Argv(args))
|
|
|
|
proc=proc,
|
|
|
|
return PopenProcess(proc, stdin=parentfp, stdout=parentfp, stderr=stderr_r)
|
|
|
|
stdin=parent_wfp,
|
|
|
|
|
|
|
|
stdout=parent_rfp,
|
|
|
|
|
|
|
|
stderr=stderr_r,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _acquire_controlling_tty():
|
|
|
|
def _acquire_controlling_tty():
|
|
|
@ -461,12 +515,14 @@ def tty_create_child(args):
|
|
|
|
raise
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
slave_fp.close()
|
|
|
|
slave_fp.close()
|
|
|
|
LOG.debug('tty_create_child() child %d fd %d, parent %d, cmd: %s',
|
|
|
|
return PopenProcess(
|
|
|
|
proc.pid, master_fp.fileno(), os.getpid(), Argv(args))
|
|
|
|
proc=proc,
|
|
|
|
return PopenProcess(proc, stdin=master_fp, stdout=master_fp)
|
|
|
|
stdin=master_fp,
|
|
|
|
|
|
|
|
stdout=master_fp,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def hybrid_tty_create_child(args):
|
|
|
|
def hybrid_tty_create_child(args, escalates_privilege=False):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Like :func:`tty_create_child`, except attach stdin/stdout to a socketpair
|
|
|
|
Like :func:`tty_create_child`, except attach stdin/stdout to a socketpair
|
|
|
|
like :func:`create_child`, but leave stderr and the controlling TTY
|
|
|
|
like :func:`create_child`, but leave stderr and the controlling TTY
|
|
|
@ -479,20 +535,25 @@ def hybrid_tty_create_child(args):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
master_fp, slave_fp = openpty()
|
|
|
|
master_fp, slave_fp = openpty()
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
parentfp, childfp = create_socketpair()
|
|
|
|
parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe(
|
|
|
|
|
|
|
|
escalates_privilege=escalates_privilege,
|
|
|
|
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
mitogen.core.set_block(childfp)
|
|
|
|
mitogen.core.set_block(child_rfp)
|
|
|
|
|
|
|
|
mitogen.core.set_block(child_wfp)
|
|
|
|
proc = popen(
|
|
|
|
proc = popen(
|
|
|
|
args=args,
|
|
|
|
args=args,
|
|
|
|
stdin=childfp,
|
|
|
|
stdin=child_rfp,
|
|
|
|
stdout=childfp,
|
|
|
|
stdout=child_wfp,
|
|
|
|
stderr=slave_fp,
|
|
|
|
stderr=slave_fp,
|
|
|
|
preexec_fn=_acquire_controlling_tty,
|
|
|
|
preexec_fn=_acquire_controlling_tty,
|
|
|
|
close_fds=True,
|
|
|
|
close_fds=True,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
except:
|
|
|
|
parentfp.close()
|
|
|
|
parent_rfp.close()
|
|
|
|
childfp.close()
|
|
|
|
child_wfp.close()
|
|
|
|
|
|
|
|
parent_wfp.close()
|
|
|
|
|
|
|
|
child_rfp.close()
|
|
|
|
raise
|
|
|
|
raise
|
|
|
|
except:
|
|
|
|
except:
|
|
|
|
master_fp.close()
|
|
|
|
master_fp.close()
|
|
|
@ -500,10 +561,14 @@ def hybrid_tty_create_child(args):
|
|
|
|
raise
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
slave_fp.close()
|
|
|
|
slave_fp.close()
|
|
|
|
childfp.close()
|
|
|
|
child_rfp.close()
|
|
|
|
LOG.debug('hybrid_tty_create_child() pid=%d stdio=%d, tty=%d, cmd: %s',
|
|
|
|
child_wfp.close()
|
|
|
|
proc.pid, parentfp.fileno(), master_fp.fileno(), Argv(args))
|
|
|
|
return PopenProcess(
|
|
|
|
return PopenProcess(proc, stdin=parentfp, stdout=parentfp, stderr=master_fp)
|
|
|
|
proc=proc,
|
|
|
|
|
|
|
|
stdin=parent_wfp,
|
|
|
|
|
|
|
|
stdout=parent_rfp,
|
|
|
|
|
|
|
|
stderr=master_fp,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Timer(object):
|
|
|
|
class Timer(object):
|
|
|
@ -1425,6 +1490,7 @@ class Connection(object):
|
|
|
|
|
|
|
|
|
|
|
|
def start_child(self):
|
|
|
|
def start_child(self):
|
|
|
|
args = self.get_boot_command()
|
|
|
|
args = self.get_boot_command()
|
|
|
|
|
|
|
|
LOG.debug('command line for %r: %s', self, Argv(args))
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return self.create_child(args=args, **self.create_child_args)
|
|
|
|
return self.create_child(args=args, **self.create_child_args)
|
|
|
|
except OSError:
|
|
|
|
except OSError:
|
|
|
|