1#!/usr/bin/env python3 2# group: rw 3# 4# Test permissions taken by the mirror-top filter 5# 6# Copyright (C) 2021 Red Hat, Inc. 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20# 21 22import os 23 24from qemu.aqmp import ConnectError 25from qemu.machine import machine 26from qemu.qmp import QMPConnectError 27 28import iotests 29from iotests import change_log_level, qemu_img 30 31 32image_size = 1 * 1024 * 1024 33source = os.path.join(iotests.test_dir, 'source.img') 34 35 36class TestMirrorTopPerms(iotests.QMPTestCase): 37 def setUp(self): 38 assert qemu_img('create', '-f', iotests.imgfmt, source, 39 str(image_size)) == 0 40 self.vm = iotests.VM() 41 self.vm.add_drive(source) 42 self.vm.add_blockdev(f'null-co,node-name=null,size={image_size}') 43 self.vm.launch() 44 45 # Will be created by the test function itself 46 self.vm_b = None 47 48 def tearDown(self): 49 try: 50 self.vm.shutdown() 51 except machine.AbnormalShutdown: 52 pass 53 54 if self.vm_b is not None: 55 self.vm_b.shutdown() 56 57 os.remove(source) 58 59 def test_cancel(self): 60 """ 61 Before commit 53431b9086b28, mirror-top used to not take any 62 permissions but WRITE and share all permissions. Because it 63 is inserted between the source's original parents and the 64 source, there generally was no parent that would have taken or 65 unshared any permissions on the source, which means that an 66 external process could access the image unhindered by locks. 67 (Unless there was a parent above the protocol node that would 68 take its own locks, e.g. a format driver.) 69 This is bad enough, but if the mirror job is then cancelled, 70 the mirroring VM tries to take back the image, restores the 71 original permissions taken and unshared, and assumes this must 72 just work. But it will not, and so the VM aborts. 73 74 Commit 53431b9086b28 made mirror keep the original permissions 75 and so no other process can "steal" the image. 76 77 (Note that you cannot really do the same with the target image 78 and then completing the job, because the mirror job always 79 took/unshared the correct permissions on the target. For 80 example, it does not share READ_CONSISTENT, which makes it 81 difficult to let some other qemu process open the image.) 82 """ 83 84 result = self.vm.qmp('blockdev-mirror', 85 job_id='mirror', 86 device='drive0', 87 target='null', 88 sync='full') 89 self.assert_qmp(result, 'return', {}) 90 91 self.vm.event_wait('BLOCK_JOB_READY') 92 93 # We want this to fail because the image cannot be locked. 94 # If it does not fail, continue still and see what happens. 95 self.vm_b = iotests.VM(path_suffix='b') 96 # Must use -blockdev -device so we can use share-rw. 97 # (And we need share-rw=on because mirror-top was always 98 # forced to take the WRITE permission so it can write to the 99 # source image.) 100 self.vm_b.add_blockdev(f'file,node-name=drive0,filename={source}') 101 self.vm_b.add_device('virtio-blk,drive=drive0,share-rw=on') 102 try: 103 # Silence AQMP errors temporarily. 104 # TODO: Remove this and just allow the errors to be logged when 105 # AQMP fully replaces QMP. 106 with change_log_level('qemu.aqmp'): 107 self.vm_b.launch() 108 print('ERROR: VM B launched successfully, ' 109 'this should not have happened') 110 except (QMPConnectError, ConnectError): 111 assert 'Is another process using the image' in self.vm_b.get_log() 112 113 result = self.vm.qmp('block-job-cancel', 114 device='mirror') 115 self.assert_qmp(result, 'return', {}) 116 117 self.vm.event_wait('BLOCK_JOB_COMPLETED') 118 119 120if __name__ == '__main__': 121 # No metadata format driver supported, because they would for 122 # example always unshare the WRITE permission. The raw driver 123 # just passes through the permissions from the guest device, and 124 # those are the permissions that we want to test. 125 iotests.main(supported_fmts=['raw'], 126 supported_protocols=['file']) 127