| 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 | |
| 22 | import os |
| 23 | |
| 24 | from qemu.machine import machine |
| 25 | |
| 26 | import iotests |
| 27 | from iotests import change_log_level, qemu_img |
| 28 | |
| 29 | |
| 30 | image_size = 1 * 1024 * 1024 |
| 31 | source = os.path.join(iotests.test_dir, 'source.img') |
| 32 | |
| 33 | |
| 34 | class TestMirrorTopPerms(iotests.QMPTestCase): |
| 35 | def setUp(self): |
| 36 | qemu_img('create', '-f', iotests.imgfmt, source, str(image_size)) |
| 37 | self.vm = iotests.VM() |
| 38 | self.vm.add_drive(source) |
| 39 | self.vm.add_blockdev(f'null-co,node-name=null,size={image_size}') |
| 40 | self.vm.launch() |
| 41 | |
| 42 | # Will be created by the test function itself |
| 43 | self.vm_b = None |
| 44 | |
| 45 | def tearDown(self): |
| 46 | try: |
| 47 | self.vm.shutdown() |
| 48 | except machine.AbnormalShutdown: |
| 49 | pass |
| 50 | |
| 51 | if self.vm_b is not None: |
| 52 | self.vm_b.shutdown() |
| 53 | |
| 54 | os.remove(source) |
| 55 | |
| 56 | def test_cancel(self): |
| 57 | """ |
| 58 | Before commit 53431b9086b28, mirror-top used to not take any |
| 59 | permissions but WRITE and share all permissions. Because it |
| 60 | is inserted between the source's original parents and the |
| 61 | source, there generally was no parent that would have taken or |
| 62 | unshared any permissions on the source, which means that an |
| 63 | external process could access the image unhindered by locks. |
| 64 | (Unless there was a parent above the protocol node that would |
| 65 | take its own locks, e.g. a format driver.) |
| 66 | This is bad enough, but if the mirror job is then cancelled, |
| 67 | the mirroring VM tries to take back the image, restores the |
| 68 | original permissions taken and unshared, and assumes this must |
| 69 | just work. But it will not, and so the VM aborts. |
| 70 | |
| 71 | Commit 53431b9086b28 made mirror keep the original permissions |
| 72 | and so no other process can "steal" the image. |
| 73 | |
| 74 | (Note that you cannot really do the same with the target image |
| 75 | and then completing the job, because the mirror job always |
| 76 | took/unshared the correct permissions on the target. For |
| 77 | example, it does not share READ_CONSISTENT, which makes it |
| 78 | difficult to let some other qemu process open the image.) |
| 79 | """ |
| 80 | |
| 81 | self.vm.cmd('blockdev-mirror', |
| 82 | job_id='mirror', |
| 83 | device='drive0', |
| 84 | target='null', |
| 85 | sync='full') |
| 86 | |
| 87 | self.vm.event_wait('BLOCK_JOB_READY') |
| 88 | |
| 89 | # We want this to fail because the image cannot be locked. |
| 90 | # If it does not fail, continue still and see what happens. |
| 91 | self.vm_b = iotests.VM(path_suffix='b') |
| 92 | # Must use -blockdev -device so we can use share-rw. |
| 93 | # (And we need share-rw=on because mirror-top was always |
| 94 | # forced to take the WRITE permission so it can write to the |
| 95 | # source image.) |
| 96 | self.vm_b.add_blockdev(f'file,node-name=drive0,filename={source}') |
| 97 | self.vm_b.add_device('virtio-blk,drive=drive0,share-rw=on') |
| 98 | try: |
| 99 | # Silence QMP logging errors temporarily. |
| 100 | with change_log_level('qemu.qmp'): |
| 101 | self.vm_b.launch() |
| 102 | print('ERROR: VM B launched successfully, ' |
| 103 | 'this should not have happened') |
| 104 | except machine.VMLaunchFailure as exc: |
| 105 | assert 'Is another process using the image' in exc.output |
| 106 | |
| 107 | self.vm.cmd('block-job-cancel', |
| 108 | device='mirror') |
| 109 | |
| 110 | self.vm.event_wait('BLOCK_JOB_COMPLETED') |
| 111 | |
| 112 | |
| 113 | if __name__ == '__main__': |
| 114 | # No metadata format driver supported, because they would for |
| 115 | # example always unshare the WRITE permission. The raw driver |
| 116 | # just passes through the permissions from the guest device, and |
| 117 | # those are the permissions that we want to test. |
| 118 | iotests.main(supported_fmts=['raw'], |
| 119 | supported_protocols=['file']) |