Commit 3fe45543bf for qemu.org
commit 3fe45543bf42ae60be1fb20e7f0b5dba6210e810
Author: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Date: Thu Nov 5 18:15:14 2026 +0200
qcow2: fix snapshot data corruption with discard-no-unref=on
Whenever option discard-no-unref=on is enabled, we bypass refcount
machinery and unconditionally punch hole in the data on 'discard' and
'write -z -u' paths. However, when this same cluster is also referenced
by an internal snapshot, such operations lead to problems. The active L2
table is copied before it is changed, so the snapshot metadata stays
correct, but the host range under it is punched out and the snapshot reads
zeroes there afterwards. qemu-img check finds nothing wrong.
Fix this by guarding the 'keep_reference' flag by the presence of the
QCOW_OFLAG_COPIED bit. I.e. only proceed with unconditional data
punching if we own this cluster exclusively.
Cc: qemu-stable@nongnu.org
Fixes: 42a2890a76f4 ("qcow2: add discard-no-unref option")
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20261105161515.794213-3-andrey.drobyshev@virtuozzo.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index e02fae6a0c..cc473c7c79 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -1953,6 +1953,7 @@ discard_in_l2_slice(BlockDriverState *bs, uint64_t offset, uint64_t nb_clusters,
QCow2ClusterType cluster_type =
qcow2_get_cluster_type(bs, old_l2_entry);
bool keep_reference = (cluster_type != QCOW2_CLUSTER_COMPRESSED) &&
+ (old_l2_entry & QCOW_OFLAG_COPIED) &&
!full_discard &&
(s->discard_no_unref &&
type == QCOW2_DISCARD_REQUEST);
@@ -2097,8 +2098,9 @@ zero_in_l2_slice(BlockDriverState *bs, uint64_t offset,
QCow2ClusterType type = qcow2_get_cluster_type(bs, old_l2_entry);
bool unmap = (type == QCOW2_CLUSTER_COMPRESSED) ||
((flags & BDRV_REQ_MAY_UNMAP) && qcow2_cluster_is_allocated(type));
- bool keep_reference =
- (s->discard_no_unref && type != QCOW2_CLUSTER_COMPRESSED);
+ bool keep_reference = s->discard_no_unref &&
+ type != QCOW2_CLUSTER_COMPRESSED &&
+ (old_l2_entry & QCOW_OFLAG_COPIED);
uint64_t new_l2_entry = old_l2_entry;
uint64_t new_l2_bitmap = old_l2_bitmap;