qubesd: add second socket for in-dom0 internal calls

This socket (and commands) are not exposed to untrusted input, so no
need to extensive sanitization. Also, there is no need to provide a
stable API here, as those methods are used internally only.

QubesOS/qubes-issues#853
This commit is contained in:
Marek Marczykowski-Górecki 2017-03-21 11:48:20 +01:00
parent c9b5d0ab15
commit 83526a28d3
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
4 changed files with 111 additions and 7 deletions

84
qubes/mgmtinternal.py Normal file
View File

@ -0,0 +1,84 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
''' Internal interface for dom0 components to communicate with qubesd. '''
import asyncio
import json
import qubes.mgmt
import qubes.vm.dispvm
api = qubes.mgmt.api
class QubesInternalMgmt(qubes.mgmt.AbstractQubesMgmt):
''' Communication interface for dom0 components,
by design the input here is trusted.'''
#
# PRIVATE METHODS, not to be called via RPC
#
#
# ACTUAL RPC CALLS
#
@api('mgmtinternal.GetSystemInfo', no_payload=True)
@asyncio.coroutine
def getsysteminfo(self):
assert self.dest.name == 'dom0'
assert not self.arg
system_info = {'domains': {
domain.name: {
'tags': list(domain.tags),
'type': domain.__class__.__name__,
'dispvm_allowed': getattr(domain, 'dispvm_allowed', False),
'default_dispvm': (str(domain.default_dispvm) if
domain.default_dispvm else None),
'icon': str(domain.label.icon),
} for domain in self.app.domains
}}
return json.dumps(system_info)
@api('mgmtinternal.vm.Start', no_payload=True)
@asyncio.coroutine
def start(self):
assert not self.arg
yield from self.dest.start()
@api('mgmtinternal.vm.Create.DispVM', no_payload=True)
@asyncio.coroutine
def create_dispvm(self):
assert not self.arg
# TODO convert to coroutine
dispvm = qubes.vm.dispvm.DispVM.from_appvm(self.dest)
return dispvm.name
@api('mgmtinternal.vm.CleanupDispVM', no_payload=True)
@asyncio.coroutine
def cleanup_dispvm(self):
assert not self.arg
# TODO convert to coroutine
self.dest.cleanup()

View File

@ -13,17 +13,20 @@ import libvirtaio
import qubes
import qubes.mgmt
import qubes.mgmtinternal
import qubes.utils
import qubes.vm.qubesvm
QUBESD_SOCK = '/var/run/qubesd.sock'
QUBESD_INTERNAL_SOCK = '/var/run/qubesd.internal.sock'
class QubesDaemonProtocol(asyncio.Protocol):
buffer_size = 65536
header = struct.Struct('Bx')
def __init__(self, *args, app, debug=False, **kwargs):
def __init__(self, handler, *args, app, debug=False, **kwargs):
super().__init__(*args, **kwargs)
self.handler = handler
self.app = app
self.untrusted_buffer = io.BytesIO()
self.len_untrusted_buffer = 0
@ -69,7 +72,7 @@ class QubesDaemonProtocol(asyncio.Protocol):
@asyncio.coroutine
def respond(self, src, method, dest, arg, *, untrusted_payload):
try:
mgmt = qubes.mgmt.QubesMgmt(self.app, src, method, dest, arg)
mgmt = self.handler(self.app, src, method, dest, arg)
response = yield from mgmt.execute(
untrusted_payload=untrusted_payload)
@ -147,9 +150,10 @@ class QubesDaemonProtocol(asyncio.Protocol):
self.transport.write(str(exc).encode('utf-8') + b'\0')
def sighandler(loop, signame, server):
def sighandler(loop, signame, server, server_internal):
print('caught {}, exiting'.format(signame))
server.close()
server_internal.close()
loop.stop()
parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon')
@ -166,20 +170,35 @@ def main(args=None):
pass
old_umask = os.umask(0o007)
server = loop.run_until_complete(loop.create_unix_server(
functools.partial(QubesDaemonProtocol, app=args.app), QUBESD_SOCK))
functools.partial(QubesDaemonProtocol, qubes.mgmt.QubesMgmt,
app=args.app), QUBESD_SOCK))
shutil.chown(QUBESD_SOCK, group='qubes')
try:
os.unlink(QUBESD_INTERNAL_SOCK)
except FileNotFoundError:
pass
server_internal = loop.run_until_complete(loop.create_unix_server(
functools.partial(QubesDaemonProtocol,
qubes.mgmtinternal.QubesInternalMgmt,
app=args.app), QUBESD_INTERNAL_SOCK))
shutil.chown(QUBESD_INTERNAL_SOCK, group='qubes')
os.umask(old_umask)
del old_umask
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
sighandler, loop, signame, server)
sighandler, loop, signame, server, server_internal)
qubes.utils.systemd_notify()
try:
loop.run_forever()
loop.run_until_complete(server.wait_closed())
loop.run_until_complete(asyncio.wait([
server.wait_closed(),
server_internal.wait_closed(),
]))
finally:
loop.close()

View File

@ -643,4 +643,4 @@ def get_system_info():
'''
system_info = qubesd_call('dom0', 'mgmtinternal.GetSystemInfo')
return json.loads(system_info)
return json.loads(system_info.decode('utf-8'))

View File

@ -234,6 +234,7 @@ fi
%{python3_sitelib}/qubes/firewall.py
%{python3_sitelib}/qubes/log.py
%{python3_sitelib}/qubes/mgmt.py
%{python3_sitelib}/qubes/mgmtinternal.py
%{python3_sitelib}/qubes/rngdoc.py
%{python3_sitelib}/qubes/tarwriter.py
%{python3_sitelib}/qubes/utils.py