issue #410: automatically work around SELinux braindamage.
This commit is contained in:
parent
ce04fd39c9
commit
e8b1bf5909
|
@ -67,6 +67,11 @@ Mitogen for Ansible
|
|||
matching *Permission denied* errors from some versions of ``su`` running on
|
||||
heavily loaded machines.
|
||||
|
||||
* `#410 <https://github.com/dw/mitogen/issues/410>`_: Use of ``AF_UNIX``
|
||||
sockets automatically replaced with plain UNIX pipes when SELinux is
|
||||
detected, to work around a broken heuristic in popular SELinux policies that
|
||||
prevents inheriting ``AF_UNIX`` sockets across privilege domains.
|
||||
|
||||
* `#549 <https://github.com/dw/mitogen/issues/549>`_: the open file descriptor
|
||||
limit for the Ansible process is increased to the available hard limit. It is
|
||||
common for distributions to ship with a much higher hard limit than their
|
||||
|
@ -166,6 +171,7 @@ bug reports, testing, features and fixes in this release contributed by
|
|||
`Andreas Hubert <https://github.com/peshay>`_.
|
||||
`Anton Markelov <https://github.com/strangeman>`_,
|
||||
`Dave Cottlehuber <https://github.com/dch>`_,
|
||||
`El Mehdi CHAOUKI <https://github.com/elmchaouki>`_,
|
||||
`James Hogarth <https://github.com/hogarthj>`_,
|
||||
`Nigel Metheringham <https://github.com/nigelm>`_,
|
||||
`Orion Poplawski <https://github.com/opoplawski>`_,
|
||||
|
|
|
@ -71,6 +71,16 @@ from mitogen.core import IOLOG
|
|||
|
||||
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:
|
||||
next
|
||||
|
@ -278,6 +288,38 @@ def create_socketpair(size=None):
|
|||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
@ -306,22 +349,27 @@ def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None):
|
|||
:param bool stderr_pipe:
|
||||
If :data:`True` and `merge_stdio` is :data:`False`, arrange for
|
||||
`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`.
|
||||
: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:
|
||||
:class:`Process` instance.
|
||||
"""
|
||||
parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe(
|
||||
escalates_privilege=escalates_privilege
|
||||
)
|
||||
|
||||
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_r = None
|
||||
if merge_stdio:
|
||||
stderr = childfp
|
||||
stderr = child_wfp
|
||||
elif stderr_pipe:
|
||||
stderr_r, stderr = mitogen.core.pipe()
|
||||
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:
|
||||
proc = popen(
|
||||
args=args,
|
||||
stdin=childfp,
|
||||
stdout=childfp,
|
||||
stdin=child_rfp,
|
||||
stdout=child_wfp,
|
||||
stderr=stderr,
|
||||
close_fds=True,
|
||||
preexec_fn=preexec_fn,
|
||||
)
|
||||
except:
|
||||
childfp.close()
|
||||
parentfp.close()
|
||||
child_rfp.close()
|
||||
child_wfp.close()
|
||||
parent_rfp.close()
|
||||
parent_wfp.close()
|
||||
if stderr_pipe:
|
||||
stderr.close()
|
||||
stderr_r.close()
|
||||
raise
|
||||
|
||||
childfp.close()
|
||||
child_rfp.close()
|
||||
child_wfp.close()
|
||||
if stderr_pipe:
|
||||
stderr.close()
|
||||
|
||||
LOG.debug('create_child() child %d fd %d, parent %d, cmd: %s',
|
||||
proc.pid, parentfp.fileno(), os.getpid(), Argv(args))
|
||||
return PopenProcess(proc, stdin=parentfp, stdout=parentfp, stderr=stderr_r)
|
||||
return PopenProcess(
|
||||
proc=proc,
|
||||
stdin=parent_wfp,
|
||||
stdout=parent_rfp,
|
||||
stderr=stderr_r,
|
||||
)
|
||||
|
||||
|
||||
def _acquire_controlling_tty():
|
||||
|
@ -461,12 +515,14 @@ def tty_create_child(args):
|
|||
raise
|
||||
|
||||
slave_fp.close()
|
||||
LOG.debug('tty_create_child() child %d fd %d, parent %d, cmd: %s',
|
||||
proc.pid, master_fp.fileno(), os.getpid(), Argv(args))
|
||||
return PopenProcess(proc, stdin=master_fp, stdout=master_fp)
|
||||
return PopenProcess(
|
||||
proc=proc,
|
||||
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:`create_child`, but leave stderr and the controlling TTY
|
||||
|
@ -479,20 +535,25 @@ def hybrid_tty_create_child(args):
|
|||
"""
|
||||
master_fp, slave_fp = openpty()
|
||||
try:
|
||||
parentfp, childfp = create_socketpair()
|
||||
parent_rfp, child_wfp, child_rfp, parent_wfp = create_best_pipe(
|
||||
escalates_privilege=escalates_privilege,
|
||||
)
|
||||
try:
|
||||
mitogen.core.set_block(childfp)
|
||||
mitogen.core.set_block(child_rfp)
|
||||
mitogen.core.set_block(child_wfp)
|
||||
proc = popen(
|
||||
args=args,
|
||||
stdin=childfp,
|
||||
stdout=childfp,
|
||||
stdin=child_rfp,
|
||||
stdout=child_wfp,
|
||||
stderr=slave_fp,
|
||||
preexec_fn=_acquire_controlling_tty,
|
||||
close_fds=True,
|
||||
)
|
||||
except:
|
||||
parentfp.close()
|
||||
childfp.close()
|
||||
parent_rfp.close()
|
||||
child_wfp.close()
|
||||
parent_wfp.close()
|
||||
child_rfp.close()
|
||||
raise
|
||||
except:
|
||||
master_fp.close()
|
||||
|
@ -500,10 +561,14 @@ def hybrid_tty_create_child(args):
|
|||
raise
|
||||
|
||||
slave_fp.close()
|
||||
childfp.close()
|
||||
LOG.debug('hybrid_tty_create_child() pid=%d stdio=%d, tty=%d, cmd: %s',
|
||||
proc.pid, parentfp.fileno(), master_fp.fileno(), Argv(args))
|
||||
return PopenProcess(proc, stdin=parentfp, stdout=parentfp, stderr=master_fp)
|
||||
child_rfp.close()
|
||||
child_wfp.close()
|
||||
return PopenProcess(
|
||||
proc=proc,
|
||||
stdin=parent_wfp,
|
||||
stdout=parent_rfp,
|
||||
stderr=master_fp,
|
||||
)
|
||||
|
||||
|
||||
class Timer(object):
|
||||
|
@ -1425,6 +1490,7 @@ class Connection(object):
|
|||
|
||||
def start_child(self):
|
||||
args = self.get_boot_command()
|
||||
LOG.debug('command line for %r: %s', self, Argv(args))
|
||||
try:
|
||||
return self.create_child(args=args, **self.create_child_args)
|
||||
except OSError:
|
||||
|
|
|
@ -244,6 +244,9 @@ class Connection(mitogen.parent.Connection):
|
|||
diag_protocol_class = SetupProtocol
|
||||
options_class = Options
|
||||
create_child = staticmethod(mitogen.parent.hybrid_tty_create_child)
|
||||
create_child_args = {
|
||||
'escalates_privilege': True,
|
||||
}
|
||||
child_is_immediate_subprocess = False
|
||||
|
||||
def _get_name(self):
|
||||
|
|
Loading…
Reference in New Issue