* [PATCH hci-dev 0/2] qcow2, block: fix reopen of a dirty read-only node and of a corrupt node #VSTOR-140073
@ 2026-11-04 14:41 Denis V. Lunev
2026-11-04 14:41 ` [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only " Denis V. Lunev
2026-11-04 14:41 ` [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073 Denis V. Lunev
0 siblings, 2 replies; 5+ messages in thread
From: Denis V. Lunev @ 2026-11-04 14:41 UTC (permalink / raw)
To: andrey.drobyshev; +Cc: svt-core, den
These are patches 1 and 2 of the hci-8.0 posting of September 16, merged
upstream as af28b65fae6 and efe98c2a093. The repair of a dirty image
that becomes writable is not part of this.
- A read-only to read-only reopen of a dirty qcow2 image fails, or
aborts on the BLK_PERM_WRITE assertion in bdrv_co_write_req_prepare()
when the file node below is writable.
- A reopen of a node which qcow2_signal_corruption() left without a
driver crashes QEMU. commit_clean() reopens the base of a commit job
this way.
Patch 1 is a clean pick. Patch 2 differs only in 060.out: HMP qemu-io on
this tree prints a failed read on the monitor instead of returning it.
Tested on dev-cl04. Unpatched hci-dev fails the new 039 and 060 cases
with that assertion and with a segfault; with the series both pass. The
other reopen iotests, qcow2 and raw, give the same result with and
without it.
Denis V. Lunev (2):
qcow2: do not clear the dirty bit when reopening a read-only node
#VSTOR-140073
block: reject a reopen of an unusable node instead of crashing
#VSTOR-140073
block.c | 14 ++++++++++-
block/qcow2.c | 8 +++---
tests/qemu-iotests/039 | 50 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/039.out | 20 +++++++++++++++
tests/qemu-iotests/060 | 30 +++++++++++++++++++++++
tests/qemu-iotests/060.out | 13 ++++++++++
6 files changed, 131 insertions(+), 4 deletions(-)
base-commit: ca7715cc68cdd379b9fbe32d794408bd87a6677e
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only node #VSTOR-140073
2026-11-04 14:41 [PATCH hci-dev 0/2] qcow2, block: fix reopen of a dirty read-only node and of a corrupt node #VSTOR-140073 Denis V. Lunev
@ 2026-11-04 14:41 ` Denis V. Lunev
2026-11-04 14:43 ` no-reply
2026-11-04 14:41 ` [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073 Denis V. Lunev
1 sibling, 1 reply; 5+ messages in thread
From: Denis V. Lunev @ 2026-11-04 14:41 UTC (permalink / raw)
To: andrey.drobyshev; +Cc: svt-core, den
From: Denis V. Lunev <den@openvz.org>
qcow2_reopen_prepare() clears the dirty bit whenever the node is
reopened read-only, with an unguarded header write. A read-only node
can still be dirty, inherited from an earlier writable session, and it
holds no BLK_PERM_WRITE to resolve that. A read-only to read-only
reopen of a dirty image therefore fails outright:
$ qemu-io -r -f qcow2 dirty.qcow2 <<< $'reopen -r\nquit'
qemu-io: failed while preparing to reopen image 'dirty.qcow2'
Where the file node below is writable the write is not refused early,
and bdrv_co_write_req_prepare() aborts on its BLK_PERM_WRITE assertion
instead.
Clear it only for a node that is writable now, the predicate
qcow2_do_open() already uses for the repair. bdrv_is_writable() also
excludes an inactive node, whose header must not be touched either.
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-2-den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit af28b65fae6fcbc22efdbcd4d71b37fd0c8adf89)
---
block/qcow2.c | 8 +++---
tests/qemu-iotests/039 | 50 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/039.out | 20 +++++++++++++++
3 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/block/qcow2.c b/block/qcow2.c
index 34498078f5e..3e0a219e156 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -2190,9 +2190,11 @@ qcow2_reopen_prepare(BDRVReopenState *state,BlockReopenQueue *queue,
goto fail;
}
- ret = qcow2_mark_clean(state->bs);
- if (ret < 0) {
- goto fail;
+ if (bdrv_is_writable(state->bs)) {
+ ret = qcow2_mark_clean(state->bs);
+ if (ret < 0) {
+ goto fail;
+ }
}
}
diff --git a/tests/qemu-iotests/039 b/tests/qemu-iotests/039
index 94a8bfe754e..3d0c073d65d 100755
--- a/tests/qemu-iotests/039
+++ b/tests/qemu-iotests/039
@@ -95,6 +95,40 @@ $QEMU_IMG info --image-opts \
# The dirty bit must still be set: this open never wrote any guest data
_qcow2_dump_header | grep incompatible_features
+echo
+echo "== Read-only reopen must not clear the dirty bit =="
+
+# A read-only node cannot write the header, and must keep the dirty bit
+$QEMU_IO -r -c "reopen -r" -c "read -P 0x5a 0 512" "$TEST_IMG" \
+ | _filter_qemu_io
+
+# The dirty bit must still be set
+_qcow2_dump_header | grep incompatible_features
+
+echo
+echo "== Read-only reopen must not write through a writable file node =="
+
+# The write the header update needs is refused by the permission system
+echo "{'execute': 'qmp_capabilities'}
+ {'execute': 'blockdev-reopen',
+ 'arguments': {'options': [{'node-name': 'drive',
+ 'driver': 'qcow2',
+ 'read-only': true,
+ 'file': 'prot'}]}}
+ {'execute': 'quit'}" \
+ | $QEMU -qmp stdio -nographic -nodefaults \
+ -blockdev "{'node-name': 'prot',
+ 'driver': 'file',
+ 'filename': '$TEST_IMG'}" \
+ -blockdev "{'node-name': 'drive',
+ 'driver': 'qcow2',
+ 'file': 'prot',
+ 'read-only': true}" \
+ | _filter_qmp
+
+# The dirty bit must still be set
+_qcow2_dump_header | grep incompatible_features
+
echo
echo "== Repairing the image file must succeed =="
@@ -108,6 +142,22 @@ echo "== Data should still be accessible after repair =="
$QEMU_IO -c "read -P 0x5a 0 512" "$TEST_IMG" | _filter_qemu_io
+echo
+echo "== A read-write to read-only reopen must clear the dirty bit =="
+
+_make_test_img -o "compat=1.1,lazy_refcounts=on" $size
+
+# The kill keeps the close from clearing the bit, so the header shows what
+# the reopen did with it
+_NO_VALGRIND \
+$QEMU_IO -c "write -P 0x5a 0 512" \
+ -c "reopen -r" \
+ -c "sigraise $(kill -l KILL)" "$TEST_IMG" 2>&1 \
+ | _filter_qemu_io
+
+# The dirty bit must not be set
+_qcow2_dump_header | grep incompatible_features
+
echo
echo "== Opening a dirty image read/write should repair it =="
diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out
index c66361128f6..ce8ee577216 100644
--- a/tests/qemu-iotests/039.out
+++ b/tests/qemu-iotests/039.out
@@ -27,6 +27,19 @@ incompatible_features [0]
== Read-only open must not crash on close ==
incompatible_features [0]
+== Read-only reopen must not clear the dirty bit ==
+read 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features [0]
+
+== Read-only reopen must not write through a writable file node ==
+QMP_VERSION
+{"return": {}}
+{"return": {}}
+{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}
+{"return": {}}
+incompatible_features [0]
+
== Repairing the image file must succeed ==
ERROR cluster 5 refcount=0 reference=1
Rebuilding refcount structure
@@ -45,6 +58,13 @@ incompatible_features []
read 512/512 bytes at offset 0
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+== A read-write to read-only reopen must clear the dirty bit ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+./common.rc: Killed ( VALGRIND_QEMU="${VALGRIND_QEMU_IO}" _qemu_proc_exec "${VALGRIND_LOGFILE}" "$QEMU_IO_PROG" $QEMU_IO_ARGS "$@" )
+incompatible_features []
+
== Opening a dirty image read/write should repair it ==
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
wrote 512/512 bytes at offset 0
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073
2026-11-04 14:41 [PATCH hci-dev 0/2] qcow2, block: fix reopen of a dirty read-only node and of a corrupt node #VSTOR-140073 Denis V. Lunev
2026-11-04 14:41 ` [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only " Denis V. Lunev
@ 2026-11-04 14:41 ` Denis V. Lunev
2026-11-04 14:43 ` no-reply
1 sibling, 1 reply; 5+ messages in thread
From: Denis V. Lunev @ 2026-11-04 14:41 UTC (permalink / raw)
To: andrey.drobyshev; +Cc: svt-core, den
From: Denis V. Lunev <den@openvz.org>
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.
[ Ported to hci-dev: HMP qemu-io here prints a failed read on the
monitor and returns an empty string, so 060.out expects that instead
of the error in the return value. ]
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>
(cherry picked from commit efe98c2a0932c09ada26b8afac3606a66f561933)
---
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 1dc7e47431c..97c813683d1 100644
--- a/block.c
+++ b/block.c
@@ -4526,6 +4526,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;
@@ -4921,9 +4926,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 5cd21a6f686..ce49fc34ecc 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 a37bf446e94..ad1912a43be 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
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only node #VSTOR-140073
2026-11-04 14:41 ` [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only " Denis V. Lunev
@ 2026-11-04 14:43 ` no-reply
0 siblings, 0 replies; 5+ messages in thread
From: no-reply @ 2026-11-04 14:43 UTC (permalink / raw)
To: andrey.drobyshev, Denis V. Lunev; +Cc: svt-core, den
> From: Denis V. Lunev <den@openvz.org>
>
> qcow2_reopen_prepare() clears the dirty bit whenever the node is
> reopened read-only, with an unguarded header write. A read-only node
> can still be dirty, inherited from an earlier writable session, and it
> holds no BLK_PERM_WRITE to resolve that. A read-only to read-only
> reopen of a dirty image therefore fails outright:
>
> $ qemu-io -r -f qcow2 dirty.qcow2 <<< $'reopen -r\nquit'
> qemu-io: failed while preparing to reopen image 'dirty.qcow2'
>
> Where the file node below is writable the write is not refused early,
> and bdrv_co_write_req_prepare() aborts on its BLK_PERM_WRITE assertion
> instead.
>
> Clear it only for a node that is writable now, the predicate
> qcow2_do_open() already uses for the repair. bdrv_is_writable() also
> excludes an inactive node, whose header must not be touched either.
>
> 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-2-den@openvz.org>
> Reviewed-by: Kevin Wolf <kwolf@redhat.com>
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261104144113.1587724-1-den@openvz.org?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073
2026-11-04 14:41 ` [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073 Denis V. Lunev
@ 2026-11-04 14:43 ` no-reply
0 siblings, 0 replies; 5+ messages in thread
From: no-reply @ 2026-11-04 14:43 UTC (permalink / raw)
To: Denis V. Lunev, andrey.drobyshev; +Cc: svt-core, den
> From: Denis V. Lunev <den@openvz.org>
>
> 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.
> [ ... ]
> 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261104144113.1587724-1-den@openvz.org?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-04 14:44 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 14:41 [PATCH hci-dev 0/2] qcow2, block: fix reopen of a dirty read-only node and of a corrupt node #VSTOR-140073 Denis V. Lunev
2026-11-04 14:41 ` [PATCH hci-dev 1/2] qcow2: do not clear the dirty bit when reopening a read-only " Denis V. Lunev
2026-11-04 14:43 ` no-reply
2026-11-04 14:41 ` [PATCH hci-dev 2/2] block: reject a reopen of an unusable node instead of crashing #VSTOR-140073 Denis V. Lunev
2026-11-04 14:43 ` no-reply
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.