diff --git a/qubes/app.py b/qubes/app.py index 2a00355b7..fd65eb9fb 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -674,6 +674,28 @@ def _setter_default_netvm(app, prop, value): return value +def validate_kernel(obj, property_name: str, kernel: str) -> None: + """Helper function to validate existence of specific kernel""" + if not kernel: + return + dirname = os.path.join( + qubes.config.qubes_base_dir, + qubes.config.system_path['qubes_kernels_base_dir'], + kernel) + if not os.path.exists(dirname): + raise qubes.exc.QubesPropertyValueError( + obj, obj.property_get_def(property_name), kernel, + 'Kernel {!r} not installed'.format( + kernel)) + for filename in ('vmlinuz',): + if not os.path.exists(os.path.join(dirname, filename)): + raise qubes.exc.QubesPropertyValueError( + obj, obj.property_get_def(property_name), kernel, + 'Kernel {!r} not properly installed: ' + 'missing {!r} file'.format( + kernel, filename)) + + class Qubes(qubes.PropertyHolder): """Main Qubes application @@ -1593,3 +1615,10 @@ def on_property_set_default_dispvm(self, event, name, newvalue, # resetting dispvm to its default value vm.fire_event('property-reset:default_dispvm', name='default_dispvm', oldvalue=oldvalue) + + @qubes.events.handler('property-pre-set:default_kernel') + # pylint: disable-next=invalid-name + def on_property_pre_set_default_kernel(self, event, name, newvalue, + oldvalue=None): + # pylint: disable=unused-argument + validate_kernel(self, 'default_kernel', newvalue) diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index f0db98fbb..0591fa57c 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -458,6 +458,14 @@ def setUp(self): self.loop = asyncio.get_event_loop() self.addCleanup(self.cleanup_loop) + self.kernel_validator_original = qubes.app.validate_kernel + def kernel_validator_patched(obj, key, value): + if value.startswith('unittest'): + self.kernel_validator_original(obj, key, value) + self.skip_kernel_validation_patch = unittest.mock.patch( + 'qubes.app.validate_kernel', kernel_validator_patched) + self.skip_kernel_validation_patch.start() + def cleanup_gc(self): gc.collect() leaked = [obj for obj in gc.get_objects() + gc.garbage diff --git a/qubes/tests/api_admin.py b/qubes/tests/api_admin.py index 97cce4eda..54f6ee8b3 100644 --- a/qubes/tests/api_admin.py +++ b/qubes/tests/api_admin.py @@ -58,9 +58,12 @@ def setUp(self): self.base_dir_patch3 = unittest.mock.patch.dict( qubes.config.defaults['pool_configs']['varlibqubes'], {'dir_path': self.test_base_dir}) + self.skip_kernel_validation_patch = unittest.mock.patch( + 'qubes.app.validate_kernel', lambda obj, key, value: None) self.base_dir_patch.start() self.base_dir_patch2.start() self.base_dir_patch3.start() + self.skip_kernel_validation_patch.start() app = qubes.Qubes('/tmp/qubes-test.xml', load=False) app.vmm = unittest.mock.Mock(spec=qubes.app.VMMConnection) app.load_initial_values() diff --git a/qubes/tests/app.py b/qubes/tests/app.py index d9779e541..c9f9ec8c6 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -820,6 +820,16 @@ def test_206_remove_attached(self): with self.assertRaises(qubes.exc.QubesVMInUseError): del self.app.domains[vm] + def test_207_default_kernel(self): + with self.assertRaises(qubes.exc.QubesPropertyValueError): + # invalid path check + self.app.default_kernel = 'unittest_Evil_Maid_Kernel' + with self.assertRaises(qubes.exc.QubesPropertyValueError): + # vmlinuz check + with mock.patch('os.path.exists') as existence: + existence.side_effect = [True, False] + self.app.default_kernel = 'unittest_GNU_Hurd_1.0.0' + @qubes.tests.skipUnlessGit def test_900_example_xml_in_doc(self): self.assertXMLIsValid( diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index 2c8edff23..fcb4fbd47 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -141,10 +141,12 @@ def test_002_setter_qid_gt_max(self): qubes.vm._setter_qid(self.vm, self.prop, qubes.config.max_qid + 5) - @unittest.skip('test not implemented') def test_020_setter_kernel(self): - pass - + self.assertEqual( + qubes.vm.qubesvm._setter_kernel(self.vm, self.prop, None), '') + with self.assertRaises(ValueError): + qubes.vm.qubesvm._setter_kernel(self.vm, + self.prop, 'path/in/kernel/property') def test_030_setter_label_object(self): label = TestApp.labels[1] diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index f586c9b16..7f1a5f0bf 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -1040,24 +1040,7 @@ def on_property_set_tmpl_for_dvms(self, event, name, @qubes.events.handler('property-pre-set:kernel') def on_property_pre_set_kernel(self, event, name, newvalue, oldvalue=None): # pylint: disable=unused-argument - if not newvalue: - return - dirname = os.path.join( - qubes.config.qubes_base_dir, - qubes.config.system_path['qubes_kernels_base_dir'], - newvalue) - if not os.path.exists(dirname): - raise qubes.exc.QubesPropertyValueError( - self, self.property_get_def(name), newvalue, - 'Kernel {!r} not installed'.format( - newvalue)) - for filename in ('vmlinuz',): - if not os.path.exists(os.path.join(dirname, filename)): - raise qubes.exc.QubesPropertyValueError( - self, self.property_get_def(name), newvalue, - 'Kernel {!r} not properly installed: ' - 'missing {!r} file'.format( - newvalue, filename)) + qubes.app.validate_kernel(self, name, newvalue) @qubes.events.handler('property-pre-set:autostart') def on_property_pre_set_autostart(self, event, name, newvalue,