2017-07-28 15:08:33 +02:00
|
|
|
# -*- encoding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
|
|
#
|
|
|
|
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
|
|
|
# <marmarek@invisiblethingslab.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
2017-07-28 15:08:33 +02:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2017-07-28 15:08:33 +02:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2017-07-28 15:08:33 +02:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
2017-07-28 15:08:33 +02:00
|
|
|
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Extension responsible for qvm-service framework"""
|
2017-07-28 15:08:33 +02:00
|
|
|
|
2020-02-16 18:53:42 +01:00
|
|
|
import os
|
2017-07-28 15:08:33 +02:00
|
|
|
import qubes.ext
|
2020-02-18 11:44:57 +01:00
|
|
|
import qubes.config
|
|
|
|
|
2020-02-16 16:07:44 +01:00
|
|
|
|
2017-07-28 15:08:33 +02:00
|
|
|
class ServicesExtension(qubes.ext.Extension):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""This extension export features with 'service.' prefix to QubesDB in
|
2017-07-28 15:08:33 +02:00
|
|
|
/qubes-service/ tree.
|
2020-02-16 16:07:44 +01:00
|
|
|
"""
|
|
|
|
|
2020-02-18 11:18:01 +01:00
|
|
|
@staticmethod
|
2020-03-08 09:51:30 +01:00
|
|
|
def add_dom0_service(vm, service):
|
2020-02-18 11:18:01 +01:00
|
|
|
try:
|
2020-02-20 14:53:44 +01:00
|
|
|
os.makedirs(
|
|
|
|
qubes.config.system_path['dom0_services_dir'], exist_ok=True)
|
|
|
|
service = '{}/{}'.format(
|
|
|
|
qubes.config.system_path['dom0_services_dir'], service)
|
2020-02-18 11:44:57 +01:00
|
|
|
if not os.path.exists(service):
|
|
|
|
os.mknod(service)
|
2020-02-18 11:18:01 +01:00
|
|
|
except PermissionError:
|
2020-02-20 14:53:44 +01:00
|
|
|
vm.log.warning("Cannot write to {}".format(
|
|
|
|
qubes.config.system_path['dom0_services_dir']))
|
2020-02-18 11:18:01 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2020-03-08 09:51:30 +01:00
|
|
|
def remove_dom0_service(vm, service):
|
2020-02-18 11:18:01 +01:00
|
|
|
try:
|
2020-02-20 14:53:44 +01:00
|
|
|
service = '{}/{}'.format(
|
|
|
|
qubes.config.system_path['dom0_services_dir'], service)
|
2020-02-18 11:44:57 +01:00
|
|
|
if os.path.exists(service):
|
|
|
|
os.remove(service)
|
2020-02-18 11:18:01 +01:00
|
|
|
except PermissionError:
|
2020-02-20 14:53:44 +01:00
|
|
|
vm.log.warning("Cannot write to {}".format(
|
|
|
|
qubes.config.system_path['dom0_services_dir']))
|
2020-02-18 11:18:01 +01:00
|
|
|
|
2017-07-28 15:08:33 +02:00
|
|
|
# pylint: disable=no-self-use
|
|
|
|
@qubes.ext.handler('domain-qdb-create')
|
2017-08-14 02:30:52 +02:00
|
|
|
def on_domain_qdb_create(self, vm, event):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Actually export features"""
|
2017-08-14 02:30:52 +02:00
|
|
|
# pylint: disable=unused-argument
|
2017-07-28 15:08:33 +02:00
|
|
|
for feature, value in vm.features.items():
|
|
|
|
if not feature.startswith('service.'):
|
|
|
|
continue
|
|
|
|
service = feature[len('service.'):]
|
|
|
|
# forcefully convert to '0' or '1'
|
|
|
|
vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
|
2020-02-16 16:07:44 +01:00
|
|
|
str(int(bool(value))))
|
2017-07-28 15:08:33 +02:00
|
|
|
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
# always set meminfo-writer according to maxmem
|
|
|
|
vm.untrusted_qdb.write('/qubes-service/meminfo-writer',
|
2020-02-16 16:07:44 +01:00
|
|
|
'1' if vm.maxmem > 0 else '0')
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
|
2018-01-06 15:05:34 +01:00
|
|
|
@qubes.ext.handler('domain-feature-set:*')
|
2017-08-14 02:30:52 +02:00
|
|
|
def on_domain_feature_set(self, vm, event, feature, value, oldvalue=None):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Update /qubes-service/ QubesDB tree in runtime"""
|
2017-07-28 15:08:33 +02:00
|
|
|
# pylint: disable=unused-argument
|
2018-11-15 15:02:14 +01:00
|
|
|
|
|
|
|
# TODO: remove this compatibility hack in Qubes 4.1
|
|
|
|
if feature == 'service.meminfo-writer':
|
|
|
|
# if someone try to enable meminfo-writer ...
|
|
|
|
if value:
|
|
|
|
# ... reset maxmem to default
|
|
|
|
vm.maxmem = qubes.property.DEFAULT
|
|
|
|
else:
|
|
|
|
# otherwise, set to 0
|
|
|
|
vm.maxmem = 0
|
|
|
|
# in any case, remove the entry, as it does not indicate memory
|
|
|
|
# balancing state anymore
|
|
|
|
del vm.features['service.meminfo-writer']
|
|
|
|
|
2017-07-28 15:08:33 +02:00
|
|
|
if not vm.is_running():
|
|
|
|
return
|
|
|
|
if not feature.startswith('service.'):
|
|
|
|
return
|
|
|
|
service = feature[len('service.'):]
|
|
|
|
# forcefully convert to '0' or '1'
|
|
|
|
vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
|
2020-02-16 16:07:44 +01:00
|
|
|
str(int(bool(value))))
|
2017-07-28 15:08:33 +02:00
|
|
|
|
2020-03-08 10:01:26 +01:00
|
|
|
if vm.name == "dom0":
|
|
|
|
if str(int(bool(value))) == "1":
|
|
|
|
self.add_dom0_service(vm, service)
|
|
|
|
else:
|
|
|
|
self.remove_dom0_service(vm, service)
|
2020-02-16 18:53:42 +01:00
|
|
|
|
2018-01-06 15:05:34 +01:00
|
|
|
@qubes.ext.handler('domain-feature-delete:*')
|
2017-08-14 02:30:52 +02:00
|
|
|
def on_domain_feature_delete(self, vm, event, feature):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Update /qubes-service/ QubesDB tree in runtime"""
|
2017-08-14 02:30:52 +02:00
|
|
|
# pylint: disable=unused-argument
|
2017-07-28 15:08:33 +02:00
|
|
|
if not vm.is_running():
|
|
|
|
return
|
|
|
|
if not feature.startswith('service.'):
|
|
|
|
return
|
|
|
|
service = feature[len('service.'):]
|
2018-11-15 15:02:14 +01:00
|
|
|
# this one is excluded from user control
|
|
|
|
if service == 'meminfo-writer':
|
|
|
|
return
|
2017-07-28 15:08:33 +02:00
|
|
|
vm.untrusted_qdb.rm('/qubes-service/{}'.format(service))
|
2018-10-18 05:44:08 +02:00
|
|
|
|
2020-02-18 11:18:01 +01:00
|
|
|
if vm.name == "dom0":
|
2020-03-08 09:51:30 +01:00
|
|
|
self.remove_dom0_service(vm, service)
|
2020-02-16 18:53:42 +01:00
|
|
|
|
2018-11-15 15:02:14 +01:00
|
|
|
@qubes.ext.handler('domain-load')
|
|
|
|
def on_domain_load(self, vm, event):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Migrate meminfo-writer service into maxmem"""
|
2018-11-15 15:02:14 +01:00
|
|
|
# pylint: disable=no-self-use,unused-argument
|
|
|
|
if 'service.meminfo-writer' in vm.features:
|
|
|
|
# if was set to false, force maxmem=0
|
|
|
|
# otherwise, simply ignore as the default is fine
|
|
|
|
if not vm.features['service.meminfo-writer']:
|
|
|
|
vm.maxmem = 0
|
|
|
|
del vm.features['service.meminfo-writer']
|
|
|
|
|
2020-02-16 18:53:42 +01:00
|
|
|
if vm.name == "dom0":
|
|
|
|
for feature, value in vm.features.items():
|
|
|
|
if not feature.startswith('service.'):
|
|
|
|
continue
|
|
|
|
service = feature[len('service.'):]
|
2020-03-08 09:51:30 +01:00
|
|
|
if str(int(bool(value))) == "1":
|
|
|
|
self.add_dom0_service(vm, service)
|
2020-03-08 10:01:26 +01:00
|
|
|
else:
|
|
|
|
self.remove_dom0_service(vm, service)
|
2020-02-16 18:53:42 +01:00
|
|
|
|
2018-10-18 05:44:08 +02:00
|
|
|
@qubes.ext.handler('features-request')
|
|
|
|
def supported_services(self, vm, event, untrusted_features):
|
2020-02-16 16:07:44 +01:00
|
|
|
"""Handle advertisement of supported services"""
|
2018-10-18 05:44:08 +02:00
|
|
|
# pylint: disable=no-self-use,unused-argument
|
|
|
|
|
|
|
|
if getattr(vm, 'template', None):
|
|
|
|
vm.log.warning(
|
|
|
|
'Ignoring qubes.FeaturesRequest from template-based VM')
|
|
|
|
return
|
|
|
|
|
|
|
|
new_supported_services = set()
|
|
|
|
for requested_service in untrusted_features:
|
|
|
|
if not requested_service.startswith('supported-service.'):
|
|
|
|
continue
|
|
|
|
if untrusted_features[requested_service] == '1':
|
|
|
|
# only allow to advertise service as supported, lack of entry
|
|
|
|
# means service is not supported
|
|
|
|
new_supported_services.add(requested_service)
|
|
|
|
del untrusted_features
|
|
|
|
|
|
|
|
# if no service is supported, ignore the whole thing - do not clear
|
|
|
|
# all services in case of empty request (manual or such)
|
|
|
|
if not new_supported_services:
|
|
|
|
return
|
|
|
|
|
|
|
|
old_supported_services = set(
|
|
|
|
feat for feat in vm.features
|
|
|
|
if feat.startswith('supported-service.') and vm.features[feat])
|
|
|
|
|
|
|
|
for feature in new_supported_services.difference(
|
|
|
|
old_supported_services):
|
|
|
|
vm.features[feature] = True
|
2020-09-02 15:07:30 +02:00
|
|
|
if feature == 'supported-service.apparmor' and \
|
|
|
|
not 'apparmor' in vm.features:
|
|
|
|
vm.features['apparmor'] = True
|
2018-10-18 05:44:08 +02:00
|
|
|
|
|
|
|
for feature in old_supported_services.difference(
|
|
|
|
new_supported_services):
|
|
|
|
del vm.features[feature]
|
2020-09-02 15:07:30 +02:00
|
|
|
if feature == 'supported-service.apparmor' and \
|
|
|
|
'apparmor' in vm.features and vm.features['apparmor'] == '1':
|
|
|
|
del vm.features['apparmor']
|