Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions qubes/ext/core_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ async def qubes_features_request(self, vm, event, untrusted_features):
) and hasattr(vm, "appvm_default_bootmode"):
bootmode_value = untrusted_feature_value
vm.features["boot-mode.appvm-default"] = bootmode_value
if "boot-mode.standalone-default" in untrusted_features:
untrusted_feature_value = untrusted_features[
"boot-mode.standalone-default"
]
if (
f"boot-mode.kernelopts.{untrusted_feature_value}" in vm.features
or untrusted_feature_value == "default"
) and hasattr(vm, "standalone_default_bootmode"):
bootmode_value = untrusted_feature_value
vm.features["boot-mode.standalone-default"] = bootmode_value

# allow VMs to switch on anon-timezone, but do not permit dropping it
if "anon-timezone" in untrusted_features:
Expand Down
6 changes: 5 additions & 1 deletion qubes/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,9 +929,13 @@ async def import_volume(self, dst_volume: Volume, src_volume: Volume):
f"data"
)

return await qubes.utils.coro_maybe(
import_rslt = await qubes.utils.coro_maybe(
dst_volume.import_volume(src_volume)
)
await self.vm.fire_event_async(
"domain-import-volume", name=dst_volume.name, source=src_volume
)
return import_rslt


class VolumesCollection:
Expand Down
79 changes: 79 additions & 0 deletions qubes/tests/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,85 @@ def test_057_bootmode_default_user_default_bootmode(self):
],
)

def test_058_bootmode_standalone_default_good(self):
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(
self.vm,
"features-request",
untrusted_features={
"boot-mode.kernelopts.orig-mode": "test1",
"boot-mode.kernelopts.new-mode": "test2",
"boot-mode.standalone-default": "new-mode",
},
)
)
self.assertListEqual(
self.vm.mock_calls,
[
("features.items", (), {}),
(
"features.__setitem__",
("boot-mode.kernelopts.orig-mode", "test1"),
{},
),
(
"features.__setitem__",
("boot-mode.kernelopts.new-mode", "test2"),
{},
),
(
"features.__contains__",
("boot-mode.kernelopts.new-mode",),
{},
),
(
"features.__setitem__",
("boot-mode.standalone-default", "new-mode"),
{},
),
("features.get", ("qrexec", False), {}),
("features.get", ("qrexec", False), {}),
],
)

def test_059_bootmode_standalone_default_bad(self):
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(
self.vm,
"features-request",
untrusted_features={
"boot-mode.kernelopts.orig-mode": "test1",
"boot-mode.kernelopts.new-mode": "test2",
"boot-mode.standalone-default": "missing-mode",
},
)
)
self.assertListEqual(
self.vm.mock_calls,
[
("features.items", (), {}),
(
"features.__setitem__",
("boot-mode.kernelopts.orig-mode", "test1"),
{},
),
(
"features.__setitem__",
("boot-mode.kernelopts.new-mode", "test2"),
{},
),
(
"features.__contains__",
("boot-mode.kernelopts.missing-mode",),
{},
),
("features.get", ("qrexec", False), {}),
("features.get", ("qrexec", False), {}),
],
)

def test_060_anon_timezone_set(self):
del self.vm.template
self.loop.run_until_complete(
Expand Down
34 changes: 34 additions & 0 deletions qubes/tests/integ/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import qubes.tests.storage_zfs
import qubes.utils
import qubes.vm.appvm
import qubes.vm.standalonevm


class StorageTestMixin(object):
Expand All @@ -46,6 +47,20 @@ def setUp(self):
qubes.vm.appvm.AppVM, name=self.make_vm_name("vm2"), label="red"
)
self.loop.run_until_complete(self.vm2.create_on_disk())
self.standalone_vm1 = self.app.add_new_vm(
qubes.vm.standalonevm.StandaloneVM,
name=self.make_vm_name("standalone-vm1"),
label="red",
)
self.standalone_vm1.clone_properties(self.app.domains[self.template])
self.standalone_vm1.features.update(
self.app.domains[self.template].features
)
self.loop.run_until_complete(
self.standalone_vm1.clone_disk_files(
self.app.domains[self.template]
)
)
self.pool = None
self.init_pool()
self.app.save()
Expand Down Expand Up @@ -485,6 +500,25 @@ async def _test_006_no_revisions(self):
)
self.assertEqual(stdout, b"test123")

def test_007_standalone_clone_boot_mode_transition(self):
return self.loop.run_until_complete(
self._test_007_standalone_clone_boot_mode_transition()
)

async def _test_007_standalone_clone_boot_mode_transition(self):
standalone_vm1.features["boot-mode.kernelopts.orig-mode"] = "test1"
standalone_vm1.features["boot-mode.kernelopts.new-mode"] = "test2"
standalone_vm1.features["boot-mode.active"] = "orig-mode"
standalone_vm1.features["boot-mode.standalone-default"] = "new-mode"
template_rootvol = self.template.storage.get_volume("root")
standalone_vm1_rootvol = self.standalone_vm1.storage.get_volume("root")
await qubes.utils.coro_maybe(
standalone_vm1_rootvol.import_volume(template_rootvol)
)
self.assertEqual(
standalone_vm1.features["boot-mode.active"], "new-mode"
)


class StorageFile(StorageTestMixin, qubes.tests.SystemTestCase):
def init_pool(self):
Expand Down
9 changes: 9 additions & 0 deletions qubes/vm/qubesvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.LocalVM):
:param event: Event name (``'domain-tag-delete:' tag``)
:param tag: tag name

.. event:: domain-import-volume (subject, event, name, source)

A volume has been imported.

:param subject: Event emitter (the qube object)
:param event: Event name (``'domain-import-volume'``)
:param name: Destination volume name
:param source: Source volume

.. event:: features-request (subject, event, *, untrusted_features)

The domain is performing a features request.
Expand Down
12 changes: 12 additions & 0 deletions qubes/vm/standalonevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,15 @@ def __init__(self, *args, **kwargs):
},
}
super().__init__(*args, **kwargs)

@qubes.events.handler("domain-import-volume")
def on_domain_import_volume(self, event, name, source):
# pylint: disable=unused-argument
if name != "root":
return
if "boot-mode.standalone-default" in self.features:
bootmode_value = self.features["boot-mode.standalone-default"]
if bootmode_value == "default":
self.features["boot-mode.active"] = "default"
if f"boot-mode.kernelopts.{bootmode_value}" in self.features:
self.features["boot-mode.active"] = bootmode_value