From 1dc1438e7d6332a468f49b7f47033a48bc606421 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Thu, 26 Mar 2026 11:48:09 +0100 Subject: [PATCH] * Thu Mar 26 2026 Miroslav Rezanina - 10.1.0-15 - kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch [RHEL-155601] - Resolves: RHEL-155601 (Mirror job can miss writes during startup, corrupting the copy [rhel-10.2]) --- ...d-dirty-bitmap-writes-during-startup.patch | 162 ++++++++++++++++++ qemu-kvm.spec | 9 +- 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch diff --git a/kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch b/kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch new file mode 100644 index 0000000..9adf132 --- /dev/null +++ b/kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch @@ -0,0 +1,162 @@ +From 19a86e8f7e88b47ac40014cd716146f0754939c2 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Thu, 19 Feb 2026 21:24:46 +0100 +Subject: [PATCH] mirror: Fix missed dirty bitmap writes during startup + +RH-Author: Kevin Wolf +RH-MergeRequest: 474: mirror: Fix missed dirty bitmap writes during startup +RH-Jira: RHEL-155601 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +RH-Commit: [1/1] a888c4b2085d5fc3639bf6721f1f4e974db65dba (kmwolf/centos-qemu-kvm) + +Currently, mirror disables the block layer's dirty bitmap before its own +replacement is working. This means that during startup, there is a +window in which the allocation status of blocks in the source has +already been checked, but new writes coming in aren't tracked yet, +resulting in a corrupted copy: + +1. Dirty bitmap is disabled in mirror_start_job() +2. Some request are started in mirror_top_bs while s->job == NULL +3. mirror_dirty_init() -> bdrv_co_is_allocated_above() runs and because + the request hasn't completed yet, the block isn't allocated +4. The request completes, still sees s->job == NULL and skips the + bitmap, and nothing else will mark it dirty either + +One ingredient is that mirror_top_opaque->job is only set after the +job is fully initialized. For the rationale, see commit 32125b1460 +("mirror: Fix access of uninitialised fields during start"). + +Fix this by giving mirror_top_bs access to dirty_bitmap and enabling it +to track writes from the beginning. Disabling the block layer's tracking +and enabling the mirror_top_bs one happens in a drained section, so +there is no danger of races with in-flight requests any more. All of +this happens well before the block allocation status is checked, so we +can be sure that no writes will be missed. + +Cc: qemu-stable@nongnu.org +Closes: https://gitlab.com/qemu-project/qemu/-/issues/3273 +Fixes: 32125b14606a ('mirror: Fix access of uninitialised fields during start') +Signed-off-by: Kevin Wolf +Message-ID: <20260219202446.312493-1-kwolf@redhat.com> +Reviewed-by: Fiona Ebner +Tested-by: Jean-Louis Dupond +Signed-off-by: Kevin Wolf +(cherry picked from commit 0f51f9c3420b31bb383e456dd7bf24d3056eeb73) +Signed-off-by: Kevin Wolf +--- + block/mirror.c | 52 +++++++++++++++++++++++++++++++------------------- + 1 file changed, 32 insertions(+), 20 deletions(-) + +diff --git a/block/mirror.c b/block/mirror.c +index b344182c74..f01be99b55 100644 +--- a/block/mirror.c ++++ b/block/mirror.c +@@ -99,6 +99,7 @@ typedef struct MirrorBlockJob { + + typedef struct MirrorBDSOpaque { + MirrorBlockJob *job; ++ BdrvDirtyBitmap *dirty_bitmap; + bool stop; + bool is_commit; + } MirrorBDSOpaque; +@@ -1672,9 +1673,11 @@ bdrv_mirror_top_do_write(BlockDriverState *bs, MirrorMethod method, + abort(); + } + +- if (!copy_to_target && s->job && s->job->dirty_bitmap) { +- qatomic_set(&s->job->actively_synced, false); +- bdrv_set_dirty_bitmap(s->job->dirty_bitmap, offset, bytes); ++ if (!copy_to_target) { ++ if (s->job) { ++ qatomic_set(&s->job->actively_synced, false); ++ } ++ bdrv_set_dirty_bitmap(s->dirty_bitmap, offset, bytes); + } + + if (ret < 0) { +@@ -1901,13 +1904,35 @@ static BlockJob *mirror_start_job( + + bdrv_drained_begin(bs); + ret = bdrv_append(mirror_top_bs, bs, errp); +- bdrv_drained_end(bs); +- + if (ret < 0) { ++ bdrv_drained_end(bs); ++ bdrv_unref(mirror_top_bs); ++ return NULL; ++ } ++ ++ bs_opaque->dirty_bitmap = bdrv_create_dirty_bitmap(mirror_top_bs, ++ granularity, ++ NULL, errp); ++ if (!bs_opaque->dirty_bitmap) { ++ bdrv_drained_end(bs); + bdrv_unref(mirror_top_bs); + return NULL; + } + ++ /* ++ * The mirror job doesn't use the block layer's dirty tracking because it ++ * needs to be able to switch seemlessly between background copy mode (which ++ * does need dirty tracking) and write blocking mode (which doesn't) and ++ * doing that would require draining the node. Instead, mirror_top_bs takes ++ * care of updating the dirty bitmap as appropriate. ++ * ++ * Note that write blocking mode only becomes effective after mirror_run() ++ * sets mirror_top_opaque->job (see should_copy_to_target()). Until then, ++ * we're still in background copy mode irrespective of @copy_mode. ++ */ ++ bdrv_disable_dirty_bitmap(bs_opaque->dirty_bitmap); ++ bdrv_drained_end(bs); ++ + /* Make sure that the source is not resized while the job is running */ + s = block_job_create(job_id, driver, NULL, mirror_top_bs, + BLK_PERM_CONSISTENT_READ, +@@ -2002,24 +2027,13 @@ static BlockJob *mirror_start_job( + s->base_overlay = bdrv_find_overlay(bs, base); + s->granularity = granularity; + s->buf_size = ROUND_UP(buf_size, granularity); ++ s->dirty_bitmap = bs_opaque->dirty_bitmap; + s->unmap = unmap; + if (auto_complete) { + s->should_complete = true; + } + bdrv_graph_rdunlock_main_loop(); + +- s->dirty_bitmap = bdrv_create_dirty_bitmap(s->mirror_top_bs, granularity, +- NULL, errp); +- if (!s->dirty_bitmap) { +- goto fail; +- } +- +- /* +- * The dirty bitmap is set by bdrv_mirror_top_do_write() when not in active +- * mode. +- */ +- bdrv_disable_dirty_bitmap(s->dirty_bitmap); +- + bdrv_graph_wrlock_drained(); + ret = block_job_add_bdrv(&s->common, "source", bs, 0, + BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | +@@ -2099,9 +2113,6 @@ fail: + g_free(s->replaces); + blk_unref(s->target); + bs_opaque->job = NULL; +- if (s->dirty_bitmap) { +- bdrv_release_dirty_bitmap(s->dirty_bitmap); +- } + job_early_fail(&s->common.job); + } + +@@ -2115,6 +2126,7 @@ fail: + bdrv_graph_wrunlock(); + bdrv_drained_end(bs); + ++ bdrv_release_dirty_bitmap(bs_opaque->dirty_bitmap); + bdrv_unref(mirror_top_bs); + + return NULL; +-- +2.47.3 + diff --git a/qemu-kvm.spec b/qemu-kvm.spec index 8abf356..40ac1d0 100644 --- a/qemu-kvm.spec +++ b/qemu-kvm.spec @@ -143,7 +143,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \ Summary: QEMU is a machine emulator and virtualizer Name: qemu-kvm Version: 10.1.0 -Release: 14%{?rcrel}%{?dist}%{?cc_suffix} +Release: 15%{?rcrel}%{?dist}%{?cc_suffix} # Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped # Epoch 15 used for RHEL 8 # Epoch 17 used for RHEL 9 (due to release versioning offset in RHEL 8.5) @@ -412,6 +412,8 @@ Patch127: kvm-Revert-hw-arm-virt-Use-ACPI-PCI-hotplug-by-default-f.patch Patch128: kvm-hw-uefi-add-variable-digest-to-vmstate.patch # For RHEL-144004 - [rhel-10] Regression in BLOCK_IO_ERROR event delivery with (w|r)error setting of 'stop' or 'enospc' due to event rate limiting Patch129: kvm-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch +# For RHEL-155601 - Mirror job can miss writes during startup, corrupting the copy [rhel-10.2] +Patch130: kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch %if %{have_clang} BuildRequires: clang @@ -1491,6 +1493,11 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ %endif %changelog +* Thu Mar 26 2026 Miroslav Rezanina - 10.1.0-15 +- kvm-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch [RHEL-155601] +- Resolves: RHEL-155601 + (Mirror job can miss writes during startup, corrupting the copy [rhel-10.2]) + * Wed Mar 18 2026 Miroslav Rezanina - 10.1.0-14 - kvm-hw-uefi-add-variable-digest-to-vmstate.patch [RHEL-153058] - kvm-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch [RHEL-144004]