xemu/tests/qemu-iotests/283
Max Reitz e417994092 iotests/283: Check that finalize drops backup-top
Without any of HEAD^ or HEAD^^ applied, qemu will most likely crash on
the qemu-io invocation, for a variety of immediate reasons.  The
underlying problem is generally a use-after-free access into
backup-top's BlockCopyState.

With only HEAD^ applied, qemu-io will run into an EIO (which is not
capture by the output, but you can see that the qemu-io invocation will
be accepted (i.e., qemu-io will run) in contrast to the reference
output, where the node name cannot be found), and qemu will then crash
in query-named-block-nodes: bdrv_get_allocated_file_size() detects
backup-top to be a filter and passes the request through to its child.
However, after bdrv_backup_top_drop(), that child is NULL, so the
recursive call crashes.

With HEAD^^ applied, this test should pass.

Signed-off-by: Max Reitz <mreitz@redhat.com>
Message-Id: <20210219153348.41861-4-mreitz@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
2021-03-08 14:55:18 +01:00

153 lines
5.2 KiB
Python
Executable File

#!/usr/bin/env python3
# group: auto quick
#
# Test for backup-top filter permission activation failure
#
# Copyright (c) 2019 Virtuozzo International GmbH.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import iotests
# The test is unrelated to formats, restrict it to qcow2 to avoid extra runs
iotests.script_initialize(
supported_fmts=['qcow2'],
)
size = 1024 * 1024
""" Test description
When performing a backup, all writes on the source subtree must go through the
backup-top filter so it can copy all data to the target before it is changed.
backup-top filter is appended above source node, to achieve this thing, so all
parents of source node are handled. A configuration with side parents of source
sub-tree with write permission is unsupported (we'd have append several
backup-top filter like nodes to handle such parents). The test create an
example of such configuration and checks that a backup is then not allowed
(blockdev-backup command should fail).
The configuration:
┌────────┐ target ┌─────────────┐
│ target │ ◀─────── │ backup_top │
└────────┘ └─────────────┘
│ backing
┌─────────────┐
│ source │
└─────────────┘
│ file
┌─────────────┐ write perm ┌───────┐
│ base │ ◀──────────── │ other │
└─────────────┘ └───────┘
On activation (see .active field of backup-top state in block/backup-top.c),
backup-top is going to unshare write permission on its source child. Write
unsharing will be propagated to the "source->base" link and will conflict with
other node write permission. So permission update will fail and backup job will
not be started.
Note, that the only thing which prevents backup of running on such
configuration is default permission propagation scheme. It may be altered by
different block drivers, so backup will run in invalid configuration. But
something is better than nothing. Also, before the previous commit (commit
preceding this test creation), starting backup on such configuration led to
crash, so current "something" is a lot better, and this test actual goal is
to check that crash is fixed :)
"""
vm = iotests.VM()
vm.launch()
vm.qmp_log('blockdev-add', **{
'node-name': 'target',
'driver': 'null-co',
'size': size,
})
vm.qmp_log('blockdev-add', **{
'node-name': 'source',
'driver': 'blkdebug',
'image': {'node-name': 'base', 'driver': 'null-co', 'size': size}
})
vm.qmp_log('blockdev-add', **{
'node-name': 'other',
'driver': 'blkdebug',
'image': 'base',
'take-child-perms': ['write']
})
vm.qmp_log('blockdev-backup', sync='full', device='source', target='target')
vm.shutdown()
print('\n=== backup-top should be gone after job-finalize ===\n')
# Check that the backup-top node is gone after job-finalize.
#
# During finalization, the node becomes inactive and can no longer
# function. If it is still present, new parents might be attached, and
# there would be no meaningful way to handle their I/O requests.
vm = iotests.VM()
vm.launch()
vm.qmp_log('blockdev-add', **{
'node-name': 'source',
'driver': 'null-co',
})
vm.qmp_log('blockdev-add', **{
'node-name': 'target',
'driver': 'null-co',
})
vm.qmp_log('blockdev-backup',
job_id='backup',
device='source',
target='target',
sync='full',
filter_node_name='backup-filter',
auto_finalize=False,
auto_dismiss=False)
vm.event_wait('BLOCK_JOB_PENDING', 5.0)
# The backup-top filter should still be present prior to finalization
assert vm.node_info('backup-filter') is not None
vm.qmp_log('job-finalize', id='backup')
vm.event_wait('BLOCK_JOB_COMPLETED', 5.0)
# The filter should be gone now. Check that by trying to access it
# with qemu-io (which will most likely crash qemu if it is still
# there.).
vm.qmp_log('human-monitor-command',
command_line='qemu-io backup-filter "write 0 1M"')
# (Also, do an explicit check.)
assert vm.node_info('backup-filter') is None
vm.qmp_log('job-dismiss', id='backup')
vm.event_wait('JOB_STATUS_CHANGE', 5.0, {'data': {'status': 'null'}})
vm.shutdown()