Commit efe98c2a09 for qemu.org
commit efe98c2a0932c09ada26b8afac3606a66f561933
Author: Denis V. Lunev <den@openvz.org>
Date: Mon Aug 24 15:37:26 2026 +0200
block: reject a reopen of an unusable node instead of crashing
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 <den@openvz.org>
Reviewed-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
CC: Kevin Wolf <kwolf@redhat.com>
CC: Hanna Reitz <hreitz@redhat.com>
CC: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Cc: qemu-stable@nongnu.org
Message-ID: <20260824133729.1141990-3-den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
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 ccdc96b7f8..50bdb8b81d 100755
--- a/tests/qemu-iotests/060
+++ b/tests/qemu-iotests/060
@@ -485,6 +485,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 27275fd6b7..7a1835221b 100644
--- a/tests/qemu-iotests/060.out
+++ b/tests/qemu-iotests/060.out
@@ -434,4 +434,16 @@ 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}}
+{"return": "Error: read failed: Input/output error\r\n"}
+{"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