tools: reset private volume when importing template over existing one

Reinstalling template is a recommended way to get it back to a clean
state after potential compromise. In that case it is essential to
discard any persistent storage of old template, as it could be used by
the attacker to re-compromise it after reinstall.
Do this similar as root volume is overridden - via volume import
function.

Fixes QubesOS/qubes-issues#5192
This commit is contained in:
Marek Marczykowski-Górecki 2019-07-29 18:48:19 +02:00
parent 21569b3a31
commit fdc632c959
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 34 additions and 4 deletions

View File

@ -160,6 +160,21 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
vm, template_dir)
self.assertAllCalled()
def test_005_reset_private_img(self):
self.app.qubesd_connection_type = 'socket'
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\0test-vm class=TemplateVM state=Halted\n'
self.app.expected_calls[
('test-vm', 'admin.vm.volume.List', None, None)] = \
b'0\0root\nprivate\nvolatile\nkernel\n'
self.app.expected_calls[('test-vm', 'admin.vm.volume.Import', 'private',
b'')] = b'0\0'
vm = self.app.domains['test-vm']
qubesadmin.tools.qvm_template_postprocess.reset_private_img(vm)
self.assertAllCalled()
def test_010_import_appmenus(self):
with open(os.path.join(self.source_dir.name,
'vm-whitelisted-appmenus.list'), 'w') as f:
@ -314,8 +329,9 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
@mock.patch('qubesadmin.tools.qvm_template_postprocess.import_appmenus')
@mock.patch('qubesadmin.tools.qvm_template_postprocess.import_root_img')
def test_021_post_install_reinstall(self, mock_import_root_img,
mock_import_appmenus):
@mock.patch('qubesadmin.tools.qvm_template_postprocess.reset_private_img')
def test_021_post_install_reinstall(self, mock_reset_private_img,
mock_import_root_img, mock_import_appmenus):
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\0test-vm class=TemplateVM state=Halted\n'
self.app.add_new_vm = mock.Mock()
@ -353,6 +369,8 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
self.assertFalse(self.app.add_new_vm.called)
mock_import_root_img.assert_called_once_with(self.app.domains[
'test-vm'], self.source_dir.name)
mock_reset_private_img.assert_called_once_with(self.app.domains[
'test-vm'])
mock_import_appmenus.assert_called_once_with(self.app.domains[
'test-vm'], self.source_dir.name)
if qubesadmin.tools.qvm_template_postprocess.have_events:
@ -366,8 +384,9 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
@mock.patch('qubesadmin.tools.qvm_template_postprocess.import_appmenus')
@mock.patch('qubesadmin.tools.qvm_template_postprocess.import_root_img')
def test_022_post_install_skip_start(self, mock_import_root_img,
mock_import_appmenus):
@mock.patch('qubesadmin.tools.qvm_template_postprocess.reset_private_img')
def test_022_post_install_skip_start(self, mock_reset_private_img,
mock_import_root_img, mock_import_appmenus):
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\0test-vm class=TemplateVM state=Halted\n'
self.app.expected_calls[
@ -391,6 +410,8 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
self.assertFalse(self.app.add_new_vm.called)
mock_import_root_img.assert_called_once_with(self.app.domains[
'test-vm'], self.source_dir.name)
mock_reset_private_img.assert_called_once_with(self.app.domains[
'test-vm'])
mock_import_appmenus.assert_called_once_with(self.app.domains[
'test-vm'], self.source_dir.name)
if qubesadmin.tools.qvm_template_postprocess.have_events:

View File

@ -122,6 +122,12 @@ def import_root_img(vm, source_dir):
root_size, str(err)))
def reset_private_img(vm):
'''Clear private volume'''
with open('/dev/null', 'rb') as null:
vm.volumes['private'].import_data(stream=null)
def import_appmenus(vm, source_dir):
'''Import appmenus settings into VM object (later: GUI VM)'''
if os.getuid() == 0:
@ -232,6 +238,9 @@ def post_install(args):
if vm_created:
del app.domains[vm.name]
raise
if not vm_created:
vm.log.info('Clearing private volume')
reset_private_img(vm)
vm.installed_by_rpm = True
import_appmenus(vm, args.dir)