From: Denis V. Lunev <[email protected]> qcow2_signal_corruption() drops bs->drv, so a node can lose its driver at any time and a reopen has to expect that. Both ends of the path assume otherwise:
$ qemu-io -c "read 0 64k" -c "reopen -r" corrupt.qcow2 qcow2: Marking image as corrupt: Cluster allocation offset 0x1200 unaligned (L2 offset: 0x40000, L2 index: 0); ... Segmentation fault bdrv_reopen_queue_child() dereferences bs->drv while descending into the children the node opened itself, and bdrv_reopen_prepare() asserts on it. commit_clean() reopens the base of a commit job back to read-only, so a base which goes corrupt under the job arrives here too. There is nothing to reopen for such a node, so stop descending into its children and let bdrv_reopen_prepare() report what every caller of bdrv_reopen() already handles. bdrv_reopen_commit() and bdrv_reopen_abort() keep their assertion, only a prepared entry reaches them. Signed-off-by: Denis V. Lunev <[email protected]> CC: Kevin Wolf <[email protected]> CC: Hanna Reitz <[email protected]> CC: Andrey Drobyshev <[email protected]> --- block.c | 14 +++++++++++++- tests/qemu-iotests/060 | 30 ++++++++++++++++++++++++++++++ tests/qemu-iotests/060.out | 13 +++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/block.c b/block.c index f0a6042e61..e39f15816a 100644 --- a/block.c +++ b/block.c @@ -4486,6 +4486,11 @@ bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, BlockDriverState *bs, !qdict_haskey(options, "backing.driver"); } + /* An unusable node is rejected by bdrv_reopen_prepare(), do not descend */ + if (!bs->drv) { + return bs_queue; + } + QLIST_FOREACH(child, &bs->children, next) { QDict *new_child_options = NULL; bool child_keep_old = keep_old_opts; @@ -4881,9 +4886,16 @@ bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, bool drv_prepared = false; assert(reopen_state != NULL); - assert(reopen_state->bs->drv != NULL); GLOBAL_STATE_CODE(); + drv = reopen_state->bs->drv; + if (drv == NULL) { + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + error_setg(errp, "Block node '%s' has no driver left to reopen", + bdrv_get_device_or_node_name(reopen_state->bs)); + return -ENOMEDIUM; + } /* This function and each driver's bdrv_reopen_prepare() remove * entries from reopen_state->options as they are processed, so diff --git a/tests/qemu-iotests/060 b/tests/qemu-iotests/060 index 5cd21a6f68..ce49fc34ec 100755 --- a/tests/qemu-iotests/060 +++ b/tests/qemu-iotests/060 @@ -486,6 +486,36 @@ echo # Image should not have been marked corrupt _img_info --format-specific | grep 'corrupt:' +echo +echo "=== Testing the reopen of an image corrupted at runtime ===" +echo + +_make_test_img 64M +poke_file "$TEST_IMG" "$l1_offset" "\x00\x00\x00\x00\x2a\x2a\x2a\x2a" + +# The read leaves the node unusable, the reopen must report that +echo "{'execute': 'qmp_capabilities'} + {'execute': 'human-monitor-command', + 'arguments': {'command-line': 'qemu-io drive \"read 0 512\"'}} + {'execute': 'blockdev-reopen', + 'arguments': {'options': [{'node-name': 'drive', + 'driver': 'qcow2', + 'read-only': true, + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG' + }}]}} + {'execute': 'quit'}" \ + | $QEMU -qmp stdio -nographic -nodefaults \ + -blockdev "{'node-name': 'drive', + 'driver': 'qcow2', + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG' + }}" \ + 2>&1 \ + | _filter_qmp | _filter_qemu_io + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/060.out b/tests/qemu-iotests/060.out index a37bf446e9..ad1912a43b 100644 --- a/tests/qemu-iotests/060.out +++ b/tests/qemu-iotests/060.out @@ -436,4 +436,17 @@ qcow2: Image is corrupt: L2 table offset 0x2a2a2a00 unaligned (L1 index: 0); fur {"return": {}} corrupt: false + +=== Testing the reopen of an image corrupted at runtime === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +QMP_VERSION +{"return": {}} +qcow2: Marking image as corrupt: L2 table offset 0x2a2a2a00 unaligned (L1 index: 0); further corruption events will be suppressed +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_IMAGE_CORRUPTED", "data": {"device": "", "msg": "L2 table offset 0x2a2a2a00 unaligned (L1 index: 0)", "node-name": "drive", "fatal": true}} +read failed: Input/output error +{"return": ""} +{"error": {"class": "GenericError", "desc": "Block node 'drive' has no driver left to reopen"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} +{"return": {}} *** done -- 2.53.0
