Update windows-related feature requests

Handle 'os' feature - if it's Windows, then set rpc-clipboard feature.
Handle 'gui-emulated' feature - request for specifically stubdomain GUI.
With 'gui' feature it is only possible to enable gui-agent based on, or
disable GUI completely.
Handle 'default-user' - verify it for weird characters and set
'default_user' property (if wasn't already set).

QubesOS/qubes-issues#3585
This commit is contained in:
Marek Marczykowski-Górecki 2018-07-09 19:42:18 +02:00
parent ce87451c73
commit af7d54d388
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
7 changed files with 129 additions and 5 deletions

View File

@ -76,7 +76,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
untrusted_features = {}
safe_set = string.ascii_letters + string.digits
expected_features = ('qrexec', 'gui', 'default-user')
expected_features = ('qrexec', 'gui', 'gui-emulated', 'default-user',
'os')
for feature in expected_features:
untrusted_value = self.src.untrusted_qdb.read(
'/qubes-tools/' + feature)

View File

@ -32,7 +32,7 @@ class CoreFeatures(qubes.ext.Extension):
return
requested_features = {}
for feature in ('qrexec', 'gui', 'qubes-firewall'):
for feature in ('qrexec', 'gui', 'gui-emulated', 'qubes-firewall'):
untrusted_value = untrusted_features.get(feature, None)
if untrusted_value in ('1', '0'):
requested_features[feature] = bool(int(untrusted_value))
@ -44,7 +44,7 @@ class CoreFeatures(qubes.ext.Extension):
# gui agent presence (0 or 1)
qrexec_before = vm.features.get('qrexec', False)
for feature in ('qrexec', 'gui'):
for feature in ('qrexec', 'gui', 'gui-emulated'):
# do not allow (Template)VM to override setting if already set
# some other way
if feature in requested_features and feature not in vm.features:

64
qubes/ext/windows.py Normal file
View File

@ -0,0 +1,64 @@
# -*- encoding: utf-8 -*-
#
# 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/>.
import qubes.ext
class WindowsFeatures(qubes.ext.Extension):
# pylint: disable=too-few-public-methods
@qubes.ext.handler('features-request')
def qubes_features_request(self, vm, event, untrusted_features):
'''Handle features provided requested by Qubes Windows Tools'''
# pylint: disable=no-self-use,unused-argument
if getattr(vm, 'template', None):
vm.log.warning(
'Ignoring qubes.NotifyTools for template-based VM')
return
guest_os = None
if 'os' in untrusted_features:
if untrusted_features['os'] in ['Windows']:
guest_os = untrusted_features['os']
qrexec = None
if 'qrexec' in untrusted_features:
if untrusted_features['qrexec'] == '1':
# qrexec feature is set by CoreFeatures extension
qrexec = True
del untrusted_features
if guest_os:
vm.features['os'] = guest_os
if guest_os == 'Windows' and qrexec:
vm.features['rpc-clipboard'] = True
@qubes.ext.handler('domain-add', system=True)
def on_domain_add(self, app, _event, vm, **kwargs):
# pylint: disable=no-self-use,unused-argument
if getattr(vm, 'template', None) is None:
# handle only template-based vms
return
template = vm.template
if template.features.check_with_template('os', None) != 'Windows':
# ignore non-windows templates
return
# TODO: consider copying template's root volume here

View File

@ -131,11 +131,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.assertEqual(self.src.mock_calls, [
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
mock.call.untrusted_qdb.read('/qubes-tools/os'),
mock.call.fire_event_async('features-request', untrusted_features={
'gui': '1',
'default-user': 'user',
'qrexec': '1'}),
'qrexec': '1',
'os': 'Linux'}),
('fire_event_async().__iter__', (), {}),
])
self.assertEqual(self.app.mock_calls, [mock.call.save()])
@ -153,11 +156,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.assertEqual(self.src.mock_calls, [
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
mock.call.untrusted_qdb.read('/qubes-tools/os'),
mock.call.fire_event_async('features-request', untrusted_features={
'gui': '1',
'default-user': 'user',
'qrexec': '1'}),
'qrexec': '1',
'os': 'Linux'}),
('fire_event_async().__iter__', (), {}),
])
self.assertEqual(self.app.mock_calls, [mock.call.save()])

View File

@ -21,6 +21,7 @@
from unittest import mock
import qubes.ext.core_features
import qubes.ext.windows
import qubes.tests
@ -163,3 +164,53 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
('features.__contains__', ('qrexec',), {}),
('features.__contains__', ('gui',), {}),
])
class TC_10_WindowsFeatures(qubes.tests.QubesTestCase):
def setUp(self):
super().setUp()
self.ext = qubes.ext.windows.WindowsFeatures()
self.vm = mock.MagicMock()
self.features = {}
self.vm.configure_mock(**{
'features.get.side_effect': self.features.get,
'features.__contains__.side_effect': self.features.__contains__,
'features.__setitem__.side_effect': self.features.__setitem__,
})
def test_000_notify_tools_full(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'gui': '1',
'version': '1',
'default-user': 'user',
'qrexec': '1',
'os': 'Windows'})
self.assertEqual(self.vm.mock_calls, [
('features.__setitem__', ('os', 'Windows'), {}),
('features.__setitem__', ('rpc-clipboard', True), {}),
])
def test_001_notify_tools_no_qrexec(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'gui': '1',
'version': '1',
'default-user': 'user',
'qrexec': '0',
'os': 'Windows'})
self.assertEqual(self.vm.mock_calls, [
('features.__setitem__', ('os', 'Windows'), {}),
])
def test_002_notify_tools_other_os(self):
del self.vm.template
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'gui': '1',
'version': '1',
'default-user': 'user',
'qrexec': '1',
'os': 'Linux'})
self.assertEqual(self.vm.mock_calls, [])

View File

@ -284,6 +284,7 @@ fi
%{python3_sitelib}/qubes/ext/qubesmanager.py
%{python3_sitelib}/qubes/ext/r3compatibility.py
%{python3_sitelib}/qubes/ext/services.py
%{python3_sitelib}/qubes/ext/windows.py
%dir %{python3_sitelib}/qubes/tests
%dir %{python3_sitelib}/qubes/tests/__pycache__

View File

@ -74,6 +74,7 @@ if __name__ == '__main__':
'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension',
'qubes.ext.block = qubes.ext.block:BlockDeviceExtension',
'qubes.ext.services = qubes.ext.services:ServicesExtension',
'qubes.ext.windows = qubes.ext.windows:WindowsFeatures',
],
'qubes.devices': [
'pci = qubes.ext.pci:PCIDevice',