* Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22

- kvm-blkdebug-Add-delay-ns-option.patch [RHEL-121686]
- kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch [RHEL-121686]
- kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch [RHEL-121686]
- kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch [RHEL-121686]
- kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch [RHEL-121686]
- kvm-ide-test-Factor-out-wait_dma_completion.patch [RHEL-121686]
- kvm-ide-test-Test-reset-during-TRIM.patch [RHEL-121686]
- kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch [RHEL-186384]
- kvm-block-curl-fix-curl-internal-handles-handling.patch [RHEL-186384]
- kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch [RHEL-186384]
- kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch [RHEL-186384]
- kvm-block-curl-fix-concurrent-completion-handling.patch [RHEL-186384]
- kvm-block-curl-free-s-password-in-cleanup-paths.patch [RHEL-186384]
- kvm-nvme-Kick-and-check-completions-in-BDS-context.patch [RHEL-186384]
- kvm-nvme-Note-in-which-AioContext-some-functions-run.patch [RHEL-186384]
- kvm-block-remove-detached-header-option-from-opts-after-.patch [RHEL-186384]
- kvm-block-fix-luks-amend-when-run-in-coroutine.patch [RHEL-186384]
- kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch [RHEL-186384]
- kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch [RHEL-186384]
- kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch [RHEL-186384]
- kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch [RHEL-186384]
- kvm-qemu-img-Fix-amend-option-parse-error-handling.patch [RHEL-186384]
- kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch [RHEL-186384]
- kvm-python-backport-drop-Python3.6-workarounds.patch [RHEL-186384]
- kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch [RHEL-186384]
- kvm-python-backport-avoid-creating-additional-event-loop.patch [RHEL-186384]
- kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch [RHEL-186384]
- kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch [RHEL-186384]
- kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch [RHEL-186384]
- kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch [RHEL-186384]
- kvm-async-access-bottom-half-flags-with-qatomic_read.patch [RHEL-186384]
- kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch [RHEL-186384]
- kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch [RHEL-186384]
- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-186384]
- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-186384]
- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-186384]
- kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch [RHEL-186384]
- kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch [RHEL-186384]
- kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch [RHEL-186384]
- kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch [RHEL-186384]
- kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch [RHEL-186384]
- kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch [RHEL-186384]
- kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch [RHEL-186384]
- kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch [RHEL-186384]
- kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch [RHEL-186384]
- kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch [RHEL-186384]
- kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch [RHEL-186384]
- kvm-qemu-io-Add-aio_discard-command.patch [RHEL-186384]
- kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch [RHEL-186384]
- kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch [RHEL-186384]
- kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch [RHEL-186384]
- kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch [RHEL-186384]
- Resolves: RHEL-121686
  (qemu-kvm hung during drain after double pause)
- Resolves: RHEL-186384
  (virt-storage: Backport stable branch fixes)
This commit is contained in:
Miroslav Rezanina 2026-06-23 09:22:28 +02:00
parent 704660b7d7
commit 6c478a495d
53 changed files with 6194 additions and 1 deletions

View File

@ -0,0 +1,69 @@
From 649398182d4897325f37cee57f81ae9f22272d66 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <pbonzini@redhat.com>
Date: Mon, 13 Oct 2025 18:24:54 +0200
Subject: [PATCH 31/52] async: access bottom half flags with qatomic_read
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [24/45] c2367e9cbd60ef60a2a2a61cda56c31f4bd9a00b (kmwolf/centos-qemu-kvm)
Running test-aio-multithread under TSAN reveals data races on bh->flags.
Because bottom halves may be scheduled or canceled asynchronously,
without taking a lock, adjust aio_compute_bh_timeout() and aio_ctx_check()
to use a relaxed read to access the flags.
Use an acquire load to ensure that anything that was written prior to
qemu_bh_schedule() is visible.
Closes: https://gitlab.com/qemu-project/qemu/-/issues/2749
Closes: https://gitlab.com/qemu-project/qemu/-/issues/851
Cc: qemu-stable@nongnu.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit 5142397c79330aab9bef3230991c8ac0c251110f)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
util/async.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/util/async.c b/util/async.c
index 2719c629ae..a736d2cd0d 100644
--- a/util/async.c
+++ b/util/async.c
@@ -256,8 +256,9 @@ static int64_t aio_compute_bh_timeout(BHList *head, int timeout)
QEMUBH *bh;
QSLIST_FOREACH_RCU(bh, head, next) {
- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
- if (bh->flags & BH_IDLE) {
+ int flags = qatomic_load_acquire(&bh->flags);
+ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+ if (flags & BH_IDLE) {
/* idle bottom halves will be polled at least
* every 10ms */
timeout = 10000000;
@@ -335,14 +336,16 @@ aio_ctx_check(GSource *source)
aio_notify_accept(ctx);
QSLIST_FOREACH_RCU(bh, &ctx->bh_list, next) {
- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+ int flags = qatomic_load_acquire(&bh->flags);
+ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
return true;
}
}
QSIMPLEQ_FOREACH(s, &ctx->bh_slice_list, next) {
QSLIST_FOREACH_RCU(bh, &s->bh_list, next) {
- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+ int flags = qatomic_load_acquire(&bh->flags);
+ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
return true;
}
}
--
2.52.0

View File

@ -0,0 +1,121 @@
From 6386e658adc798b2af7400a53941523d21dd581d Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:26 +0200
Subject: [PATCH 01/52] blkdebug: Add 'delay-ns' option
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [1/7] b6ade079279909f3b38a03fc710c1f0ebd475915 (kmwolf/centos-qemu-kvm)
Sometimes reproducing a problem for debugging involves slow I/O, so
let's add something to blkdebug to make I/O slow when we need it. This
can be used either together with an error so that the request fails
after the delay, or with errno=0, which allows the request to succeed
after the delay.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-2-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit d5e4090177ad382e01084a1594a1a60a69f4c1cd)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/blkdebug.c | 15 ++++++++++++++-
qapi/block-core.json | 4 ++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/block/blkdebug.c b/block/blkdebug.c
index c54aee0c84..8954fc2977 100644
--- a/block/blkdebug.c
+++ b/block/blkdebug.c
@@ -95,6 +95,7 @@ typedef struct BlkdebugRule {
int immediately;
int once;
int64_t offset;
+ int64_t delay_ns;
} inject;
struct {
int new_state;
@@ -144,6 +145,10 @@ static QemuOptsList inject_error_opts = {
.name = "immediately",
.type = QEMU_OPT_BOOL,
},
+ {
+ .name = "delay-ns",
+ .type = QEMU_OPT_NUMBER,
+ },
{ /* end of list */ }
},
};
@@ -216,6 +221,8 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0);
rule->options.inject.immediately =
qemu_opt_get_bool(opts, "immediately", 0);
+ rule->options.inject.delay_ns =
+ qemu_opt_get_number(opts, "delay-ns", 0);
sector = qemu_opt_get_number(opts, "sector", -1);
rule->options.inject.offset =
sector == -1 ? -1 : sector * BDRV_SECTOR_SIZE;
@@ -594,6 +601,7 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset,
BlkdebugRule *rule = NULL;
int error;
bool immediately;
+ int64_t delay_ns;
qemu_mutex_lock(&s->lock);
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
@@ -608,13 +616,14 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset,
}
}
- if (!rule || !rule->options.inject.error) {
+ if (!rule) {
qemu_mutex_unlock(&s->lock);
return 0;
}
immediately = rule->options.inject.immediately;
error = rule->options.inject.error;
+ delay_ns = rule->options.inject.delay_ns;
if (rule->options.inject.once) {
QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
@@ -622,6 +631,10 @@ static int coroutine_fn rule_check(BlockDriverState *bs, uint64_t offset,
}
qemu_mutex_unlock(&s->lock);
+
+ if (delay_ns) {
+ qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, delay_ns);
+ }
if (!immediately) {
aio_co_schedule(qemu_get_current_aio_context(), qemu_coroutine_self());
qemu_coroutine_yield();
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 0236936139..6cf8c1b9c8 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3909,6 +3909,9 @@
#
# @errno: error identifier (errno) to be returned; defaults to EIO
#
+# @delay-ns: request delay before completion in nanoseconds
+# (default: 0, since: 11.1)
+#
# @sector: specifies the sector index which has to be affected in
# order to actually trigger the event; defaults to "any sector"
#
@@ -3924,6 +3927,7 @@
'*state': 'int',
'*iotype': 'BlkdebugIOType',
'*errno': 'int',
+ '*delay-ns': 'int',
'*sector': 'int',
'*once': 'bool',
'*immediately': 'bool' } }
--
2.52.0

View File

@ -0,0 +1,203 @@
From c452d113b1d71de097e3b87f7d40d6865731b275 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:27 +0200
Subject: [PATCH 02/52] block: Add blk_co_start/end_request() and
BDRV_REQ_NO_QUEUE
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [2/7] 4053fabc93d73e2c7143763fa2676c7be7ad249d (kmwolf/centos-qemu-kvm)
If a device uses blk_inc/dec_in_flight() in order to build macro
operations that involve multiple requests for the block layer and that
need to be completed as a unit before the BlockBackend can be considered
drained, it sets the stage for a deadlock: When a drain is requested,
the inner request at the BlockBackend level will be queued in
blk_wait_while_drained() and wait until the drained section ends, but at
the same time, drain_begin can only return if the whole macro operation
at the device level has completed.
Introduce a new interface to allow implementing the logic correctly:
Instead of queueing individual requests, blk_co_start_request() calls
blk_wait_while_drained() once at the beginning. The individual requests
must then set BDRV_REQ_NO_QUEUE to avoid being queued and running into
the deadlock; being wrapped in blk_co_start/end_request() makes sure
that drain_begin waits for them and they don't sneak in when the
BlockBackend is supposed to already be quiescent.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-3-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 34a67637767d3ed1ac813c44effe827bbfba5996)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/block-backend.c | 38 +++++++++++++++++++++++--------
include/block/block-common.h | 11 ++++++++-
include/system/block-backend-io.h | 2 ++
3 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/block/block-backend.c b/block/block-backend.c
index d6df369188..a0f4c841da 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -82,6 +82,7 @@ struct BlockBackend {
QemuMutex queued_requests_lock; /* protects queued_requests */
CoQueue queued_requests;
bool disable_request_queuing; /* atomic */
+ int start_request_count; /* atomic */
VMChangeStateEntry *vmsh;
bool force_allow_inactivate;
@@ -1306,10 +1307,16 @@ bool blk_in_drain(BlockBackend *blk)
}
/* To be called between exactly one pair of blk_inc/dec_in_flight() */
-static void coroutine_fn blk_wait_while_drained(BlockBackend *blk)
+static void coroutine_fn blk_wait_while_drained(BlockBackend *blk,
+ BdrvRequestFlags flags)
{
assert(blk->in_flight > 0);
+ if (flags & BDRV_REQ_NO_QUEUE) {
+ assert(qatomic_read(&blk->start_request_count));
+ return;
+ }
+
if (qatomic_read(&blk->quiesce_counter) &&
!qatomic_read(&blk->disable_request_queuing)) {
/*
@@ -1335,7 +1342,7 @@ blk_co_do_preadv_part(BlockBackend *blk, int64_t offset, int64_t bytes,
BlockDriverState *bs;
IO_CODE();
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, flags);
GRAPH_RDLOCK_GUARD();
/* Call blk_bs() only after waiting, the graph may have changed */
@@ -1410,7 +1417,7 @@ blk_co_do_pwritev_part(BlockBackend *blk, int64_t offset, int64_t bytes,
BlockDriverState *bs;
IO_CODE();
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, flags);
GRAPH_RDLOCK_GUARD();
/* Call blk_bs() only after waiting, the graph may have changed */
@@ -1523,6 +1530,19 @@ void blk_dec_in_flight(BlockBackend *blk)
aio_wait_kick();
}
+void coroutine_fn blk_co_start_request(BlockBackend *blk)
+{
+ blk_inc_in_flight(blk);
+ blk_wait_while_drained(blk, 0);
+ qatomic_inc(&blk->start_request_count);
+}
+
+void blk_end_request(BlockBackend *blk)
+{
+ qatomic_dec(&blk->start_request_count);
+ blk_dec_in_flight(blk);
+}
+
static void error_callback_bh(void *opaque)
{
struct BlockBackendAIOCB *acb = opaque;
@@ -1741,7 +1761,7 @@ blk_co_do_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
{
IO_CODE();
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, 0);
GRAPH_RDLOCK_GUARD();
if (!blk_co_is_available(blk)) {
@@ -1788,7 +1808,7 @@ blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes)
int ret;
IO_CODE();
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, 0);
GRAPH_RDLOCK_GUARD();
ret = blk_check_byte_request(blk, offset, bytes);
@@ -1834,7 +1854,7 @@ int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset,
static int coroutine_fn blk_co_do_flush(BlockBackend *blk)
{
IO_CODE();
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, 0);
GRAPH_RDLOCK_GUARD();
if (!blk_co_is_available(blk)) {
@@ -2009,7 +2029,7 @@ int coroutine_fn blk_co_zone_report(BlockBackend *blk, int64_t offset,
IO_CODE();
blk_inc_in_flight(blk); /* increase before waiting */
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, 0);
GRAPH_RDLOCK_GUARD();
if (!blk_is_available(blk)) {
blk_dec_in_flight(blk);
@@ -2034,7 +2054,7 @@ int coroutine_fn blk_co_zone_mgmt(BlockBackend *blk, BlockZoneOp op,
IO_CODE();
blk_inc_in_flight(blk);
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, 0);
GRAPH_RDLOCK_GUARD();
ret = blk_check_byte_request(blk, offset, len);
@@ -2058,7 +2078,7 @@ int coroutine_fn blk_co_zone_append(BlockBackend *blk, int64_t *offset,
IO_CODE();
blk_inc_in_flight(blk);
- blk_wait_while_drained(blk);
+ blk_wait_while_drained(blk, flags);
GRAPH_RDLOCK_GUARD();
if (!blk_is_available(blk)) {
blk_dec_in_flight(blk);
diff --git a/include/block/block-common.h b/include/block/block-common.h
index c8c626daea..895ea17541 100644
--- a/include/block/block-common.h
+++ b/include/block/block-common.h
@@ -215,8 +215,17 @@ typedef enum {
*/
BDRV_REQ_NO_WAIT = 0x400,
+ /*
+ * Used between blk_co_start_request() and blk_end_request() to avoid
+ * that the request waits in a drained BlockBackend until the drained
+ * section ends. Waiting would cause a deadlock because drain waits for
+ * blk_end_request() to be called, but the request never completes
+ * because it waits for the drain to end.
+ */
+ BDRV_REQ_NO_QUEUE = 0x800,
+
/* Mask of valid flags */
- BDRV_REQ_MASK = 0x7ff,
+ BDRV_REQ_MASK = 0xfff,
} BdrvRequestFlags;
#define BDRV_O_NO_SHARE 0x0001 /* don't share permissions */
diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h
index ba8dfcc7d0..59841e04a8 100644
--- a/include/system/block-backend-io.h
+++ b/include/system/block-backend-io.h
@@ -71,6 +71,8 @@ BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
void blk_inc_in_flight(BlockBackend *blk);
void blk_dec_in_flight(BlockBackend *blk);
+void coroutine_fn blk_co_start_request(BlockBackend *blk);
+void blk_end_request(BlockBackend *blk);
bool coroutine_fn GRAPH_RDLOCK blk_co_is_inserted(BlockBackend *blk);
bool co_wrapper_mixed_bdrv_rdlock blk_is_inserted(BlockBackend *blk);
--
2.52.0

View File

@ -0,0 +1,174 @@
From 9a2d6f97d94f24ad7bfc83aea06d3324cbd46424 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:28 +0200
Subject: [PATCH 03/52] block: Add flags parameter to blk_*_pdiscard()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [3/7] 40ce25b1169cccc9c655eeb59827f8bf7daf75f4 (kmwolf/centos-qemu-kvm)
All existing callers pass 0, but we need a way to pass BDRV_REQ_NO_QUEUE
for discard requests.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-4-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 53074ba0330ae8831abbae2521c012e1d9072ed3)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/block-backend.c | 11 ++++++-----
block/export/virtio-blk-handler.c | 2 +-
block/mirror.c | 4 ++--
include/system/block-backend-io.h | 4 ++--
nbd/server.c | 2 +-
qemu-io-cmds.c | 2 +-
tests/unit/test-block-iothread.c | 4 ++--
7 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/block/block-backend.c b/block/block-backend.c
index a0f4c841da..d97b26b743 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -1803,12 +1803,13 @@ BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
/* To be called between exactly one pair of blk_inc/dec_in_flight() */
static int coroutine_fn
-blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes)
+blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes,
+ BdrvRequestFlags flags)
{
int ret;
IO_CODE();
- blk_wait_while_drained(blk, 0);
+ blk_wait_while_drained(blk, flags);
GRAPH_RDLOCK_GUARD();
ret = blk_check_byte_request(blk, offset, bytes);
@@ -1824,7 +1825,7 @@ static void coroutine_fn blk_aio_pdiscard_entry(void *opaque)
BlkAioEmAIOCB *acb = opaque;
BlkRwCo *rwco = &acb->rwco;
- rwco->ret = blk_co_do_pdiscard(rwco->blk, rwco->offset, acb->bytes);
+ rwco->ret = blk_co_do_pdiscard(rwco->blk, rwco->offset, acb->bytes, 0);
blk_aio_complete(acb);
}
@@ -1838,13 +1839,13 @@ BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk,
}
int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset,
- int64_t bytes)
+ int64_t bytes, BdrvRequestFlags flags)
{
int ret;
IO_OR_GS_CODE();
blk_inc_in_flight(blk);
- ret = blk_co_do_pdiscard(blk, offset, bytes);
+ ret = blk_co_do_pdiscard(blk, offset, bytes, flags);
blk_dec_in_flight(blk);
return ret;
diff --git a/block/export/virtio-blk-handler.c b/block/export/virtio-blk-handler.c
index bc1cec6757..b82baae553 100644
--- a/block/export/virtio-blk-handler.c
+++ b/block/export/virtio-blk-handler.c
@@ -121,7 +121,7 @@ virtio_blk_discard_write_zeroes(VirtioBlkHandler *handler, struct iovec *iov,
}
if (blk_co_pdiscard(blk, sector << VIRTIO_BLK_SECTOR_BITS,
- bytes) == 0) {
+ bytes, 0) == 0) {
return VIRTIO_BLK_S_OK;
}
}
diff --git a/block/mirror.c b/block/mirror.c
index f01be99b55..c87f1e205b 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -454,7 +454,7 @@ static void coroutine_fn mirror_co_discard(void *opaque)
*op->bytes_handled = op->bytes;
op->is_in_flight = true;
- ret = blk_co_pdiscard(op->s->target, op->offset, op->bytes);
+ ret = blk_co_pdiscard(op->s->target, op->offset, op->bytes, 0);
mirror_write_complete(op, ret);
}
@@ -1527,7 +1527,7 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
zero_bitmap_end - zero_bitmap_offset);
}
assert(!qiov);
- ret = blk_co_pdiscard(job->target, offset, bytes);
+ ret = blk_co_pdiscard(job->target, offset, bytes, 0);
break;
default:
diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h
index 59841e04a8..91c48299b7 100644
--- a/include/system/block-backend-io.h
+++ b/include/system/block-backend-io.h
@@ -217,9 +217,9 @@ int co_wrapper_mixed blk_zone_append(BlockBackend *blk, int64_t *offset,
BdrvRequestFlags flags);
int co_wrapper_mixed blk_pdiscard(BlockBackend *blk, int64_t offset,
- int64_t bytes);
+ int64_t bytes, BdrvRequestFlags flags);
int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset,
- int64_t bytes);
+ int64_t bytes, BdrvRequestFlags flags);
int co_wrapper_mixed blk_flush(BlockBackend *blk);
int coroutine_fn blk_co_flush(BlockBackend *blk);
diff --git a/nbd/server.c b/nbd/server.c
index acec0487a8..bd103a8840 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -2984,7 +2984,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
"flush failed", errp);
case NBD_CMD_TRIM:
- ret = blk_co_pdiscard(exp->common.blk, request->from, request->len);
+ ret = blk_co_pdiscard(exp->common.blk, request->from, request->len, 0);
if (ret >= 0 && request->flags & NBD_CMD_FLAG_FUA) {
ret = blk_co_flush(exp->common.blk);
}
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 13e0330162..f6d077908f 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -2201,7 +2201,7 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
}
clock_gettime(CLOCK_MONOTONIC, &t1);
- ret = blk_pdiscard(blk, offset, bytes);
+ ret = blk_pdiscard(blk, offset, bytes, 0);
clock_gettime(CLOCK_MONOTONIC, &t2);
if (ret < 0) {
diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c
index e26b3be593..5273ff235a 100644
--- a/tests/unit/test-block-iothread.c
+++ b/tests/unit/test-block-iothread.c
@@ -270,11 +270,11 @@ static void test_sync_op_blk_pdiscard(BlockBackend *blk)
int ret;
/* Early success: UNMAP not supported */
- ret = blk_pdiscard(blk, 0, 512);
+ ret = blk_pdiscard(blk, 0, 512, 0);
g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */
- ret = blk_pdiscard(blk, -2, 512);
+ ret = blk_pdiscard(blk, -2, 512, 0);
g_assert_cmpint(ret, ==, -EIO);
}
--
2.52.0

View File

@ -0,0 +1,56 @@
From 748bbe57c3467eae94de533fb4099e89b8f52cc9 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Fri, 10 Apr 2026 17:23:14 +0200
Subject: [PATCH 43/52] block: Add more defaults to DEFAULT_BLOCK_CONF
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [36/45] 47d0e6be200b5f5a481d43a1304c3e0f5ce6b973 (kmwolf/centos-qemu-kvm)
discard_granularity was missing from this, which means that SCSI disks
created with -drive if=scsi would default to 0 (i.e. disabling discards)
instead of -1, which makes scsi-hd automatically pick a granularity and
is the default of the corresponding qdev property for -device scsi-hd.
This was broken in QEMU 9.0 with commit 3089637.
Also set other fields whose default isn't an obvious 0. These are not
actual bug fixes because ON_OFF_AUTO_AUTO in fact happens to be 0, but
it's better not to rely on the order of enums.
Cc: qemu-stable@nongnu.org
Fixes: 308963746169 ('scsi: Don't ignore most usb-storage properties')
Reported-by: Lexi Winter <ivy@FreeBSD.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260410152314.86412-3-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit f27aea1896338f4dd085a0e2cb2ab3797c5fe3e9)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
include/hw/block/block.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/hw/block/block.h b/include/hw/block/block.h
index 7da643faff..9225e75925 100644
--- a/include/hw/block/block.h
+++ b/include/hw/block/block.h
@@ -53,7 +53,12 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf)
#define DEFAULT_BLOCK_CONF (BlockConf) { \
.bootindex = -1, \
+ .backend_defaults = ON_OFF_AUTO_AUTO, \
+ .discard_granularity = -1, \
+ .wce = ON_OFF_AUTO_AUTO, \
.share_rw = false, \
+ .account_invalid = ON_OFF_AUTO_AUTO, \
+ .account_failed = ON_OFF_AUTO_AUTO, \
.rerror = BLOCKDEV_ON_ERROR_AUTO, \
.werror = BLOCKDEV_ON_ERROR_AUTO, \
}
--
2.52.0

View File

@ -0,0 +1,68 @@
From d3c16be7233df4e4103cb048f392d4cb1ffa6e53 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Fri, 10 Apr 2026 17:23:13 +0200
Subject: [PATCH 42/52] block: Create DEFAULT_BLOCK_CONF macro
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [35/45] 8794cac427709a1462918bad08b4f135911b3c1b (kmwolf/centos-qemu-kvm)
The property default values from include/hw/block/block.h were
duplicated in scsi_bus_legacy_handle_cmdline(), allowing them to go out
of sync easily. There doesn't seem a good way to avoid the duplication,
but moving them next to each other in the header file should help to
avoid this problem in the future.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260410152314.86412-2-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit a1310cc6281d22ac948f4aa198dcc55d58fc039d)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/scsi/scsi-bus.c | 7 +------
include/hw/block/block.h | 7 +++++++
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
index 878ccf62c9..88373d957f 100644
--- a/hw/scsi/scsi-bus.c
+++ b/hw/scsi/scsi-bus.c
@@ -485,12 +485,7 @@ void scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
Location loc;
DriveInfo *dinfo;
int unit;
- BlockConf conf = {
- .bootindex = -1,
- .share_rw = false,
- .rerror = BLOCKDEV_ON_ERROR_AUTO,
- .werror = BLOCKDEV_ON_ERROR_AUTO,
- };
+ BlockConf conf = DEFAULT_BLOCK_CONF;
loc_push_none(&loc);
for (unit = 0; unit <= bus->info->max_target; unit++) {
diff --git a/include/hw/block/block.h b/include/hw/block/block.h
index b4d914624e..7da643faff 100644
--- a/include/hw/block/block.h
+++ b/include/hw/block/block.h
@@ -51,6 +51,13 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf)
return exp;
}
+#define DEFAULT_BLOCK_CONF (BlockConf) { \
+ .bootindex = -1, \
+ .share_rw = false, \
+ .rerror = BLOCKDEV_ON_ERROR_AUTO, \
+ .werror = BLOCKDEV_ON_ERROR_AUTO, \
+}
+
#define DEFINE_BLOCK_PROPERTIES_BASE(_state, _conf) \
DEFINE_PROP_ON_OFF_AUTO("backend_defaults", _state, \
_conf.backend_defaults, ON_OFF_AUTO_AUTO), \
--
2.52.0

View File

@ -0,0 +1,65 @@
From a694d76d085f091f21d5062c4c0328db7b5eb188 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 31 Mar 2026 12:26:08 +0200
Subject: [PATCH 52/52] block: Fix crash after setting latency historygram with
single bin
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [45/45] c604a01f5c30539af7097ee14f49dfdb22a83b2d (kmwolf/centos-qemu-kvm)
Passing an empty list of boundaries to block-latency-histogram-set sets
up a state that leads to a NULL pointer dereference when the next
request should be accounted for. This is not a useful configuration, so
just error out if the user tries to set it.
The crash can easily be reproduced with the following script:
qmp() {
cat <<EOF
{'execute':'qmp_capabilities'}
{'execute':'block-latency-histogram-set',
'arguments': {'id':'ide0','boundaries':[]}}
{'execute':'cont'}
EOF
}
qmp | ./qemu-system-x86_64 -S -qmp stdio \
-drive if=none,format=raw,file=null-co:// \
-device ide-hd,drive=none0,id=ide0
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260331102608.60882-1-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit a55402d5c3a8c63c801de86896f86c9abeda0ca8)
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/accounting.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/block/accounting.c b/block/accounting.c
index 5cf51f029b..f00fe99740 100644
--- a/block/accounting.c
+++ b/block/accounting.c
@@ -185,6 +185,15 @@ int block_latency_histogram_set(BlockAcctStats *stats, enum BlockAcctType type,
prev = entry->value;
}
+ /*
+ * block_latency_histogram_account() assumes that it can always access
+ * hist->boundaries[0], so require at least one boundary. A histogram with
+ * a single bin is useless anyway.
+ */
+ if (new_nbins <= 1) {
+ return -EINVAL;
+ }
+
hist->nbins = new_nbins;
g_free(hist->boundaries);
hist->boundaries = g_new(uint64_t, hist->nbins - 1);
--
2.52.0

View File

@ -0,0 +1,71 @@
From 974fa918950f21b9ee6134af75c551fbc9a61905 Mon Sep 17 00:00:00 2001
From: Antoine Damhet <adamhet@scaleway.com>
Date: Thu, 12 Feb 2026 17:27:24 +0100
Subject: [PATCH 12/52] block/curl: fix concurrent completion handling
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [5/45] c2be04a98f68f562b5cf702572acf31a361934ed (kmwolf/centos-qemu-kvm)
curl_multi_check_completion would bail upon the first completed
transfer even if more completion messages were available thus leaving
some in flight IOs stuck.
Rework a bit the loop to make the iterations clearer and drop the breaks.
The original hang can be somewhat reproduced with the following command:
$ qemu-img convert -p -m 16 -O qcow2 -c --image-opts \
'file.driver=https,file.url=https://scaleway.testdebit.info/10G.iso,file.readahead=1M' \
/tmp/test.qcow2
Fixes: 1f2cead32443 ("curl: Ensure all informationals are checked for completion")
Cc: qemu-stable@nongnu.org
Signed-off-by: Antoine Damhet <adamhet@scaleway.com>
Message-ID: <20260212162730.440855-2-adamhet@scaleway.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 6f7b0a23a6ea0cc72ad222ab37936248d99d4256)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/curl.c | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/block/curl.c b/block/curl.c
index 96498aac1d..dabd2a905e 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -324,17 +324,11 @@ curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len, CURLAIOCB *acb)
static void curl_multi_check_completion(BDRVCURLState *s)
{
int msgs_in_queue;
+ CURLMsg *msg;
/* Try to find done transfers, so we can free the easy
* handle again. */
- for (;;) {
- CURLMsg *msg;
- msg = curl_multi_info_read(s->multi, &msgs_in_queue);
-
- /* Quit when there are no more completions */
- if (!msg)
- break;
-
+ while ((msg = curl_multi_info_read(s->multi, &msgs_in_queue))) {
if (msg->msg == CURLMSG_DONE) {
int i;
CURLState *state = NULL;
@@ -397,7 +391,6 @@ static void curl_multi_check_completion(BDRVCURLState *s)
}
curl_clean_state(state);
- break;
}
}
}
--
2.52.0

View File

@ -0,0 +1,71 @@
From e61f10f2711f4fee1c4ef0611b89d85cf3141f26 Mon Sep 17 00:00:00 2001
From: Michael Tokarev <mjt@tls.msk.ru>
Date: Sun, 24 Aug 2025 03:05:32 +0300
Subject: [PATCH 09/52] block/curl: fix curl internal handles handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [2/45] adce90f28e968783f18a66f44e42f8023b46b0e1 (kmwolf/centos-qemu-kvm)
block/curl.c uses CURLMOPT_SOCKETFUNCTION to register a socket callback.
According to the documentation, this callback is called not just with
application-created sockets but also with internal curl sockets, - and
for such sockets, user data pointer is not set by the application, so
the result qemu crashing.
Pass BDRVCURLState directly to the callback function as user pointer,
instead of relying on CURLINFO_PRIVATE.
This problem started happening with update of libcurl from 8.9 to 8.10 --
apparently with this change curl started using private handles more.
(CURLINFO_PRIVATE is used in one more place, in curl_multi_check_completion() -
it might need a similar fix too)
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3081
Cc: qemu-stable@qemu.org
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
(cherry picked from commit 606978500c3d18fb89a49844f253097b17f757de)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/curl.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/block/curl.c b/block/curl.c
index d69bcdff79..74ec62d9d8 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -162,13 +162,9 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
void *userp, void *sp)
{
- BDRVCURLState *s;
- CURLState *state = NULL;
+ BDRVCURLState *s = userp;
CURLSocket *socket;
- curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&state);
- s = state->s;
-
socket = g_hash_table_lookup(s->sockets, GINT_TO_POINTER(fd));
if (!socket) {
socket = g_new0(CURLSocket, 1);
@@ -619,6 +615,7 @@ static void curl_attach_aio_context(BlockDriverState *bs,
assert(!s->multi);
s->multi = curl_multi_init();
s->aio_context = new_context;
+ curl_multi_setopt(s->multi, CURLMOPT_SOCKETDATA, s);
curl_multi_setopt(s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb);
curl_multi_setopt(s->multi, CURLMOPT_TIMERDATA, s);
curl_multi_setopt(s->multi, CURLMOPT_TIMERFUNCTION, curl_timer_cb);
--
2.52.0

View File

@ -0,0 +1,57 @@
From 36b96d00d6e51e02eca8bd2b4a11536376187f54 Mon Sep 17 00:00:00 2001
From: GuoHan Zhao <zhaoguohan@kylinos.cn>
Date: Fri, 20 Mar 2026 14:30:16 +0800
Subject: [PATCH 13/52] block/curl: free s->password in cleanup paths
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [6/45] 8c30ddfca7d21b63120d66714cf653bd39cdb611 (kmwolf/centos-qemu-kvm)
When password-secret is used, curl_open() resolves it with
qcrypto_secret_lookup_as_utf8() and stores the returned buffer in
s->password.
Unlike s->proxypassword, s->password is not freed either in the open
failure path or in curl_close(), so the resolved secret leaks once it
has been allocated.
Free s->password in both cleanup paths.
Fixes: 1bff96064290 ('curl: add support for HTTP authentication parameters')
Signed-off-by: GuoHan Zhao <zhaoguohan@kylinos.cn>
Message-ID: <20260320063016.262954-1-zhaoguohan_salmon@163.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 51fc8443c122fedf4d4891bbc3a1ff25dd8bacdf)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/curl.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/block/curl.c b/block/curl.c
index dabd2a905e..aabb602d9b 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -876,6 +876,7 @@ out_noclean:
g_free(s->cookie);
g_free(s->url);
g_free(s->username);
+ g_free(s->password);
g_free(s->proxyusername);
g_free(s->proxypassword);
if (s->sockets) {
@@ -987,6 +988,7 @@ static void curl_close(BlockDriverState *bs)
g_free(s->cookie);
g_free(s->url);
g_free(s->username);
+ g_free(s->password);
g_free(s->proxyusername);
g_free(s->proxypassword);
}
--
2.52.0

View File

@ -0,0 +1,44 @@
From e3921ea32fdc43a74a639e215a861e464ec3da80 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Mon, 13 Oct 2025 13:41:19 +0100
Subject: [PATCH 11/52] block/curl.c: Fix CURLOPT_VERBOSE parameter type
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [4/45] 7adffe8d6de2464ed632918f3d8b4200ada1f2a0 (kmwolf/centos-qemu-kvm)
In commit ed26056d90 ("block/curl.c: Use explicit long constants in
curl_easy_setopt calls") we missed a further call that takes a long
parameter.
Reported-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
Message-ID: <20251013124127.604401-1-rjones@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit ad97769e9dcf4dbdaae6d859176e5f37fd6a7c66)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/curl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/block/curl.c b/block/curl.c
index bbe891dafb..96498aac1d 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -538,7 +538,7 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
#endif
#ifdef DEBUG_VERBOSE
- if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1)) {
+ if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1L)) {
goto err;
}
#endif
--
2.52.0

View File

@ -0,0 +1,90 @@
From a03e26bc3b51997e4584fe95c4da0feba85c076a Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 9 Oct 2025 15:08:31 +0100
Subject: [PATCH 10/52] block/curl.c: Use explicit long constants in
curl_easy_setopt calls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [3/45] 4e4b4f2c1704cc17a27ebca3c1a70bfc8dcd1ebd (kmwolf/centos-qemu-kvm)
curl_easy_setopt takes a variable argument that depends on what
CURLOPT you are setting. Some require a long constant. Passing a
plain int constant is potentially wrong on some platforms.
With warnings enabled, multiple warnings like this were printed:
../block/curl.c: In function curl_init_state:
../block/curl.c:474:13: warning: call to _curl_easy_setopt_err_long declared with attribute warning: curl_easy_setopt expects a long argument [-Wattribute-warning]
474 | curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1) ||
| ^
Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
Signed-off-by: Chenxi Mao <maochenxi@bosc.ac.cn>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Message-ID: <20251009141026.4042021-2-rjones@redhat.com>
(cherry picked from commit ed26056d90ddff21351f3efd2cb47fea4f0e1d45)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/curl.c | 10 +++++-----
contrib/elf2dmp/download.c | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/block/curl.c b/block/curl.c
index 74ec62d9d8..bbe891dafb 100644
--- a/block/curl.c
+++ b/block/curl.c
@@ -485,11 +485,11 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
(void *)curl_read_cb) ||
curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state) ||
curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state) ||
- curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1) ||
- curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1) ||
- curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1) ||
+ curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1L) ||
+ curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1L) ||
+ curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1L) ||
curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg) ||
- curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1)) {
+ curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1L)) {
goto err;
}
if (s->username) {
@@ -814,7 +814,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
}
s->accept_range = false;
- if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1) ||
+ if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1L) ||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_header_cb) ||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s)) {
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
diff --git a/contrib/elf2dmp/download.c b/contrib/elf2dmp/download.c
index 21306b3fd4..fa8da0f9a2 100644
--- a/contrib/elf2dmp/download.c
+++ b/contrib/elf2dmp/download.c
@@ -27,8 +27,8 @@ bool download_url(const char *name, const char *url)
if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK
|| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL) != CURLE_OK
|| curl_easy_setopt(curl, CURLOPT_WRITEDATA, file) != CURLE_OK
- || curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) != CURLE_OK
- || curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK
+ || curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK
+ || curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK
|| curl_easy_perform(curl) != CURLE_OK) {
unlink(name);
fclose(file);
--
2.52.0

View File

@ -0,0 +1,131 @@
From fa0ed7060fac1b81af1f4b4390ad3f4ddbd03270 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
Date: Fri, 19 Sep 2025 12:22:13 +0100
Subject: [PATCH 17/52] block: fix luks 'amend' when run in coroutine
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [10/45] 3dedc21108a95bebdc893820adfb19c2b88189cc (kmwolf/centos-qemu-kvm)
Launch QEMU with
$ qemu-img create \
--object secret,id=sec0,data=123456 \
-f luks -o key-secret=sec0 demo.luks 1g
$ qemu-system-x86_64 \
--object secret,id=sec0,data=123456 \
-blockdev driver=luks,key-secret=sec0,file.filename=demo.luks,file.driver=file,node-name=luks
Then in QMP shell attempt
x-blockdev-amend job-id=fish node-name=luks options={'state':'active','new-secret':'sec0','driver':'luks'}
It will result in an assertion
#0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#1 0x00007fad18b73f63 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:89
#2 0x00007fad18b19f3e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3 0x00007fad18b016d0 in __GI_abort () at abort.c:77
#4 0x00007fad18b01639 in __assert_fail_base
(fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at assert.c:118
#5 0x00007fad18b120af in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>)
at assert.c:127
#6 0x000055ff74fdbd46 in bdrv_graph_rdlock_main_loop () at ../block/graph-lock.c:260
#7 0x000055ff7548521b in graph_lockable_auto_lock_mainloop (x=<optimized out>)
at /usr/src/debug/qemu-9.2.4-1.fc42.x86_64/include/block/graph-lock.h:266
#8 block_crypto_read_func (block=<optimized out>, offset=4096, buf=0x55ffb6d66ef0 "", buflen=256000, opaque=0x55ffb5edcc30, errp=0x55ffb6f00700)
at ../block/crypto.c:71
#9 0x000055ff75439f8b in qcrypto_block_luks_load_key
(block=block@entry=0x55ffb5edbe90, slot_idx=slot_idx@entry=0, password=password@entry=0x55ffb67dc260 "123456", masterkey=masterkey@entry=0x55ffb5fb0c40 "", readfunc=readfunc@entry=0x55ff754851e0 <block_crypto_read_func>, opaque=opaque@entry=0x55ffb5edcc30, errp=0x55ffb6f00700)
at ../crypto/block-luks.c:927
#10 0x000055ff7543b90f in qcrypto_block_luks_find_key
(block=<optimized out>, password=<optimized out>, masterkey=<optimized out>, readfunc=<optimized out>, opaque=<optimized out>, errp=<optimized out>) at ../crypto/block-luks.c:1045
#11 qcrypto_block_luks_amend_add_keyslot
(block=0x55ffb5edbe90, readfunc=0x55ff754851e0 <block_crypto_read_func>, writefunc=0x55ff75485100 <block_crypto_write_func>, opaque=0x55ffb5edcc3, opts_luks=0x7fad1715aef8, force=<optimized out>, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1673
#12 qcrypto_block_luks_amend_options
(block=0x55ffb5edbe90, readfunc=0x55ff754851e0 <block_crypto_read_func>, writefunc=0x55ff75485100 <block_crypto_write_func>, opaque=0x55ffb5edcc30, options=0x7fad1715aef0, force=<optimized out>, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1865
#13 0x000055ff75485b95 in block_crypto_amend_options_generic_luks
(bs=<optimized out>, amend_options=<optimized out>, force=<optimized out>, errp=<optimized out>) at ../block/crypto.c:949
#14 0x000055ff75485c28 in block_crypto_co_amend_luks (bs=<optimized out>, opts=<optimized out>, force=<optimized out>, errp=<optimized out>)
at ../block/crypto.c:1008
#15 0x000055ff754778e5 in blockdev_amend_run (job=0x55ffb6f00640, errp=0x55ffb6f00700) at ../block/amend.c:52
#16 0x000055ff75468b90 in job_co_entry (opaque=0x55ffb6f00640) at ../job.c:1106
#17 0x000055ff755a0fc2 in coroutine_trampoline (i0=<optimized out>, i1=<optimized out>) at ../util/coroutine-ucontext.c:175
This changes the read/write callbacks to not assert that they
are run in mainloop context if already in a coroutine.
This is also reproduced by qemu-iotests cases 295 and 296.
Fixes: 1f051dcbdf2e4b6f518db731c84e304b2b9d15ce
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Message-ID: <20250919112213.1530079-1-berrange@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit c86488abaf017ed3f5a636c3247cd640a93d3b08)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/crypto.c | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/block/crypto.c b/block/crypto.c
index 17b4749a1e..7c37b23e36 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -67,11 +67,18 @@ static int block_crypto_read_func(QCryptoBlock *block,
BlockCrypto *crypto = bs->opaque;
ssize_t ret;
- GLOBAL_STATE_CODE();
- GRAPH_RDLOCK_GUARD_MAINLOOP();
+ if (qemu_in_coroutine()) {
+ GRAPH_RDLOCK_GUARD();
- ret = bdrv_pread(crypto->header ? crypto->header : bs->file,
- offset, buflen, buf, 0);
+ ret = bdrv_co_pread(crypto->header ? crypto->header : bs->file,
+ offset, buflen, buf, 0);
+ } else {
+ GLOBAL_STATE_CODE();
+ GRAPH_RDLOCK_GUARD_MAINLOOP();
+
+ ret = bdrv_pread(crypto->header ? crypto->header : bs->file,
+ offset, buflen, buf, 0);
+ }
if (ret < 0) {
error_setg_errno(errp, -ret, "Could not read encryption header");
return ret;
@@ -90,11 +97,18 @@ static int block_crypto_write_func(QCryptoBlock *block,
BlockCrypto *crypto = bs->opaque;
ssize_t ret;
- GLOBAL_STATE_CODE();
- GRAPH_RDLOCK_GUARD_MAINLOOP();
+ if (qemu_in_coroutine()) {
+ GRAPH_RDLOCK_GUARD();
- ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file,
- offset, buflen, buf, 0);
+ ret = bdrv_co_pwrite(crypto->header ? crypto->header : bs->file,
+ offset, buflen, buf, 0);
+ } else {
+ GLOBAL_STATE_CODE();
+ GRAPH_RDLOCK_GUARD_MAINLOOP();
+
+ ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file,
+ offset, buflen, buf, 0);
+ }
if (ret < 0) {
error_setg_errno(errp, -ret, "Could not write encryption header");
return ret;
--
2.52.0

View File

@ -0,0 +1,107 @@
From 319a9f69ecdd6bbe003503eb66aa4c30ffef3d40 Mon Sep 17 00:00:00 2001
From: "Denis V. Lunev" <den@openvz.org>
Date: Fri, 24 Apr 2026 12:39:16 +0200
Subject: [PATCH 08/52] block/graph-lock: fix missed wakeup in
bdrv_graph_co_rdunlock()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/45] 03368780c08d1ee94cf412e6739c41c927259f9e (kmwolf/centos-qemu-kvm)
tests/qemu-iotests/tests/iothreads-create reproduces the hang on
master under `stress-ng --cpu $(nproc) --timeout 0`. The iotest's
vm.run_job() times out and qemu stays permanently stuck in
ppoll(timeout=-1) inside bdrv_graph_wrlock_drained -> blk_remove_bs
during qemu_cleanup(). The timing window is narrow on modern
bare-metal hardware and much wider in a VM guest; downstream trees
that still use plain bdrv_graph_wrlock() in blk_remove_bs() hit it
on the first iteration under the same stress.
bdrv_graph_wrlock() zeroes has_writer around its AIO_WAIT_WHILE loop
so that callbacks dispatched by aio_poll() can still take the read
lock on the fast path. The rdunlock side, however, only kicks a
waiting writer when has_writer is observed set; a reader that drops
its lock inside the polling window silently returns and nothing ever
wakes the writer:
main thread iothread0 coroutine
----------- -------------------
bdrv_graph_wrlock: rdlock held, reader_count=1
bdrv_drain_all_begin_nopoll
has_writer = 0
AIO_WAIT_WHILE_UNLOCKED(
NULL, reader_count >= 1):
num_waiters++
smp_mb
aio_poll(main_ctx, true) --> bdrv_graph_co_rdunlock:
(ppoll, blocked) reader_count-- -> 0
smp_mb
read has_writer = 0
skip aio_wait_kick()
return
reader_count is now 0 and num_waiters is still 1, but no BH, fd or
timer on the main AioContext will fire -- the only entity that could
kick just decided it did not have to. Main stays in ppoll() holding
BQL, so RCU, VCPUs and any iothread path that needs BQL stall behind
it. The hang is final; no timeout, no forward progress, no recovery
as there is no other source of wake up inside qemu_cleanup().
bdrv_drain_all_begin() does not close the race on its own: it
quiesces in-flight I/O, but graph readers also include non-I/O
coroutines (block-job cleanup, virtio-scsi polling) that drain does
not evict. The bdrv_graph_wrlock_drained() wrapper narrows the
window but does not eliminate it; every plain bdrv_graph_wrlock()
site is exposed on the same basis.
Drop the has_writer check in bdrv_graph_co_rdunlock() and call
aio_wait_kick() unconditionally. The helper itself loads num_waiters
atomically and only schedules a dummy BH when a waiter exists, so the
change is a no-op on the no-writer path and closes the missed-wakeup
on the writer path.
Signed-off-by: Denis V. Lunev <den@openvz.org>
Cc: Kevin Wolf <kwolf@redhat.com>
Cc: Hanna Reitz <hreitz@redhat.com>
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Fiona Ebner <f.ebner@proxmox.com>
Message-ID: <20260424103917.248668-2-den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit e3082ab3b38538ebdbc5cd62b4c476b673c5e515)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/graph-lock.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/block/graph-lock.c b/block/graph-lock.c
index b7319473a1..f2501d75fb 100644
--- a/block/graph-lock.c
+++ b/block/graph-lock.c
@@ -278,14 +278,12 @@ void coroutine_fn bdrv_graph_co_rdunlock(void)
smp_mb();
/*
- * has_writer == 0: this means reader will read reader_count decreased
- * has_writer == 1: we don't know if writer read reader_count old or
- * new. Therefore, kick again so on next iteration
- * writer will for sure read the updated value.
+ * Always kick: bdrv_graph_wrlock() zeroes has_writer while polling (to
+ * let callbacks take the reader lock via the fast path), so we cannot
+ * rely on has_writer to detect a waiting writer. aio_wait_kick() is a
+ * no-op when no one is waiting, so it is cheap in the common case.
*/
- if (qatomic_read(&has_writer)) {
- aio_wait_kick();
- }
+ aio_wait_kick();
}
void bdrv_graph_rdlock_main_loop(void)
--
2.52.0

View File

@ -0,0 +1,66 @@
From dfe7857ca3612b7c52b052735c8d1a96ecf1102d Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Sat, 13 Jun 2026 23:03:34 +0300
Subject: [PATCH 33/52] block/io: fallback to bounce buffer if BLKZEROOUT is
not supported because of alignment
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [26/45] 7a8351c003c1383c23e94f06617911b691cb7ec5 (kmwolf/centos-qemu-kvm)
Commit 5634622bcb ("file-posix: allow BLKZEROOUT with -t writeback")
enables the BLKZEROOUT ioctl when using 'writeback' cache, regressing
certain 'qemu-img convert' invocations, because of a pre-existing
issue. Namely, the BLKZEROOUT ioctl might fail with errno EINVAL when
the request is shorter than the block size of the block device.
Fallback to the bounce buffer, similar to when the ioctl is not
supported at all, rather than treating such an error as fatal.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3257
Resolves: https://bugzilla.proxmox.com/show_bug.cgi?id=7197
Cc: qemu-stable@nongnu.org
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-ID: <20260105143416.737482-1-f.ebner@proxmox.com>
[Added TODO comment describing a larger fix that could be implemented in
the future.
--Stefan]
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit b4e28c304bc58325f8f712cb25e5d700826caa25)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-51-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/io.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/block/io.c b/block/io.c
index 37df1e0253..84de5ab420 100644
--- a/block/io.c
+++ b/block/io.c
@@ -1920,7 +1920,18 @@ bdrv_co_do_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes,
assert(!bs->supported_zero_flags);
}
- if (ret == -ENOTSUP && !(flags & BDRV_REQ_NO_FALLBACK)) {
+ /*
+ * TODO The ret == -EINVAL && num < alignment case is a workaround for
+ * when request_alignment is 1 on files with cache=writeback. The Linux
+ * ioctl(BLKZEROOUT) requires block alignment and will fail with
+ * EINVAL. The block layer should align the request to
+ * write_zeroes_alignment instead of trying the syscall, failing, and
+ * falling back to a bounce buffer. Doing that is not easy so for now
+ * we use a bounce buffer:
+ * https://lore.kernel.org/qemu-devel/20260109120837.2772961-1-f.ebner@proxmox.com/
+ */
+ if ((ret == -ENOTSUP || (ret == -EINVAL && num < alignment)) &&
+ !(flags & BDRV_REQ_NO_FALLBACK)) {
/* Fall back to bounce buffer if write zeroes is unsupported */
BdrvRequestFlags write_flags = flags & ~BDRV_REQ_ZERO_WRITE;
--
2.52.0

View File

@ -0,0 +1,140 @@
From 9a6c4bad7f575826796a4c690a0fa6bbcda1f5ad Mon Sep 17 00:00:00 2001
From: "Denis V. Lunev" <den@openvz.org>
Date: Sat, 13 Jun 2026 23:03:08 +0300
Subject: [PATCH 32/52] block/linux-aio: bound ioq_submit() recursion depth
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [25/45] 04545c714dacb2571f169b3da3c7cf493dea31bd (kmwolf/centos-qemu-kvm)
qemu_laio_process_completions() wraps its body in defer_call_begin /
defer_call_end. Inside the section, completion callbacks wake coroutines
that queue new aiocbs; laio_do_submit() defers laio_deferred_fn. At the
bottom of qemu_laio_process_completions() the defer_call_end() fires
laio_deferred_fn, which calls ioq_submit(), closing the cycle:
ioq_submit
-> io_submit(2) // some sync completions
-> qemu_laio_process_completions // defer_call_begin
-> aio_co_wake // resumes coroutine
-> laio_do_submit
-> defer_call(laio_deferred_fn, s) // enqueued
-> defer_call_end // nesting drops to 0
-> laio_deferred_fn
-> ioq_submit // +1 stack frame, loop
When io_submit(2) returns asynchronously (O_DIRECT) the cycle
terminates in one extra frame: the fresh aiocb is still in flight, no
completion is drained, no coroutine wakes, no new submission queues.
When submissions complete synchronously (non-O_DIRECT, or per-descriptor
drivers such as vmdk) each level enqueues more work for the next
defer_call_end() to drain, so recursion grows without bound and QEMU
crashes with SIGSEGV on the thread guard page.
The cycle was closed by two performance commits, each correct in
isolation:
076682885d ("block/linux-aio: convert to blk_io_plug_call() API")
-- introduced laio_deferred_fn and wired
laio_do_submit -> defer_call(laio_deferred_fn, s).
84d61e5f36 ("virtio: use defer_call() in virtio_irqfd_notify()")
-- added defer_call_begin/end around qemu_laio_process_completions
so virtio-irqfd notifications batch across a completion pass.
The supported aio=native + cache=none pairing keeps submissions
asynchronous, so the cycle stays bounded; nothing in the code enforces
that contract. Observed in production as a SIGSEGV during a backup job
configured with --cached + aio=native; reproducible on upstream with
qemu-io against vmdk.
Cap ioq_submit() recursion with a counter on LaioQueue, which is only
accessed from the AioContext home thread. On overflow, return without
submitting. The pending work is drained by s->completion_bh, which
qemu_laio_process_completions() has already scheduled on entry -- no
work is lost; one event-loop round-trip of latency is paid only when
the bound is hit, which cannot happen on a supported configuration.
Signed-off-by: Denis V. Lunev <den@openvz.org>
CC: Kevin Wolf <kwolf@redhat.com>
CC: Hanna Reitz <hreitz@redhat.com>
CC: Stefan Hajnoczi <stefanha@redhat.com>
CC: Paolo Bonzini <pbonzini@redhat.com>
Message-ID: <20260520142503.251959-2-den@openvz.org>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit 6864bec553b2e37699739615e604fc3c7bae0e1d)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-25-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/linux-aio.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/block/linux-aio.c b/block/linux-aio.c
index 84397de54c..37de9b564b 100644
--- a/block/linux-aio.c
+++ b/block/linux-aio.c
@@ -36,6 +36,19 @@
/* Maximum number of requests in a batch. (default value) */
#define DEFAULT_MAX_BATCH 32
+/*
+ * Bound on how deep ioq_submit() may recurse on a single LaioQueue via the
+ * ioq_submit -> qemu_laio_process_completions -> defer_call_end ->
+ * laio_deferred_fn -> ioq_submit cycle. The cycle terminates naturally
+ * when io_submit(2) returns asynchronously (O_DIRECT), but can grow
+ * without bound when submissions complete synchronously. On overflow
+ * the caller returns without submitting; the outermost
+ * qemu_laio_process_completions() has already scheduled s->completion_bh
+ * (via qemu_bh_schedule() at the top of that function), which resumes
+ * submission from the next event-loop dispatch.
+ */
+#define IOQ_SUBMIT_MAX_DEPTH 8
+
struct qemu_laiocb {
Coroutine *co;
LinuxAioState *ctx;
@@ -61,6 +74,7 @@ typedef struct {
unsigned int in_queue;
unsigned int in_flight;
bool blocked;
+ unsigned int submit_depth;
QSIMPLEQ_HEAD(, qemu_laiocb) pending;
} LaioQueue;
@@ -331,6 +345,7 @@ static void ioq_init(LaioQueue *io_q)
io_q->in_queue = 0;
io_q->in_flight = 0;
io_q->blocked = false;
+ io_q->submit_depth = 0;
}
static void ioq_submit(LinuxAioState *s)
@@ -340,6 +355,11 @@ static void ioq_submit(LinuxAioState *s)
QEMU_UNINITIALIZED struct iocb *iocbs[MAX_EVENTS];
QSIMPLEQ_HEAD(, qemu_laiocb) completed;
+ if (s->io_q.submit_depth >= IOQ_SUBMIT_MAX_DEPTH) {
+ return;
+ }
+ s->io_q.submit_depth++;
+
do {
if (s->io_q.in_flight >= MAX_EVENTS) {
break;
@@ -385,6 +405,8 @@ static void ioq_submit(LinuxAioState *s)
* pended requests will be submitted from there.
*/
}
+
+ s->io_q.submit_depth--;
}
static uint64_t laio_max_batch(LinuxAioState *s, uint64_t dev_max_batch)
--
2.52.0

View File

@ -0,0 +1,74 @@
From 86b2caec2f830d34a03f4f55028a525bcf11d515 Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Mon, 12 Jan 2026 16:23:51 +0100
Subject: [PATCH 44/52] block/mirror: check range when setting zero bitmap for
sync write
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [37/45] 858e42b5e327560c28e10a151d4e46a9cf273363 (kmwolf/centos-qemu-kvm)
Some Proxmox users reported an occasional assertion failure [0][1] in
busy VMs when using drive mirror with active mode. In particular, the
failure may occur for zero writes shorter than the job granularity:
> #0 0x00007b421154b507 in abort ()
> #1 0x00007b421154b420 in ?? ()
> #2 0x0000641c582e061f in bitmap_set (map=0x7b4204014e00, start=14, nr=-1)
> #3 0x0000641c58062824 in do_sync_target_write (job=0x641c7e73d1e0,
> method=MIRROR_METHOD_ZERO, offset=852480, bytes=4096, qiov=0x0, flags=0)
> #4 0x0000641c58062250 in bdrv_mirror_top_do_write (bs=0x641c7e62e1f0,
method=MIRROR_METHOD_ZERO, copy_to_target=true, offset=852480,
bytes=4096, qiov=0x0, flags=0)
> #5 0x0000641c58061f31 in bdrv_mirror_top_pwrite_zeroes (bs=0x641c7e62e1f0,
offset=852480, bytes=4096, flags=0)
The range for the dirty bitmap described by dirty_bitmap_offset and
dirty_bitmap_end is narrower than the original range and in fact,
dirty_bitmap_end might be smaller than dirty_bitmap_offset. There
already is a check for 'dirty_bitmap_offset < dirty_bitmap_end' before
resetting the dirty bitmap. Add such a check for setting the zero
bitmap too, which uses the same narrower range.
[0]: https://forum.proxmox.com/threads/177981/
[1]: https://bugzilla.proxmox.com/show_bug.cgi?id=7222
Cc: qemu-stable@nongnu.org
Fixes: 7e277545b9 ("mirror: Skip writing zeroes when target is already zero")
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-ID: <20260112152544.261923-1-f.ebner@proxmox.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
(cherry picked from commit 4a7b1bd18d2e1a6b3796e177ae5df9b198264a0b)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/mirror.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/block/mirror.c b/block/mirror.c
index c87f1e205b..d22f168ff0 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1515,9 +1515,12 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
assert(!qiov);
ret = blk_co_pwrite_zeroes(job->target, offset, bytes, flags);
if (job->zero_bitmap && ret >= 0) {
- bitmap_set(job->zero_bitmap, dirty_bitmap_offset / job->granularity,
- (dirty_bitmap_end - dirty_bitmap_offset) /
- job->granularity);
+ if (dirty_bitmap_offset < dirty_bitmap_end) {
+ bitmap_set(job->zero_bitmap,
+ dirty_bitmap_offset / job->granularity,
+ (dirty_bitmap_end - dirty_bitmap_offset) /
+ job->granularity);
+ }
}
break;
--
2.52.0

View File

@ -0,0 +1,75 @@
From 65523fcaa292ec714b66fa2b91ce8f50cd27ab64 Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Wed, 11 Mar 2026 15:54:25 +0100
Subject: [PATCH 46/52] block/mirror: fix assertion failure upon duplicate
complete for job using 'replaces'
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [39/45] 31abb5c45002235fdabe5cebfb7bee05e1b3c653 (kmwolf/centos-qemu-kvm)
If s->replace_blocker was already set by an earlier invocation of
mirror_complete(), then there will be an assertion failure when
error_setg() is called for it a second time. The bdrv_op_block_all()
and bdrv_ref() operations should only be done a single time too.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-Id: <20260311145717.668492-2-f.ebner@proxmox.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
(cherry picked from commit 9ac85f4cc7995217db8f736733b990d6addcb036)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/mirror.c | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/block/mirror.c b/block/mirror.c
index d22f168ff0..089856f4a8 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1276,23 +1276,25 @@ static void mirror_complete(Job *job, Error **errp)
return;
}
- /* block all operations on to_replace bs */
- if (s->replaces) {
- s->to_replace = bdrv_find_node(s->replaces);
- if (!s->to_replace) {
- error_setg(errp, "Node name '%s' not found", s->replaces);
- return;
+ if (!s->should_complete) {
+ /* block all operations on to_replace bs */
+ if (s->replaces) {
+ s->to_replace = bdrv_find_node(s->replaces);
+ if (!s->to_replace) {
+ error_setg(errp, "Node name '%s' not found", s->replaces);
+ return;
+ }
+
+ /* TODO Translate this into child freeze system. */
+ error_setg(&s->replace_blocker,
+ "block device is in use by block-job-complete");
+ bdrv_op_block_all(s->to_replace, s->replace_blocker);
+ bdrv_ref(s->to_replace);
}
- /* TODO Translate this into child freeze system. */
- error_setg(&s->replace_blocker,
- "block device is in use by block-job-complete");
- bdrv_op_block_all(s->to_replace, s->replace_blocker);
- bdrv_ref(s->to_replace);
+ s->should_complete = true;
}
- s->should_complete = true;
-
/* If the job is paused, it will be re-entered when it is resumed */
WITH_JOB_LOCK_GUARD() {
if (!job->paused) {
--
2.52.0

View File

@ -0,0 +1,72 @@
From 3984b109d0b4e1a86c46d3b79641b08043e5d64a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
Date: Fri, 19 Sep 2025 11:38:10 +0100
Subject: [PATCH 16/52] block: remove 'detached-header' option from opts after
use
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [9/45] 383bd840869b184dfe4588f930e2d924f6faec39 (kmwolf/centos-qemu-kvm)
The code for creating LUKS devices references a 'detached-header'
option in the QemuOpts data, but does not consume (remove) the
option.
Thus when the code later tries to convert the remaining unused
QemuOpts into a QCryptoBlockCreateOptions struct, an error is
reported by the QAPI code that 'detached-header' is not a valid
field.
This fixes a regression caused by
commit e818c01ae6e7c54c7019baaf307be59d99ce80b9
Author: Daniel P. Berrangé <berrange@redhat.com>
Date: Mon Feb 19 15:12:59 2024 +0000
qapi: drop unused QCryptoBlockCreateOptionsLUKS.detached-header
which identified that the QAPI field was unused, but failed to
realize the QemuOpts -> QCryptoBlockCreateOptions conversion
was seeing the left-over 'detached-header' option which had not
been removed from QemuOpts.
This problem was identified by the 'luks-detached-header' I/O
test, but unfortunately I/O tests are not run regularly for the
LUKS format.
Fixes: e818c01ae6e7c54c7019baaf307be59d99ce80b9
Reported-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Message-ID: <20250919103810.1513109-1-berrange@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 6eda39a87f4fda78befa4085e3644e4440afc1dd)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/crypto.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/block/crypto.c b/block/crypto.c
index d4226cc68a..17b4749a1e 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -792,7 +792,7 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename,
char *buf = NULL;
int64_t size;
bool detached_hdr =
- qemu_opt_get_bool(opts, "detached-header", false);
+ qemu_opt_get_bool_del(opts, "detached-header", false);
unsigned int cflags = 0;
int ret;
Error *local_err = NULL;
--
2.52.0

View File

@ -0,0 +1,146 @@
From 1c6a0b9352bf6eda335bd661869a46bc56d0c2fc Mon Sep 17 00:00:00 2001
From: Dmitry Guryanov <dmitry.guryanov@gmail.com>
Date: Mon, 8 Dec 2025 11:55:28 +0300
Subject: [PATCH 20/52] block/throttle-groups: fix deadlock with iolimits and
muliple iothreads
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [13/45] b36053836c5ced989f7b2495dbe170a36ad190c5 (kmwolf/centos-qemu-kvm)
Details: https://gitlab.com/qemu-project/qemu/-/issues/3144
The function schedule_next_request is called with tg->lock held and
it may call throttle_group_co_restart_queue, which takes
tgm->throttled_reqs_lock, qemu_co_mutex_lock may leave current
coroutine if other iothread has taken the lock. If the next
coroutine will call throttle_group_co_io_limits_intercept - it
will try to take the mutex tg->lock which will never be released.
Here is the backtrace of the iothread:
Thread 30 (Thread 0x7f8aad1fd6c0 (LWP 24240) "IO iothread2"):
#0 futex_wait (futex_word=0x5611adb7d828, expected=2, private=0) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait (futex=futex@entry=0x5611adb7d828, private=0) at lowlevellock.c:49
#2 0x00007f8ab5a97501 in lll_mutex_lock_optimized (mutex=0x5611adb7d828) at pthread_mutex_lock.c:48
#3 ___pthread_mutex_lock (mutex=0x5611adb7d828) at pthread_mutex_lock.c:93
#4 0x00005611823f5482 in qemu_mutex_lock_impl (mutex=0x5611adb7d828, file=0x56118289daca "../block/throttle-groups.c", line=372) at ../util/qemu-thread-posix.c:94
#5 0x00005611822b0b39 in throttle_group_co_io_limits_intercept (tgm=0x5611af1bb4d8, bytes=4096, direction=THROTTLE_READ) at ../block/throttle-groups.c:372
#6 0x00005611822473b1 in blk_co_do_preadv_part (blk=0x5611af1bb490, offset=15972311040, bytes=4096, qiov=0x7f8aa4000f98, qiov_offset=0, flags=BDRV_REQ_REGISTERED_BUF) at ../block/block-backend.c:1354
#7 0x0000561182247fa0 in blk_aio_read_entry (opaque=0x7f8aa4005910) at ../block/block-backend.c:1619
#8 0x000056118241952e in coroutine_trampoline (i0=-1543497424, i1=32650) at ../util/coroutine-ucontext.c:175
#9 0x00007f8ab5a56f70 in ?? () at ../sysdeps/unix/sysv/linux/x86_64/__start_context.S:66 from target:/lib64/libc.so.6
#10 0x00007f8aad1ef190 in ?? ()
#11 0x0000000000000000 in ?? ()
The lock is taken in line 386:
(gdb) p tg.lock
$1 = {lock = {__data = {__lock = 2, __count = 0, __owner = 24240, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}},
__size = "\002\000\000\000\000\000\000\000\260^\000\000\001", '\000' <repeats 26 times>, __align = 2}, file = 0x56118289daca "../block/throttle-groups.c",
line = 386, initialized = true}
The solution is to use tg->lock to protect both ThreadGroup fields and
ThrottleGroupMember.throttled_reqs. It doesn't seem to be possible
to use separate locks because we need to first manipulate ThrottleGroup
fields, then schedule next coroutine using throttled_reqs and after than
update token field from ThrottleGroup depending on the throttled_reqs
state.
Signed-off-by: Dmitry Guryanov <dmitry.guryanov@gmail.com>
Message-ID: <20251208085528.890098-1-dmitry.guryanov@gmail.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit d4816177654d59e26ce212c436513f01842eb410)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/throttle-groups.c | 21 ++++++---------------
include/block/throttle-groups.h | 3 +--
2 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index 66fdce9a90..5329ff1fdb 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -295,19 +295,15 @@ static bool throttle_group_schedule_timer(ThrottleGroupMember *tgm,
/* Start the next pending I/O request for a ThrottleGroupMember. Return whether
* any request was actually pending.
*
+ * This assumes that tg->lock is held.
+ *
* @tgm: the current ThrottleGroupMember
* @direction: the ThrottleDirection
*/
static bool coroutine_fn throttle_group_co_restart_queue(ThrottleGroupMember *tgm,
ThrottleDirection direction)
{
- bool ret;
-
- qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
- ret = qemu_co_queue_next(&tgm->throttled_reqs[direction]);
- qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
-
- return ret;
+ return qemu_co_queue_next(&tgm->throttled_reqs[direction]);
}
/* Look for the next pending I/O request and schedule it.
@@ -378,12 +374,8 @@ void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm
/* Wait if there's a timer set or queued requests of this type */
if (must_wait || tgm->pending_reqs[direction]) {
tgm->pending_reqs[direction]++;
- qemu_mutex_unlock(&tg->lock);
- qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
qemu_co_queue_wait(&tgm->throttled_reqs[direction],
- &tgm->throttled_reqs_lock);
- qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
- qemu_mutex_lock(&tg->lock);
+ &tg->lock);
tgm->pending_reqs[direction]--;
}
@@ -410,15 +402,15 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
ThrottleDirection direction = data->direction;
bool empty_queue;
+ qemu_mutex_lock(&tg->lock);
empty_queue = !throttle_group_co_restart_queue(tgm, direction);
/* If the request queue was empty then we have to take care of
* scheduling the next one */
if (empty_queue) {
- qemu_mutex_lock(&tg->lock);
schedule_next_request(tgm, direction);
- qemu_mutex_unlock(&tg->lock);
}
+ qemu_mutex_unlock(&tg->lock);
g_free(data);
@@ -569,7 +561,6 @@ void throttle_group_register_tgm(ThrottleGroupMember *tgm,
read_timer_cb,
write_timer_cb,
tgm);
- qemu_co_mutex_init(&tgm->throttled_reqs_lock);
}
/* Unregister a ThrottleGroupMember from its group, removing it from the list,
diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
index 2355e8d9de..7dfc81f7b5 100644
--- a/include/block/throttle-groups.h
+++ b/include/block/throttle-groups.h
@@ -35,8 +35,7 @@
typedef struct ThrottleGroupMember {
AioContext *aio_context;
- /* throttled_reqs_lock protects the CoQueues for throttled requests. */
- CoMutex throttled_reqs_lock;
+ /* Protected by ThrottleGroup.lock */
CoQueue throttled_reqs[THROTTLE_MAX];
/* Nonzero if the I/O limits are currently being ignored; generally
--
2.52.0

View File

@ -0,0 +1,103 @@
From ca326a462336a694d317d03793b5f741125633b7 Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Tue, 7 Oct 2025 10:16:59 -0400
Subject: [PATCH 35/52] block: use pwrite_zeroes_alignment when writing first
sector
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [28/45] aff37a9c095009b900cbf2d02ca378e21ec365aa (kmwolf/centos-qemu-kvm)
Since commit 5634622bcb33 ("file-posix: allow BLKZEROOUT with -t
writeback"), qemu-img create errors out on a Linux loop block device
with a 4 KB sector size:
# dd if=/dev/zero of=blockfile bs=1M count=1024
# losetup --sector-size 4096 /dev/loop0 blockfile
# qemu-img create -f raw /dev/loop0 1G
Formatting '/dev/loop0', fmt=raw size=1073741824
qemu-img: /dev/loop0: Failed to clear the new image's first sector: Invalid argument
Use the pwrite_zeroes_alignment block limit to avoid misaligned
fallocate(2) or ioctl(BLKZEROOUT) in the block/file-posix.c block
driver.
Cc: qemu-stable@nongnu.org
Fixes: 5634622bcb33 ("file-posix: allow BLKZEROOUT with -t writeback")
Reported-by: Jean-Louis Dupond <jean-louis@dupond.be>
Buglink: https://gitlab.com/qemu-project/qemu/-/issues/3127
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-ID: <20251007141700.71891-3-stefanha@redhat.com>
Tested-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit d704a13d2c025779bc91d04e127427347ddcf3b3)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block.c | 3 ++-
block/block-backend.c | 11 +++++++++++
include/system/block-backend-io.h | 1 +
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/block.c b/block.c
index 8848e9a7ed..be77e03904 100644
--- a/block.c
+++ b/block.c
@@ -606,12 +606,13 @@ create_file_fallback_zero_first_sector(BlockBackend *blk,
int64_t current_size,
Error **errp)
{
+ uint32_t alignment = blk_get_pwrite_zeroes_alignment(blk);
int64_t bytes_to_clear;
int ret;
GLOBAL_STATE_CODE();
- bytes_to_clear = MIN(current_size, BDRV_SECTOR_SIZE);
+ bytes_to_clear = MIN(current_size, MAX(BDRV_SECTOR_SIZE, alignment));
if (bytes_to_clear) {
ret = blk_co_pwrite_zeroes(blk, 0, bytes_to_clear, BDRV_REQ_MAY_UNMAP);
if (ret < 0) {
diff --git a/block/block-backend.c b/block/block-backend.c
index d97b26b743..850f2ecec2 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -2326,6 +2326,17 @@ uint32_t blk_get_request_alignment(BlockBackend *blk)
return bs ? bs->bl.request_alignment : BDRV_SECTOR_SIZE;
}
+/* Returns the optimal write zeroes alignment, in bytes; guaranteed nonzero */
+uint32_t blk_get_pwrite_zeroes_alignment(BlockBackend *blk)
+{
+ BlockDriverState *bs = blk_bs(blk);
+ IO_CODE();
+ if (!bs) {
+ return BDRV_SECTOR_SIZE;
+ }
+ return bs->bl.pwrite_zeroes_alignment ?: bs->bl.request_alignment;
+}
+
/* Returns the maximum hardware transfer length, in bytes; guaranteed nonzero */
uint64_t blk_get_max_hw_transfer(BlockBackend *blk)
{
diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h
index 91c48299b7..fd84723d9d 100644
--- a/include/system/block-backend-io.h
+++ b/include/system/block-backend-io.h
@@ -118,6 +118,7 @@ BlockAIOCB *blk_abort_aio_request(BlockBackend *blk,
void *opaque, int ret);
uint32_t blk_get_request_alignment(BlockBackend *blk);
+uint32_t blk_get_pwrite_zeroes_alignment(BlockBackend *blk);
uint32_t blk_get_max_transfer(BlockBackend *blk);
uint64_t blk_get_max_hw_transfer(BlockBackend *blk);
--
2.52.0

View File

@ -0,0 +1,51 @@
From 9aac9cce4de8006a02800a3a20caaf11e87fc767 Mon Sep 17 00:00:00 2001
From: "Halil Oktay (oblivionsage)" <cookieandcream560@gmail.com>
Date: Tue, 10 Feb 2026 13:33:25 +0100
Subject: [PATCH 19/52] block/vmdk: fix OOB read in vmdk_read_extent()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [12/45] 6af02246586e3e7a9a71a23df28a7ffb55110ead (kmwolf/centos-qemu-kvm)
Bounds check for marker.size doesn't account for the 12-byte marker
header, allowing zlib to read past the allocated buffer.
Move the check inside the has_marker block and subtract the marker size.
Fixes: CVE-2026-2243
Reported-by: Halil Oktay (oblivionsage) <cookieandcream560@gmail.com>
Signed-off-by: Halil Oktay (oblivionsage) <cookieandcream560@gmail.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit cfda94eddb6c9c49b66461c950b22845a46a75c9)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/vmdk.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/block/vmdk.c b/block/vmdk.c
index 7b98debc2b..7176435fc5 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -1951,10 +1951,10 @@ vmdk_read_extent(VmdkExtent *extent, int64_t cluster_offset,
marker = (VmdkGrainMarker *)cluster_buf;
compressed_data = marker->data;
data_len = le32_to_cpu(marker->size);
- }
- if (!data_len || data_len > buf_bytes) {
- ret = -EINVAL;
- goto out;
+ if (!data_len || data_len > buf_bytes - sizeof(VmdkGrainMarker)) {
+ ret = -EINVAL;
+ goto out;
+ }
}
ret = uncompress(uncomp_buf, &buf_len, compressed_data, data_len);
if (ret != Z_OK) {
--
2.52.0

View File

@ -0,0 +1,85 @@
From 43fe4a491f9843019d780bfc41db44e86602f184 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Mon, 27 Apr 2026 19:05:17 +0200
Subject: [PATCH 47/52] commit: Drain nodes across all of bdrv_commit()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [40/45] 29cfac9b1a72bf5d4b4497eb4096bc05d41e146e (kmwolf/centos-qemu-kvm)
The whole implementation of bdrv_commit() is only correct if no new
writes come in while it's running: It has only a single loop checking
the allocation status for each block and finally calls bdrv_make_empty()
without checking if that throws away any new changes.
We already have to drain while taking the graph write lock. Just extend
the drained section to all of bdrv_commit() to make sure that we don't
get any inconsistencies.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260427170520.101242-2-kwolf@redhat.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Tested-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit f0d9ccd46cf8fc576ab7d514f10f766546cdbc14)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/commit.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/block/commit.c b/block/commit.c
index 0d9e1a16d7..c5e3ef03a2 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -518,6 +518,7 @@ int bdrv_commit(BlockDriverState *bs)
if (!drv)
return -ENOMEDIUM;
+ bdrv_drain_all_begin();
bdrv_graph_rdlock_main_loop();
backing_file_bs = bdrv_cow_bs(bs);
@@ -549,6 +550,10 @@ int bdrv_commit(BlockDriverState *bs)
BLK_PERM_ALL);
backing = blk_new(ctx, BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
+ /* We drained all nodes, but still make requests through BlockBackends */
+ blk_set_disable_request_queuing(src, true);
+ blk_set_disable_request_queuing(backing, true);
+
ret = blk_insert_bs(src, bs, &local_err);
if (ret < 0) {
error_report_err(local_err);
@@ -565,7 +570,7 @@ int bdrv_commit(BlockDriverState *bs)
bdrv_graph_rdunlock_main_loop();
- bdrv_graph_wrlock_drained();
+ bdrv_graph_wrlock();
bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort);
bdrv_set_backing_hd(bs, commit_top_bs, &error_abort);
bdrv_graph_wrunlock();
@@ -647,7 +652,7 @@ ro_cleanup:
blk_unref(backing);
bdrv_graph_rdunlock_main_loop();
- bdrv_graph_wrlock_drained();
+ bdrv_graph_wrlock();
if (bdrv_cow_bs(bs) != backing_file_bs) {
bdrv_set_backing_hd(bs, backing_file_bs, &error_abort);
}
@@ -663,6 +668,7 @@ ro_cleanup:
out:
bdrv_graph_rdunlock_main_loop();
+ bdrv_drain_all_end();
return ret;
}
--
2.52.0

View File

@ -0,0 +1,65 @@
From 746244e9df305293f9518dcb1dd636a7f8def4ac Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Tue, 7 Oct 2025 10:16:58 -0400
Subject: [PATCH 34/52] file-posix: populate pwrite_zeroes_alignment
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [27/45] 4e268f2334911f502cc5290a93732acdd9a8df3b (kmwolf/centos-qemu-kvm)
Linux block devices require write zeroes alignment whereas files do not.
It may come as a surprise that block devices opened in buffered I/O mode
require the alignment for write zeroes requests although normal
read/write requests do not.
Therefore it is necessary to populate the pwrite_zeroes_alignment field.
Cc: qemu-stable@nongnu.org
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-ID: <20251007141700.71891-2-stefanha@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Tested-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 98e788b91ad037193b1fb375561ef7e0fef3c2fd)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/file-posix.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/block/file-posix.c b/block/file-posix.c
index ffca37130b..0129413273 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -1607,6 +1607,22 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
bs->bl.pdiscard_alignment = dalign;
}
+
+#ifdef __linux__
+ /*
+ * Linux requires logical block size alignment for write zeroes even
+ * when normal reads/writes do not require alignment.
+ */
+ if (!s->needs_alignment) {
+ ret = probe_logical_blocksize(s->fd,
+ &bs->bl.pwrite_zeroes_alignment);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "Failed to probe logical block size");
+ return;
+ }
+ }
+#endif /* __linux__ */
}
raw_refresh_zoned_limits(bs, &st, errp);
--
2.52.0

View File

@ -0,0 +1,93 @@
From 71535e86e4aa44d7a2eefe15c0aaba1bbc0c1a10 Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Fri, 17 Oct 2025 11:43:30 +0200
Subject: [PATCH 39/52] hw/scsi: avoid deadlock upon TMF request cancelling
with VirtIO
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [32/45] 16f2462f42bd9a0205f40426a94a87448aae2292 (kmwolf/centos-qemu-kvm)
When scsi_req_dequeue() is reached via
scsi_req_cancel_async()
virtio_scsi_tmf_cancel_req()
virtio_scsi_do_tmf_aio_context(),
there is a deadlock when trying to acquire the SCSI device's requests
lock, because it was already acquired in
virtio_scsi_do_tmf_aio_context().
In particular, the issue happens with a FreeBSD guest (13, 14, 15,
maybe more), when it cancels SCSI requests, because of timeout.
This is a regression caused by commit da6eebb33b ("virtio-scsi:
perform TMFs in appropriate AioContexts") and the introduction of the
requests_lock earlier.
To fix the issue, only cancel the requests after releasing the
requests_lock. For this, the SCSI device's requests are iterated while
holding the requests_lock and the requests to be cancelled are
collected in a list. Then, the collected requests are cancelled
one by one while not holding the requests_lock. This is safe, because
only requests from the current AioContext are collected and acted
upon.
Originally reported by Proxmox VE users:
https://bugzilla.proxmox.com/show_bug.cgi?id=6810
https://forum.proxmox.com/threads/173914/
Fixes: da6eebb33b ("virtio-scsi: perform TMFs in appropriate AioContexts")
Suggested-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-id: 20251017094518.328905-1-f.ebner@proxmox.com
[Changed g_list_append() to g_list_prepend() to avoid traversing the
list each time.
--Stefan]
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit 6910f04aa646f63a0257f77201ad8ea15992b816)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/scsi/virtio-scsi.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
index 0ce4718407..a632feedcb 100644
--- a/hw/scsi/virtio-scsi.c
+++ b/hw/scsi/virtio-scsi.c
@@ -343,6 +343,7 @@ static void virtio_scsi_do_tmf_aio_context(void *opaque)
SCSIDevice *d = virtio_scsi_device_get(s, tmf->req.tmf.lun);
SCSIRequest *r;
bool match_tag;
+ g_autoptr(GList) reqs = NULL;
if (!d) {
tmf->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET;
@@ -378,10 +379,21 @@ static void virtio_scsi_do_tmf_aio_context(void *opaque)
if (match_tag && cmd_req->req.cmd.tag != tmf->req.tmf.tag) {
continue;
}
- virtio_scsi_tmf_cancel_req(tmf, r);
+ /*
+ * Cannot cancel directly, because scsi_req_dequeue() would deadlock
+ * when attempting to acquire the request_lock a second time. Taking
+ * a reference here is paired with an unref after cancelling below.
+ */
+ scsi_req_ref(r);
+ reqs = g_list_prepend(reqs, r);
}
}
+ for (GList *elem = g_list_first(reqs); elem; elem = g_list_next(elem)) {
+ virtio_scsi_tmf_cancel_req(tmf, elem->data);
+ scsi_req_unref(elem->data);
+ }
+
/* Incremented by virtio_scsi_do_tmf() */
virtio_scsi_tmf_dec_remaining(tmf);
--
2.52.0

View File

@ -0,0 +1,163 @@
From 377ac72f84cb83972c65a190cc5bc4fdf9bdab9b Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:30 +0200
Subject: [PATCH 05/52] ide: Clean up ide_trim_co_entry() to be idiomatic
coroutine code
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [5/7] 8d798543f3052121940fe208e13c710734b7c88c (kmwolf/centos-qemu-kvm)
The previous commit did a minimal conversion of the callback based state
machine for TRIM to a coroutine in order to fix a bug. Refactor it to
actually look like normal coroutine based code, which improves its
readability.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-6-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit c1c71a7e167fdabaa9827d00c0be3aeafebdd921)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/ide/core.c | 87 +++++++++++++++++++++++----------------------------
1 file changed, 39 insertions(+), 48 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 82c3ada14a..8b9f06547b 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -420,18 +420,15 @@ typedef struct TrimAIOCB {
QEMUBH *bh;
int ret;
QEMUIOVector *qiov;
- int i, j;
+ bool canceled;
} TrimAIOCB;
static void trim_aio_cancel(BlockAIOCB *acb)
{
TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common);
- /* Exit the loop so ide_issue_trim_cb will not continue */
- iocb->j = iocb->qiov->niov - 1;
- iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1;
-
- iocb->ret = -ECANCELED;
+ /* Exit the loop so ide_trim_co_entry will not continue */
+ iocb->canceled = true;
}
static const AIOCBInfo trim_aiocb_info = {
@@ -458,60 +455,55 @@ static void coroutine_fn ide_trim_co_entry(void *opaque)
{
TrimAIOCB *iocb = opaque;
IDEState *s = iocb->s;
- int ret = 0;
+ int i, j;
+ int ret;
/* Paired with blk_end_request in ide_trim_bh_cb() */
blk_co_start_request(s->blk);
-loop:
- if (iocb->i >= 0) {
- if (ret >= 0) {
- block_acct_done(blk_get_stats(s->blk), &s->acct);
- } else {
- block_acct_failed(blk_get_stats(s->blk), &s->acct);
- }
- }
+ for (j = 0; j < iocb->qiov->niov; j++) {
+ for (i = 0; i < iocb->qiov->iov[j].iov_len / 8; i++) {
+ uint64_t *buffer = iocb->qiov->iov[j].iov_base;
- if (ret >= 0) {
- while (iocb->j < iocb->qiov->niov) {
- int j = iocb->j;
- while (++iocb->i < iocb->qiov->iov[j].iov_len / 8) {
- int i = iocb->i;
- uint64_t *buffer = iocb->qiov->iov[j].iov_base;
+ /* 6-byte LBA + 2-byte range per entry */
+ uint64_t entry = le64_to_cpu(buffer[i]);
+ uint64_t sector = entry & 0x0000ffffffffffffULL;
+ uint16_t count = entry >> 48;
- /* 6-byte LBA + 2-byte range per entry */
- uint64_t entry = le64_to_cpu(buffer[i]);
- uint64_t sector = entry & 0x0000ffffffffffffULL;
- uint16_t count = entry >> 48;
+ if (count == 0) {
+ continue;
+ }
- if (count == 0) {
- continue;
- }
+ if (iocb->canceled) {
+ iocb->ret = -ECANCELED;
+ goto done;
+ }
- if (!ide_sect_range_ok(s, sector, count)) {
- block_acct_invalid(blk_get_stats(s->blk), BLOCK_ACCT_UNMAP);
- iocb->ret = -EINVAL;
- goto done;
- }
+ if (!ide_sect_range_ok(s, sector, count)) {
+ block_acct_invalid(blk_get_stats(s->blk), BLOCK_ACCT_UNMAP);
+ iocb->ret = -EINVAL;
+ goto done;
+ }
- block_acct_start(blk_get_stats(s->blk), &s->acct,
- count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP);
+ block_acct_start(blk_get_stats(s->blk), &s->acct,
+ count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP);
- /* Got an entry! Submit and exit. */
- ret = blk_co_pdiscard(s->blk,
- sector << BDRV_SECTOR_BITS,
- count << BDRV_SECTOR_BITS,
- BDRV_REQ_NO_QUEUE);
- goto loop;
+ /* Got an entry! Submit and exit. */
+ ret = blk_co_pdiscard(s->blk,
+ sector << BDRV_SECTOR_BITS,
+ count << BDRV_SECTOR_BITS,
+ BDRV_REQ_NO_QUEUE);
+ if (ret >= 0) {
+ block_acct_done(blk_get_stats(s->blk), &s->acct);
+ } else {
+ iocb->ret = ret;
+ block_acct_failed(blk_get_stats(s->blk), &s->acct);
+ goto done;
}
-
- iocb->j++;
- iocb->i = -1;
}
- } else {
- iocb->ret = ret;
}
+ iocb->ret = 0;
done:
if (iocb->bh) {
replay_bh_schedule_event(iocb->bh);
@@ -533,8 +525,7 @@ BlockAIOCB *ide_issue_trim(
&DEVICE(dev)->mem_reentrancy_guard);
iocb->ret = 0;
iocb->qiov = qiov;
- iocb->i = -1;
- iocb->j = 0;
+ iocb->canceled = false;
co = qemu_coroutine_create(ide_trim_co_entry, iocb);
aio_co_enter(qemu_get_current_aio_context(), co);
--
2.52.0

View File

@ -0,0 +1,57 @@
From ba430cce181388723ec2d3174195e1d468ed18b2 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Thu, 26 Mar 2026 17:51:24 +0100
Subject: [PATCH 41/52] ide: Fix potential assertion failure on VM stop for PIO
read error
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [34/45] 4a6db09841ffa4323e11a9e47e170c919d292b3e (kmwolf/centos-qemu-kvm)
ide_sector_read() as well as its callers neglect to call ide_set_retry()
before starting I/O. If the I/O fails, this means that the retry
information is stale. In particular, ide_handle_rw_error() has an
assertion that s->bus->retry_unit == s->unit, which can fail if either
there was no previous request or it came from another device on the bus.
If the assertion weren't there, a wrong request would be retried after
resuming the VM.
Fix this by adding a ide_set_retry() call to ide_sector_read().
This affects only reads because ide_transfer_start() does call
ide_set_retry(). For writes, the data transfer comes first and the I/O
is only started when the data has been read into s->io_buffer, so by
that time, ide_set_retry() has been called. For reads, however, the I/O
comes first and only then the data is transferred to the guest, so the
call in ide_transfer_start() is too late.
Buglink: https://redhat.atlassian.net/browse/RHEL-153537
Reported-by: Tingting Mao <timao@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260326165124.138593-1-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 59c1d31136688415e5d682a87942292dbb3caaeb)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/ide/core.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 8b9f06547b..bf616b7703 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -789,6 +789,7 @@ static void ide_sector_read(IDEState *s)
s->error = 0; /* not needed by IDE spec, but needed by Windows */
sector_num = ide_get_sector(s);
n = s->nsector;
+ ide_set_retry(s);
if (n == 0) {
ide_transfer_stop(s);
--
2.52.0

View File

@ -0,0 +1,141 @@
From b2cc73c49eeee7f25626c4fdbb9aeaf561ffcdfd Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:29 +0200
Subject: [PATCH 04/52] ide: Minimal fix for deadlock between TRIM and drain
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [4/7] 4324446f87c1e7d56ca13b3740d10bf5ed64e8c6 (kmwolf/centos-qemu-kvm)
The implementation of TRIM in IDE can chain multiple discard requests
and uses blk_inc/dec_in_flight() to make sure that the whole TRIM
operation has completed when the device needs to be quiescent (e.g. for
the drain when performing an IDE reset, it would be bad if an IDE
request like TRIM were still in flight).
The problem is that each drain request calls blk_wait_while_drained()
and when draining, it waits until the drained section ends. At the same
time, drain_begin can only return if the whole TRIM operation has
completed. This is a classic deadlock.
Use blk_co_start/end_request() and BDRV_REQ_NO_QUEUE to avoid the
problem. This requires moving the TRIM state machine to a coroutine.
This commit does the minimal conversion so that we do have a coroutine
that works for the fix, but it still looks much like a callback-based
implementation. This will be cleaned up in the next patch.
Cc: qemu-stable@nongnu.org
Fixes: 7e5cdb345f77 ('ide: Increment BB in-flight counter for TRIM BH')
Buglink: https://redhat.atlassian.net/browse/RHEL-121686
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-5-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 095c08a7ba68cabaa6e0ce7a8a0804a949542c4c)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/ide/core.c | 37 ++++++++++++++++++-------------------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index b14983ec54..82c3ada14a 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -420,7 +420,6 @@ typedef struct TrimAIOCB {
QEMUBH *bh;
int ret;
QEMUIOVector *qiov;
- BlockAIOCB *aiocb;
int i, j;
} TrimAIOCB;
@@ -433,11 +432,6 @@ static void trim_aio_cancel(BlockAIOCB *acb)
iocb->i = (iocb->qiov->iov[iocb->j].iov_len / 8) - 1;
iocb->ret = -ECANCELED;
-
- if (iocb->aiocb) {
- blk_aio_cancel_async(iocb->aiocb);
- iocb->aiocb = NULL;
- }
}
static const AIOCBInfo trim_aiocb_info = {
@@ -456,15 +450,20 @@ static void ide_trim_bh_cb(void *opaque)
iocb->bh = NULL;
qemu_aio_unref(iocb);
- /* Paired with an increment in ide_issue_trim() */
- blk_dec_in_flight(blk);
+ /* Paired with blk_co_start_request in ide_trim_co_entry() */
+ blk_end_request(blk);
}
-static void ide_issue_trim_cb(void *opaque, int ret)
+static void coroutine_fn ide_trim_co_entry(void *opaque)
{
TrimAIOCB *iocb = opaque;
IDEState *s = iocb->s;
+ int ret = 0;
+
+ /* Paired with blk_end_request in ide_trim_bh_cb() */
+ blk_co_start_request(s->blk);
+loop:
if (iocb->i >= 0) {
if (ret >= 0) {
block_acct_done(blk_get_stats(s->blk), &s->acct);
@@ -499,11 +498,11 @@ static void ide_issue_trim_cb(void *opaque, int ret)
count << BDRV_SECTOR_BITS, BLOCK_ACCT_UNMAP);
/* Got an entry! Submit and exit. */
- iocb->aiocb = blk_aio_pdiscard(s->blk,
- sector << BDRV_SECTOR_BITS,
- count << BDRV_SECTOR_BITS,
- ide_issue_trim_cb, opaque);
- return;
+ ret = blk_co_pdiscard(s->blk,
+ sector << BDRV_SECTOR_BITS,
+ count << BDRV_SECTOR_BITS,
+ BDRV_REQ_NO_QUEUE);
+ goto loop;
}
iocb->j++;
@@ -514,7 +513,6 @@ static void ide_issue_trim_cb(void *opaque, int ret)
}
done:
- iocb->aiocb = NULL;
if (iocb->bh) {
replay_bh_schedule_event(iocb->bh);
}
@@ -527,9 +525,7 @@ BlockAIOCB *ide_issue_trim(
IDEState *s = opaque;
IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
TrimAIOCB *iocb;
-
- /* Paired with a decrement in ide_trim_bh_cb() */
- blk_inc_in_flight(s->blk);
+ Coroutine *co;
iocb = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque);
iocb->s = s;
@@ -539,7 +535,10 @@ BlockAIOCB *ide_issue_trim(
iocb->qiov = qiov;
iocb->i = -1;
iocb->j = 0;
- ide_issue_trim_cb(iocb, 0);
+
+ co = qemu_coroutine_create(ide_trim_co_entry, iocb);
+ aio_co_enter(qemu_get_current_aio_context(), co);
+
return &iocb->common;
}
--
2.52.0

View File

@ -0,0 +1,91 @@
From 03274f77317b494c8a5b29c0b3bf7112592d5d8a Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:31 +0200
Subject: [PATCH 06/52] ide-test: Factor out wait_dma_completion()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [6/7] 640daa938349847f3531cfc90e9c9d4d2b05fcc7 (kmwolf/centos-qemu-kvm)
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-7-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 92854c9c7539bdbf4f9c1abb33dd3ba59ff91e58)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qtest/ide-test.c | 48 +++++++++++++++++++++++++-----------------
1 file changed, 29 insertions(+), 19 deletions(-)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index ceee444a9e..c6dcb2c074 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -200,6 +200,34 @@ static uint64_t trim_range_le(uint64_t sector, uint16_t count)
return cpu_to_le64(((uint64_t)count << 48) + sector);
}
+static uint8_t wait_dma_completion(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bmdma_bar, QPCIBar ide_bar)
+{
+ uint8_t status;
+
+ /* Wait for the DMA transfer to complete */
+ do {
+ status = qpci_io_readb(dev, bmdma_bar, bmreg_status);
+ } while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE);
+
+ g_assert_cmpint(qtest_get_irq(qts, IDE_PRIMARY_IRQ), ==,
+ !!(status & BM_STS_INTR));
+
+ /* Check IDE status code */
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), DRDY);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), BSY | DRQ);
+
+ /* Reading the status register clears the IRQ */
+ g_assert(!qtest_get_irq(qts, IDE_PRIMARY_IRQ));
+
+ /* Stop DMA transfer if still active */
+ if (status & BM_STS_ACTIVE) {
+ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
+ }
+
+ return status;
+}
+
static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
int nb_sectors, PrdtEntry *prdt, int prdt_entries,
void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar,
@@ -280,25 +308,7 @@ static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
}
- /* Wait for the DMA transfer to complete */
- do {
- status = qpci_io_readb(dev, bmdma_bar, bmreg_status);
- } while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE);
-
- g_assert_cmpint(qtest_get_irq(qts, IDE_PRIMARY_IRQ), ==,
- !!(status & BM_STS_INTR));
-
- /* Check IDE status code */
- assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), DRDY);
- assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), BSY | DRQ);
-
- /* Reading the status register clears the IRQ */
- g_assert(!qtest_get_irq(qts, IDE_PRIMARY_IRQ));
-
- /* Stop DMA transfer if still active */
- if (status & BM_STS_ACTIVE) {
- qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
- }
+ status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar);
free_pci_device(dev);
--
2.52.0

View File

@ -0,0 +1,181 @@
From 1d7ff7d45d25d64fc17087a7efadca12fb9ee4e9 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Tue, 21 Apr 2026 18:11:32 +0200
Subject: [PATCH 07/52] ide-test: Test reset during TRIM
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
RH-Jira: RHEL-121686
RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Commit: [7/7] ce0ab448a920d91472da7c89080eeafa3d9cf830 (kmwolf/centos-qemu-kvm)
This is a regression test for the bug fixed in the previous commits, a
deadlock between the drain issued by an IDE reset and the TRIM state
machine.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260421161132.99878-8-kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 2fa24e9755994f76f08ea2452215eb50f26f4c21)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qtest/ide-test.c | 95 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 87 insertions(+), 8 deletions(-)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index c6dcb2c074..721e78170b 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -41,8 +41,11 @@
#define IDE_PCI_FUNC 1
#define IDE_BASE 0x1f0
+#define IDE_BASE2 0x3f6
#define IDE_PRIMARY_IRQ 14
+#define IDE_CTRL_RESET 0x04
+
#define ATAPI_BLOCK_SIZE 2048
/* How many bytes to receive via ATAPI PIO at one time.
@@ -99,6 +102,7 @@ enum {
CMDF_ABORT = 0x100,
CMDF_NO_BM = 0x200,
+ CMDF_NO_WAIT = 0x400,
};
enum {
@@ -228,21 +232,21 @@ static uint8_t wait_dma_completion(QTestState *qts, QPCIDevice *dev,
return status;
}
-static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
- int nb_sectors, PrdtEntry *prdt, int prdt_entries,
- void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar,
- uint64_t sector, int nb_sectors))
+static int send_dma_request_dev(QTestState *qts, QPCIDevice *dev,
+ QPCIBar bmdma_bar, QPCIBar ide_bar, int cmd,
+ uint64_t sector, int nb_sectors,
+ PrdtEntry *prdt, int prdt_entries,
+ void(*post_exec)(QPCIDevice *dev,
+ QPCIBar ide_bar,
+ uint64_t sector,
+ int nb_sectors))
{
- QPCIDevice *dev;
- QPCIBar bmdma_bar, ide_bar;
uintptr_t guest_prdt;
size_t len;
bool from_dev;
uint8_t status;
int flags;
- dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
-
flags = cmd & ~0xff;
cmd &= 0xff;
@@ -308,8 +312,28 @@ static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
}
+ if (flags & CMDF_NO_WAIT) {
+ return 0;
+ }
+
status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar);
+ return status;
+}
+
+static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
+ int nb_sectors, PrdtEntry *prdt, int prdt_entries,
+ void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar,
+ uint64_t sector, int nb_sectors))
+{
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+ status = send_dma_request_dev(qts, dev, bmdma_bar, ide_bar,
+ cmd, sector, nb_sectors, prdt, prdt_entries,
+ post_exec);
free_pci_device(dev);
return status;
@@ -457,6 +481,60 @@ static void test_bmdma_trim(void)
test_bmdma_teardown(qts);
}
+static void test_bmdma_trim_reset(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar, ide_bar2;
+ uint8_t status;
+ const uint64_t trim_range[] = {
+ trim_range_le(0, 2),
+ trim_range_le(6, 8),
+ };
+ size_t len = 512;
+ uint8_t *buf;
+ uintptr_t guest_buf;
+ PrdtEntry prdt[1];
+
+ qts = ide_test_start(
+ "-blockdev file,filename=%s,node-name=img "
+ "-blockdev blkdebug,image=img,node-name=dbg,discard=unmap,"
+ "inject-error.0.event=none,inject-error.0.iotype=discard,"
+ "inject-error.0.errno=0,inject-error.0.delay-ns=1000000 "
+ "-device ide-hd,drive=dbg,bus=ide.0",
+ tmp_path[0]);
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ guest_buf = guest_alloc(&guest_malloc, len);
+ prdt[0].addr = cpu_to_le32(guest_buf),
+ prdt[0].size = cpu_to_le32(len | PRDT_EOT),
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+ ide_bar2 = qpci_legacy_iomap(dev, IDE_BASE2);
+
+ buf = g_malloc(len);
+
+ /* TRIM request with two segments */
+ *((uint64_t *)buf) = trim_range[0];
+ *((uint64_t *)buf + 1) = trim_range[1];
+
+ qtest_memwrite(qts, guest_buf, buf, 2 * sizeof(uint64_t));
+
+ send_dma_request_dev(qts, dev, bmdma_bar, ide_bar, CMD_DSM | CMDF_NO_WAIT, 0, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+
+ /* Reset the device while the first segment is in flight */
+ qpci_io_writeb(dev, ide_bar2, 0, IDE_CTRL_RESET);
+
+ status = wait_dma_completion(qts, dev, bmdma_bar, ide_bar);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ free_pci_device(dev);
+ g_free(buf);
+ test_bmdma_teardown(qts);
+}
+
/*
* This test is developed according to the Programming Interface for
* Bus Master IDE Controller (Revision 1.0 5/16/94)
@@ -1138,6 +1216,7 @@ int main(int argc, char **argv)
qtest_add_func("/ide/bmdma/simple_rw", test_bmdma_simple_rw);
qtest_add_func("/ide/bmdma/trim", test_bmdma_trim);
+ qtest_add_func("/ide/bmdma/trim_reset", test_bmdma_trim_reset);
qtest_add_func("/ide/bmdma/various_prdts", test_bmdma_various_prdts);
qtest_add_func("/ide/bmdma/no_busmaster", test_bmdma_no_busmaster);
--
2.52.0

View File

@ -0,0 +1,153 @@
From 37596bc8c96fb8261c13fc4f78d0f95ea8ab1397 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Sat, 13 Jun 2026 23:03:38 +0300
Subject: [PATCH 50/52] iotests/046: Test that discard/write_zeroes wait for
dependencies
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [43/45] 7548e248ef6195c061c197e13865a49ab4586678 (kmwolf/centos-qemu-kvm)
This is a regression test for the bug fixed in the previous commit where
discard and write_zeroes operations wouldn't consider their dependencies
in s->cluster_allocs. Without the fix, this results in a corrupted
image.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260427170520.101242-5-kwolf@redhat.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Tested-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 389f5bcc744d3ddc127d550a57261aed9bbba1f3)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-55-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/046 | 46 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/046.out | 36 +++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/tests/qemu-iotests/046 b/tests/qemu-iotests/046
index 4c9ed4d26e..e03dd40147 100755
--- a/tests/qemu-iotests/046
+++ b/tests/qemu-iotests/046
@@ -184,6 +184,48 @@ aio_write -P 160 0x104000 0x18000
resume A
aio_flush
EOF
+
+# Create a pre-allocated zero cluster, then start a write on it and discard it
+# before the L2 update is made
+cat <<EOF
+write -P 181 0x120000 0x10000
+write -z 0x120000 0x10000
+
+break write_aio A
+aio_write -P 180 0x120000 0x10000
+wait_break A
+aio_discard 0x120000 0x10000
+resume A
+aio_flush
+EOF
+
+# Create a pre-allocated zero cluster, then start a write on it and a
+# concurrent zero write with MAY_UNMAP before the L2 update is made
+cat <<EOF
+write -P 181 0x130000 0x10000
+write -z 0x130000 0x10000
+
+break write_aio A
+aio_write -P 180 0x130000 0x10000
+wait_break A
+aio_write -z -u 0x130000 0x10000
+resume A
+aio_flush
+EOF
+
+# Create a pre-allocated zero cluster, then start a write on it and a
+# concurrent zero write without MAY_UNMAP before the L2 update is made
+cat <<EOF
+write -P 181 0x140000 0x10000
+write -z 0x140000 0x10000
+
+break write_aio A
+aio_write -P 180 0x140000 0x10000
+wait_break A
+aio_write -z 0x140000 0x10000
+resume A
+aio_flush
+EOF
}
overlay_io | $QEMU_IO blkdebug::"$TEST_IMG" | _filter_qemu_io |\
@@ -264,6 +306,10 @@ verify_io()
# Undefined content for 0x10c000 0x8000
echo read -P 160 0x114000 0x8000
echo read -P 17 0x11c000 0x4000
+
+ echo read -P 0 0x120000 0x10000
+ echo read -P 0 0x130000 0x10000
+ echo read -P 0 0x140000 0x10000
}
verify_io | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
diff --git a/tests/qemu-iotests/046.out b/tests/qemu-iotests/046.out
index b1a03f4041..6341df335c 100644
--- a/tests/qemu-iotests/046.out
+++ b/tests/qemu-iotests/046.out
@@ -139,6 +139,36 @@ wrote XXX/XXX bytes at offset XXX
XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
wrote XXX/XXX bytes at offset XXX
XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+blkdebug: Suspended request 'A'
+blkdebug: Resuming request 'A'
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+discarded XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+blkdebug: Suspended request 'A'
+blkdebug: Resuming request 'A'
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+blkdebug: Suspended request 'A'
+blkdebug: Resuming request 'A'
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Verify image content ==
read 65536/65536 bytes at offset 0
@@ -239,5 +269,11 @@ read 32768/32768 bytes at offset 1130496
32 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 16384/16384 bytes at offset 1163264
16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 65536/65536 bytes at offset 1179648
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 65536/65536 bytes at offset 1245184
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 65536/65536 bytes at offset 1310720
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
*** done
--
2.52.0

View File

@ -0,0 +1,42 @@
From a25d168305434f211fded8dc89c3b849dcf759e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
Date: Tue, 15 Jul 2025 15:30:21 +0100
Subject: [PATCH 27/52] iotests/147: ensure temporary sockets are closed before
exiting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [20/45] 80c29c667a333d72e60c403ad8c4e07bf42db189 (kmwolf/centos-qemu-kvm)
This avoids the python resource leak detector from issuing warnings
in the iotests.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
(cherry picked from commit d4d0ebfcc926c11d16320d0d5accf22e3441c115)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/147 | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
index 6d6f077a14..3e14bd389a 100755
--- a/tests/qemu-iotests/147
+++ b/tests/qemu-iotests/147
@@ -277,6 +277,7 @@ class BuiltinNBD(NBDBlockdevAddBase):
} }
self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
+ sockfd.close()
self._server_down()
--
2.52.0

View File

@ -0,0 +1,49 @@
From 45efe35d9113cbd76d4652ee56a805cbde58f6b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
Date: Tue, 15 Jul 2025 15:30:22 +0100
Subject: [PATCH 28/52] iotests/151: ensure subprocesses are cleaned up
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [21/45] 58ae050da1f3c7bfee87daad073dcc99b8eec51b (kmwolf/centos-qemu-kvm)
The iotest 151 creates a bunch of subprocesses, with their stdout
connected to a pipe but never reads any data from them and does
not gurantee the processes are killed on cleanup.
This triggers resource leak warnings from python when the
subprocess.Popen object is garbage collected.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
(cherry picked from commit 2b2fb25c2aaf5b2e8172d845db39cc50a951a12e)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/151 | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tests/qemu-iotests/151 b/tests/qemu-iotests/151
index f2ff9c5dac..06ee3585db 100755
--- a/tests/qemu-iotests/151
+++ b/tests/qemu-iotests/151
@@ -263,6 +263,11 @@ class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
break
except subprocess.TimeoutExpired:
self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
+ try:
+ p.kill()
+ p.stdout.close()
+ except:
+ pass
except IndexError:
pass
--
2.52.0

View File

@ -0,0 +1,116 @@
From 915f7779592a69d1243427ed8e5e95285b6daa4f Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Tue, 7 Oct 2025 10:17:00 -0400
Subject: [PATCH 36/52] iotests: add Linux loop device image creation test
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [29/45] b46da09b4329cc3b3c3263f2ac62b3386035de8d (kmwolf/centos-qemu-kvm)
This qemu-iotests test case is based on the reproducer that Jean-Louis
Dupond <jean-louis@dupond.be> shared in
https://gitlab.com/qemu-project/qemu/-/issues/3127.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-ID: <20251007141700.71891-4-stefanha@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Tested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Tested-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 59a1cf0cd31597d2f6e2c18dc400a1de8427d47d)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/tests/loop-create-file | 59 +++++++++++++++++++
tests/qemu-iotests/tests/loop-create-file.out | 8 +++
2 files changed, 67 insertions(+)
create mode 100755 tests/qemu-iotests/tests/loop-create-file
create mode 100644 tests/qemu-iotests/tests/loop-create-file.out
diff --git a/tests/qemu-iotests/tests/loop-create-file b/tests/qemu-iotests/tests/loop-create-file
new file mode 100755
index 0000000000..5ec75b046b
--- /dev/null
+++ b/tests/qemu-iotests/tests/loop-create-file
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# group: quick
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat, Inc.
+#
+# Test Linux loop device image creation
+#
+# This test verifies #3127 "qemu-img create fails on loop device with sector size 4096"
+# https://gitlab.com/qemu-project/qemu/-/issues/3127
+
+seq="$(basename $0)"
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup() {
+ if [ -n "$loopdev" ]; then
+ sudo losetup --detach "$loopdev"
+ fi
+
+ _cleanup_test_img
+}
+
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+cd ..
+. ./common.rc
+. ./common.filter
+
+_supported_fmt raw
+_supported_proto file
+_supported_os Linux
+
+if ! sudo -n losetup &>/dev/null; then
+ _notrun "sudo losetup not available"
+fi
+
+echo
+echo "=== Create image on a 4 KB sector size loop device ==="
+echo
+
+_make_test_img -f $IMGFMT 1M
+
+loopdev=$(sudo losetup --sector-size 4096 --find --show "$TEST_IMG")
+if [ -z "$loopdev" ]; then
+ _fail
+fi
+
+sudo $QEMU_IMG_PROG create -f raw "$loopdev" 1M | \
+ sed -e "s#/dev/loop[0-9]\\+#LOOPDEV#g"
+
+# success, all done
+echo
+echo '*** done'
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/tests/loop-create-file.out b/tests/qemu-iotests/tests/loop-create-file.out
new file mode 100644
index 0000000000..32d4155695
--- /dev/null
+++ b/tests/qemu-iotests/tests/loop-create-file.out
@@ -0,0 +1,8 @@
+QA output created by loop-create-file
+
+=== Create image on a 4 KB sector size loop device ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
+Formatting 'LOOPDEV', fmt=raw size=1048576
+
+*** done
--
2.52.0

View File

@ -0,0 +1,77 @@
From 86d893766713ddb454a9e7bf9e3069a41cf77b1e Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Tue, 20 Jan 2026 12:38:57 +0100
Subject: [PATCH 45/52] iotests: test active mirror with unaligned, small write
zeroes op
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [38/45] 39a5ae389b5bd6ee71ee61fb45fb8456e7068511 (kmwolf/centos-qemu-kvm)
This tests the scenario fixed by "block/mirror: check range
when setting zero bitmap for sync write" [0].
[0] https://lore.kernel.org/qemu-devel/20260112152544.261923-1-f.ebner@proxmox.com/
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-ID: <20260120113859.251743-1-f.ebner@proxmox.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Tested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
(cherry picked from commit 267d7ae99a1d3b5be9d3421db3bdf651cc18c7ab)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/151 | 20 ++++++++++++++++++++
tests/qemu-iotests/151.out | 4 ++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/tests/qemu-iotests/151 b/tests/qemu-iotests/151
index 06ee3585db..9b9c815db5 100755
--- a/tests/qemu-iotests/151
+++ b/tests/qemu-iotests/151
@@ -191,6 +191,26 @@ class TestActiveMirror(iotests.QMPTestCase):
self.potential_writes_in_flight = False
+ def testUnalignedSmallerThanGranularityWriteZeroes(self):
+ # Fill the source image
+ self.vm.hmp_qemu_io('source', 'write -P 1 0 %i' % self.image_len);
+
+ # Start the block job
+ self.vm.cmd('blockdev-mirror',
+ job_id='mirror',
+ filter_node_name='mirror-node',
+ device='source-node',
+ target='target-node',
+ sync='full',
+ copy_mode='write-blocking')
+
+ # Wait for the READY event
+ self.wait_ready(drive='mirror')
+
+ for offset in range(6 * self.image_len // 8, 7 * self.image_len // 8, 1024 * 1024):
+ self.vm.hmp_qemu_io('source', 'aio_write -z %i 512' % (offset + 512))
+
+ self.complete_and_wait(drive='mirror', wait_ready=False)
class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
image_len = 128 * 1024 * 1024 # MB
diff --git a/tests/qemu-iotests/151.out b/tests/qemu-iotests/151.out
index 3f8a935a08..2f7d3902f2 100644
--- a/tests/qemu-iotests/151.out
+++ b/tests/qemu-iotests/151.out
@@ -1,5 +1,5 @@
-......
+.......
----------------------------------------------------------------------
-Ran 6 tests
+Ran 7 tests
OK
--
2.52.0

View File

@ -0,0 +1,75 @@
From c9239fed6d3b7f14b847f10d53fd7c057e08cafd Mon Sep 17 00:00:00 2001
From: Hanna Czenczek <hreitz@redhat.com>
Date: Mon, 10 Nov 2025 16:48:42 +0100
Subject: [PATCH 14/52] nvme: Kick and check completions in BDS context
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [7/45] 6f2f54a3568591087538e0cc2d423ca71f44d661 (kmwolf/centos-qemu-kvm)
nvme_process_completion() must run in the main BDS context, so schedule
a BH for requests that arent there.
The context in which we kick does not matter, but lets just keep kick
and process_completion together for simplicitys sake.
(For what its worth, a quick fio bandwidth test indicates that on my
test hardware, if anything, this may be a bit better than kicking
immediately before scheduling a pure nvme_process_completion() BH. But
I wouldnt take more from those results than that it doesnt really seem
to matter either way.)
Cc: qemu-stable@nongnu.org
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
Message-ID: <20251110154854.151484-8-hreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 7a501bbd51941fb1867d78e6b0d1dc69e396b9e2)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/nvme.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/block/nvme.c b/block/nvme.c
index 8df53ee4ca..7ed5f570bc 100644
--- a/block/nvme.c
+++ b/block/nvme.c
@@ -481,7 +481,7 @@ static void nvme_trace_command(const NvmeCmd *cmd)
}
}
-static void nvme_deferred_fn(void *opaque)
+static void nvme_kick_and_check_completions(void *opaque)
{
NVMeQueuePair *q = opaque;
@@ -490,6 +490,18 @@ static void nvme_deferred_fn(void *opaque)
nvme_process_completion(q);
}
+static void nvme_deferred_fn(void *opaque)
+{
+ NVMeQueuePair *q = opaque;
+
+ if (qemu_get_current_aio_context() == q->s->aio_context) {
+ nvme_kick_and_check_completions(q);
+ } else {
+ aio_bh_schedule_oneshot(q->s->aio_context,
+ nvme_kick_and_check_completions, q);
+ }
+}
+
static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
NvmeCmd *cmd, BlockCompletionFunc cb,
void *opaque)
--
2.52.0

View File

@ -0,0 +1,273 @@
From af936e3ce66c2b8ab98fe6a529cea73d86f78ee9 Mon Sep 17 00:00:00 2001
From: Hanna Czenczek <hreitz@redhat.com>
Date: Mon, 10 Nov 2025 16:48:44 +0100
Subject: [PATCH 15/52] nvme: Note in which AioContext some functions run
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [8/45] 1ad4b84c0f2def1565fb3de24dcb6b3a17a18fec (kmwolf/centos-qemu-kvm)
Sprinkle comments throughout block/nvme.c noting for some functions
(where it may not be obvious) that they require a certain AioContext, or
in which AioContext they do happen to run (for callbacks, BHs, event
notifiers).
Suggested-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
Message-ID: <20251110154854.151484-10-hreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit ac3520f599fedee05945ce06bb0f71820a7b2ffc)
(Mjt: pick this comments-only, no-code-changes commit to 10.1.x
so the next change applies cleanly)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/nvme.c | 43 ++++++++++++++++++++++++++++++++++---------
1 file changed, 34 insertions(+), 9 deletions(-)
diff --git a/block/nvme.c b/block/nvme.c
index 7ed5f570bc..c3d3b99d1f 100644
--- a/block/nvme.c
+++ b/block/nvme.c
@@ -65,6 +65,7 @@ typedef struct {
} NVMeQueue;
typedef struct {
+ /* Called from nvme_process_completion() in the BDS's main AioContext */
BlockCompletionFunc *cb;
void *opaque;
int cid;
@@ -84,6 +85,7 @@ typedef struct {
uint8_t *prp_list_pages;
/* Fields protected by @lock */
+ /* Coroutines in this queue are woken in their own context */
CoQueue free_req_queue;
NVMeQueue sq, cq;
int cq_phase;
@@ -92,7 +94,7 @@ typedef struct {
int need_kick;
int inflight;
- /* Thread-safe, no lock necessary */
+ /* Thread-safe, no lock necessary; runs in the BDS's main context */
QEMUBH *completion_bh;
} NVMeQueuePair;
@@ -206,11 +208,13 @@ static void nvme_free_queue_pair(NVMeQueuePair *q)
g_free(q);
}
+/* Runs in the BDS's main AioContext */
static void nvme_free_req_queue_cb(void *opaque)
{
NVMeQueuePair *q = opaque;
qemu_mutex_lock(&q->lock);
+ /* qemu_co_enter_next() wakes the coroutine in its own AioContext */
while (q->free_req_head != -1 &&
qemu_co_enter_next(&q->free_req_queue, &q->lock)) {
/* Retry waiting requests */
@@ -281,7 +285,7 @@ fail:
return NULL;
}
-/* With q->lock */
+/* With q->lock, must be run in the BDS's main AioContext */
static void nvme_kick(NVMeQueuePair *q)
{
BDRVNVMeState *s = q->s;
@@ -308,7 +312,10 @@ static NVMeRequest *nvme_get_free_req_nofail_locked(NVMeQueuePair *q)
return req;
}
-/* Return a free request element if any, otherwise return NULL. */
+/*
+ * Return a free request element if any, otherwise return NULL.
+ * May be run from any AioContext.
+ */
static NVMeRequest *nvme_get_free_req_nowait(NVMeQueuePair *q)
{
QEMU_LOCK_GUARD(&q->lock);
@@ -321,6 +328,7 @@ static NVMeRequest *nvme_get_free_req_nowait(NVMeQueuePair *q)
/*
* Wait for a free request to become available if necessary, then
* return it.
+ * May be called in any AioContext.
*/
static coroutine_fn NVMeRequest *nvme_get_free_req(NVMeQueuePair *q)
{
@@ -328,20 +336,21 @@ static coroutine_fn NVMeRequest *nvme_get_free_req(NVMeQueuePair *q)
while (q->free_req_head == -1) {
trace_nvme_free_req_queue_wait(q->s, q->index);
+ /* nvme_free_req_queue_cb() wakes us in our own AioContext */
qemu_co_queue_wait(&q->free_req_queue, &q->lock);
}
return nvme_get_free_req_nofail_locked(q);
}
-/* With q->lock */
+/* With q->lock, may be called in any AioContext */
static void nvme_put_free_req_locked(NVMeQueuePair *q, NVMeRequest *req)
{
req->free_req_next = q->free_req_head;
q->free_req_head = req - q->reqs;
}
-/* With q->lock */
+/* With q->lock, may be called in any AioContext */
static void nvme_wake_free_req_locked(NVMeQueuePair *q)
{
if (!qemu_co_queue_empty(&q->free_req_queue)) {
@@ -350,7 +359,7 @@ static void nvme_wake_free_req_locked(NVMeQueuePair *q)
}
}
-/* Insert a request in the freelist and wake waiters */
+/* Insert a request in the freelist and wake waiters (from any AioContext) */
static void nvme_put_free_req_and_wake(NVMeQueuePair *q, NVMeRequest *req)
{
qemu_mutex_lock(&q->lock);
@@ -381,7 +390,7 @@ static inline int nvme_translate_error(const NvmeCqe *c)
}
}
-/* With q->lock */
+/* With q->lock, must be run in the BDS's main AioContext */
static bool nvme_process_completion(NVMeQueuePair *q)
{
BDRVNVMeState *s = q->s;
@@ -451,6 +460,7 @@ static bool nvme_process_completion(NVMeQueuePair *q)
return progress;
}
+/* As q->completion_bh, runs in the BDS's main AioContext */
static void nvme_process_completion_bh(void *opaque)
{
NVMeQueuePair *q = opaque;
@@ -481,6 +491,7 @@ static void nvme_trace_command(const NvmeCmd *cmd)
}
}
+/* Must be run in the BDS's main AioContext */
static void nvme_kick_and_check_completions(void *opaque)
{
NVMeQueuePair *q = opaque;
@@ -490,6 +501,7 @@ static void nvme_kick_and_check_completions(void *opaque)
nvme_process_completion(q);
}
+/* Runs in nvme_submit_command()'s AioContext */
static void nvme_deferred_fn(void *opaque)
{
NVMeQueuePair *q = opaque;
@@ -502,6 +514,7 @@ static void nvme_deferred_fn(void *opaque)
}
}
+/* May be run in any AioContext */
static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
NvmeCmd *cmd, BlockCompletionFunc cb,
void *opaque)
@@ -523,6 +536,7 @@ static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
defer_call(nvme_deferred_fn, q);
}
+/* Put into NVMeRequest.cb, so runs in the BDS's main AioContext */
static void nvme_admin_cmd_sync_cb(void *opaque, int ret)
{
int *pret = opaque;
@@ -530,6 +544,7 @@ static void nvme_admin_cmd_sync_cb(void *opaque, int ret)
aio_wait_kick();
}
+/* Must be run in the BDS's or qemu's main AioContext */
static int nvme_admin_cmd_sync(BlockDriverState *bs, NvmeCmd *cmd)
{
BDRVNVMeState *s = bs->opaque;
@@ -638,6 +653,7 @@ out:
return ret;
}
+/* Must be run in the BDS's main AioContext */
static void nvme_poll_queue(NVMeQueuePair *q)
{
const size_t cqe_offset = q->cq.head * NVME_CQ_ENTRY_BYTES;
@@ -660,6 +676,7 @@ static void nvme_poll_queue(NVMeQueuePair *q)
qemu_mutex_unlock(&q->lock);
}
+/* Must be run in the BDS's main AioContext */
static void nvme_poll_queues(BDRVNVMeState *s)
{
int i;
@@ -669,6 +686,7 @@ static void nvme_poll_queues(BDRVNVMeState *s)
}
}
+/* Run as an event notifier in the BDS's main AioContext */
static void nvme_handle_event(EventNotifier *n)
{
BDRVNVMeState *s = container_of(n, BDRVNVMeState,
@@ -722,6 +740,7 @@ out_error:
return false;
}
+/* Run as an event notifier in the BDS's main AioContext */
static bool nvme_poll_cb(void *opaque)
{
EventNotifier *e = opaque;
@@ -745,6 +764,7 @@ static bool nvme_poll_cb(void *opaque)
return false;
}
+/* Run as an event notifier in the BDS's main AioContext */
static void nvme_poll_ready(EventNotifier *e)
{
BDRVNVMeState *s = container_of(e, BDRVNVMeState,
@@ -1050,7 +1070,7 @@ static int nvme_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz)
return 0;
}
-/* Called with s->dma_map_lock */
+/* Called with s->dma_map_lock, may be run in any AioContext */
static coroutine_fn int nvme_cmd_unmap_qiov(BlockDriverState *bs,
QEMUIOVector *qiov)
{
@@ -1061,13 +1081,17 @@ static coroutine_fn int nvme_cmd_unmap_qiov(BlockDriverState *bs,
if (!s->dma_map_count && !qemu_co_queue_empty(&s->dma_flush_queue)) {
r = qemu_vfio_dma_reset_temporary(s->vfio);
if (!r) {
+ /*
+ * Queue access is protected by the dma_map_lock, and all
+ * coroutines are woken in their own AioContext
+ */
qemu_co_queue_restart_all(&s->dma_flush_queue);
}
}
return r;
}
-/* Called with s->dma_map_lock */
+/* Called with s->dma_map_lock, may be run in any AioContext */
static coroutine_fn int nvme_cmd_map_qiov(BlockDriverState *bs, NvmeCmd *cmd,
NVMeRequest *req, QEMUIOVector *qiov)
{
@@ -1186,6 +1210,7 @@ static void nvme_rw_cb_bh(void *opaque)
qemu_coroutine_enter(data->co);
}
+/* Put into NVMeRequest.cb, so runs in the BDS's main AioContext */
static void nvme_rw_cb(void *opaque, int ret)
{
NVMeCoData *data = opaque;
--
2.52.0

View File

@ -0,0 +1,104 @@
From d8bdd0cbf122d1f01acaec8c9846b9cdbf925d3c Mon Sep 17 00:00:00 2001
From: John Snow <jsnow@redhat.com>
Date: Tue, 13 Aug 2024 09:35:30 -0400
Subject: [PATCH 25/52] python: backport 'Remove deprecated get_event_loop
calls'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [18/45] 7f8d672738b92fa5935342eded7559bc16bef7ab (kmwolf/centos-qemu-kvm)
This method was deprecated in 3.12 because it ordinarily should not be
used from coroutines; if there is not a currently running event loop,
this automatically creates a new event loop - which is usually not what
you want from code that would ever run in the bottom half.
In our case, we do want this behavior in two places:
(1) The synchronous shim, for convenience: this allows fully sync
programs to use QEMUMonitorProtocol() without needing to set up an event
loop beforehand. This is intentional to fully box in the async
complexities into the legacy sync shim.
(2) The qmp_tui shell; instead of relying on asyncio.run to create and
run an asyncio program, we need to be able to pass the current asyncio
loop to urwid setup functions. For convenience, again, we create one if
one is not present to simplify the creation of the TUI appliance.
The remaining user of get_event_loop() was in fact one of the erroneous
users that should not have been using this function: if there's no
running event loop inside of a coroutine, you're in big trouble :)
Signed-off-by: John Snow <jsnow@redhat.com>
cherry picked from commit python-qemu-qmp@aa1ff9907603a3033296027e1bd021133df86ef1
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
(cherry picked from commit 5d99044d09db0fa8c2b3294e301927118f9effc9)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
python/qemu/qmp/legacy.py | 9 ++++++++-
python/qemu/qmp/qmp_tui.py | 7 ++++++-
python/tests/protocol.py | 2 +-
3 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index 22a2b5616e..ea9b8032c3 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -86,7 +86,14 @@ def __init__(self,
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
- self._aloop = asyncio.get_event_loop()
+
+ try:
+ self._aloop = asyncio.get_running_loop()
+ except RuntimeError:
+ # No running loop; since this is a sync shim likely to be
+ # used in fully sync programs, create one if neccessary.
+ self._aloop = asyncio.get_event_loop_policy().get_event_loop()
+
self._address = address
self._timeout: Optional[float] = None
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 562be008d5..651f611316 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -377,7 +377,12 @@ def run(self, debug: bool = False) -> None:
screen = urwid.raw_display.Screen()
screen.set_terminal_properties(256)
- self.aloop = asyncio.get_event_loop()
+ try:
+ self.aloop = asyncio.get_running_loop()
+ except RuntimeError:
+ # No running asyncio event loop. Create one if necessary.
+ self.aloop = asyncio.get_event_loop_policy().get_event_loop()
+
self.aloop.set_debug(debug)
# Gracefully handle SIGTERM and SIGINT signals
diff --git a/python/tests/protocol.py b/python/tests/protocol.py
index c254c77b17..e565802516 100644
--- a/python/tests/protocol.py
+++ b/python/tests/protocol.py
@@ -227,7 +227,7 @@ def async_test(async_test_method):
Decorator; adds SetUp and TearDown to async tests.
"""
async def _wrapper(self, *args, **kwargs):
- loop = asyncio.get_event_loop()
+ loop = asyncio.get_running_loop()
loop.set_debug(True)
await self._asyncSetUp()
--
2.52.0

View File

@ -0,0 +1,210 @@
From 3739feb5280370c3439030e1f1fe0ec0d0eb2ccb Mon Sep 17 00:00:00 2001
From: John Snow <jsnow@redhat.com>
Date: Wed, 3 Sep 2025 01:06:30 -0400
Subject: [PATCH 26/52] python: backport 'avoid creating additional event loops
per thread'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [19/45] 1f870d1d45d5158c428bac46820d5aa7621f810f (kmwolf/centos-qemu-kvm)
This commit is two backports squashed into one to avoid regressions.
python: *really* remove get_event_loop
A prior commit, aa1ff990, switched away from using get_event_loop *by
default*, but this is not good enough to avoid deprecation warnings as
`asyncio.get_event_loop_policy().get_event_loop()` is *also*
deprecated. Replace this mechanism with explicit calls to
asyncio.get_new_loop() and revise the cleanup mechanisms in __del__ to
match.
python: avoid creating additional event loops per thread
"Too hasty by far!", commit 21ce2ee4 attempted to avoid deprecated
behavior altogether by calling new_event_loop() directly if there was no
loop currently running, but this has the unfortunate side effect of
potentially creating multiple event loops per thread if tests
instantiate multiple QMP connections in a single thread. This behavior
is apparently not well-defined and causes problems in some, but not all,
combinations of Python interpreter version and platform environment.
Partially revert to Daniel Berrange's original patch, which calls
get_event_loop and simply suppresses the deprecation warning in
Python<=3.13. This time, however, additionally register new loops
created with new_event_loop() so that future calls to get_event_loop()
will return the loop already created.
Reported-by: Richard W.M. Jones <rjones@redhat.com>
Reported-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
cherry picked from commit python-qemu-qmp@21ce2ee4f2df87efe84a27b9c5112487f4670622
cherry picked from commit python-qemu-qmp@c08fb82b38212956ccffc03fc6d015c3979f42fe
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
(cherry picked from commit 85f223e5b031eb8ab63fbca314a4fb296a3a2632)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
python/qemu/qmp/legacy.py | 46 +++++++++++++++++++++++---------------
python/qemu/qmp/qmp_tui.py | 10 ++-------
python/qemu/qmp/util.py | 27 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 26 deletions(-)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index ea9b8032c3..c732212c04 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -38,6 +38,7 @@
from .error import QMPError
from .protocol import Runstate, SocketAddrT
from .qmp_client import QMPClient
+from .util import get_or_create_event_loop
#: QMPMessage is an entire QMP message of any kind.
@@ -86,17 +87,13 @@ def __init__(self,
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
-
- try:
- self._aloop = asyncio.get_running_loop()
- except RuntimeError:
- # No running loop; since this is a sync shim likely to be
- # used in fully sync programs, create one if neccessary.
- self._aloop = asyncio.get_event_loop_policy().get_event_loop()
-
self._address = address
self._timeout: Optional[float] = None
+ # This is a sync shim intended for use in fully synchronous
+ # programs. Create and set an event loop if necessary.
+ self._aloop = get_or_create_event_loop()
+
if server:
assert not isinstance(self._address, socket.socket)
self._sync(self._qmp.start_server(self._address))
@@ -310,17 +307,30 @@ def send_fd_scm(self, fd: int) -> None:
self._qmp.send_fd_scm(fd)
def __del__(self) -> None:
- if self._qmp.runstate == Runstate.IDLE:
- return
+ if self._qmp.runstate != Runstate.IDLE:
+ self._qmp.logger.warning(
+ "QEMUMonitorProtocol object garbage collected without a prior "
+ "call to close()"
+ )
if not self._aloop.is_running():
- self.close()
- else:
- # Garbage collection ran while the event loop was running.
- # Nothing we can do about it now, but if we don't raise our
- # own error, the user will be treated to a lot of traceback
- # they might not understand.
+ if self._qmp.runstate != Runstate.IDLE:
+ # If the user neglected to close the QMP session and we
+ # are not currently running in an asyncio context, we
+ # have the opportunity to close the QMP session. If we
+ # do not do this, the error messages presented over
+ # dangling async resources may not make any sense to the
+ # user.
+ self.close()
+
+ if self._qmp.runstate != Runstate.IDLE:
+ # If QMP is still not quiesced, it means that the garbage
+ # collector ran from a context within the event loop and we
+ # are simply too late to take any corrective action. Raise
+ # our own error to give meaningful feedback to the user in
+ # order to prevent pages of asyncio stacktrace jargon.
raise QMPError(
- "QEMUMonitorProtocol.close()"
- " was not called before object was garbage collected"
+ "QEMUMonitorProtocol.close() was not called before object was "
+ "garbage collected, and could not be closed due to GC running "
+ "in the event loop"
)
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 651f611316..89b0f5e081 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -40,7 +40,7 @@
from .message import DeserializationError, Message, UnexpectedTypeError
from .protocol import ConnectError, Runstate
from .qmp_client import ExecInterruptedError, QMPClient
-from .util import pretty_traceback
+from .util import get_or_create_event_loop, pretty_traceback
# The name of the signal that is used to update the history list
@@ -376,13 +376,7 @@ def run(self, debug: bool = False) -> None:
"""
screen = urwid.raw_display.Screen()
screen.set_terminal_properties(256)
-
- try:
- self.aloop = asyncio.get_running_loop()
- except RuntimeError:
- # No running asyncio event loop. Create one if necessary.
- self.aloop = asyncio.get_event_loop_policy().get_event_loop()
-
+ self.aloop = get_or_create_event_loop()
self.aloop.set_debug(debug)
# Gracefully handle SIGTERM and SIGINT signals
diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
index 0b3e781373..47ec39a8b5 100644
--- a/python/qemu/qmp/util.py
+++ b/python/qemu/qmp/util.py
@@ -10,6 +10,7 @@
import sys
import traceback
from typing import TypeVar, cast
+import warnings
T = TypeVar('T')
@@ -20,6 +21,32 @@
# --------------------------
+def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
+ """
+ Return this thread's current event loop, or create a new one.
+
+ This function behaves similarly to asyncio.get_event_loop() in
+ Python<=3.13, where if there is no event loop currently associated
+ with the current context, it will create and register one. It should
+ generally not be used in any asyncio-native applications.
+ """
+ try:
+ with warnings.catch_warnings():
+ # Python <= 3.13 will trigger deprecation warnings if no
+ # event loop is set, but will create and set a new loop.
+ warnings.simplefilter("ignore")
+ loop = asyncio.get_event_loop()
+ except RuntimeError:
+ # Python 3.14+: No event loop set for this thread,
+ # create and set one.
+ loop = asyncio.new_event_loop()
+ # Set this loop as the current thread's loop, to be returned
+ # by calls to get_event_loop() in the future.
+ asyncio.set_event_loop(loop)
+
+ return loop
+
+
async def flush(writer: asyncio.StreamWriter) -> None:
"""
Utility function to ensure a StreamWriter is *fully* drained.
--
2.52.0

View File

@ -0,0 +1,300 @@
From 5de86fbbb5f6c0353bd2668c0424da28bf786264 Mon Sep 17 00:00:00 2001
From: John Snow <jsnow@redhat.com>
Date: Tue, 6 Jun 2023 13:19:11 -0400
Subject: [PATCH 24/52] python: backport 'drop Python3.6 workarounds'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [17/45] 65ffd438927850c395611deb3821d01b71d3c2c1 (kmwolf/centos-qemu-kvm)
Now that the minimum version is 3.7, drop some of the 3.6-specific hacks
we've been carrying. A single remaining compatibility hack concerning
3.6's lack of @asynccontextmanager is addressed in the following commit.
Signed-off-by: John Snow <jsnow@redhat.com>
cherry picked from commit python-qemu-qmp@3e8e34e594cfc6b707e6f67959166acde4b421b8
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
(cherry picked from commit f9d2e0a3bd7ba2a693a892881f91cf53fa90cc71)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
python/qemu/qmp/protocol.py | 13 ++---
python/qemu/qmp/qmp_tui.py | 8 +--
python/qemu/qmp/util.py | 107 ++----------------------------------
python/tests/protocol.py | 8 +--
4 files changed, 17 insertions(+), 119 deletions(-)
diff --git a/python/qemu/qmp/protocol.py b/python/qemu/qmp/protocol.py
index a4ffdfad51..4aff0ea423 100644
--- a/python/qemu/qmp/protocol.py
+++ b/python/qemu/qmp/protocol.py
@@ -36,13 +36,10 @@
from .error import QMPError
from .util import (
bottom_half,
- create_task,
exception_summary,
flush,
- is_closing,
pretty_traceback,
upper_half,
- wait_closed,
)
@@ -663,8 +660,8 @@ async def _establish_session(self) -> None:
reader_coro = self._bh_loop_forever(self._bh_recv_message, 'Reader')
writer_coro = self._bh_loop_forever(self._bh_send_message, 'Writer')
- self._reader_task = create_task(reader_coro)
- self._writer_task = create_task(writer_coro)
+ self._reader_task = asyncio.create_task(reader_coro)
+ self._writer_task = asyncio.create_task(writer_coro)
self._bh_tasks = asyncio.gather(
self._reader_task,
@@ -689,7 +686,7 @@ def _schedule_disconnect(self) -> None:
if not self._dc_task:
self._set_state(Runstate.DISCONNECTING)
self.logger.debug("Scheduling disconnect.")
- self._dc_task = create_task(self._bh_disconnect())
+ self._dc_task = asyncio.create_task(self._bh_disconnect())
@upper_half
async def _wait_disconnect(self) -> None:
@@ -825,13 +822,13 @@ async def _bh_close_stream(self, error_pathway: bool = False) -> None:
if not self._writer:
return
- if not is_closing(self._writer):
+ if not self._writer.is_closing():
self.logger.debug("Closing StreamWriter.")
self._writer.close()
self.logger.debug("Waiting for StreamWriter to close ...")
try:
- await wait_closed(self._writer)
+ await self._writer.wait_closed()
except Exception: # pylint: disable=broad-except
# It's hard to tell if the Stream is already closed or
# not. Even if one of the tasks has failed, it may have
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 2d9ebbd20b..562be008d5 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -40,7 +40,7 @@
from .message import DeserializationError, Message, UnexpectedTypeError
from .protocol import ConnectError, Runstate
from .qmp_client import ExecInterruptedError, QMPClient
-from .util import create_task, pretty_traceback
+from .util import pretty_traceback
# The name of the signal that is used to update the history list
@@ -225,7 +225,7 @@ def cb_send_to_server(self, raw_msg: str) -> None:
"""
try:
msg = Message(bytes(raw_msg, encoding='utf-8'))
- create_task(self._send_to_server(msg))
+ asyncio.create_task(self._send_to_server(msg))
except (DeserializationError, UnexpectedTypeError) as err:
raw_msg = format_json(raw_msg)
logging.info('Invalid message: %s', err.error_message)
@@ -246,7 +246,7 @@ def kill_app(self) -> None:
Initiates killing of app. A bridge between asynchronous and synchronous
code.
"""
- create_task(self._kill_app())
+ asyncio.create_task(self._kill_app())
async def _kill_app(self) -> None:
"""
@@ -393,7 +393,7 @@ def run(self, debug: bool = False) -> None:
handle_mouse=True,
event_loop=event_loop)
- create_task(self.manage_connection(), self.aloop)
+ self.aloop.create_task(self.manage_connection())
try:
main_loop.run()
except Exception as err:
diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
index ca6225e9cd..0b3e781373 100644
--- a/python/qemu/qmp/util.py
+++ b/python/qemu/qmp/util.py
@@ -1,25 +1,15 @@
"""
Miscellaneous Utilities
-This module provides asyncio utilities and compatibility wrappers for
-Python 3.6 to provide some features that otherwise become available in
-Python 3.7+.
-
-Various logging and debugging utilities are also provided, such as
-`exception_summary()` and `pretty_traceback()`, used primarily for
-adding information into the logging stream.
+This module provides asyncio and various logging and debugging
+utilities, such as `exception_summary()` and `pretty_traceback()`, used
+primarily for adding information into the logging stream.
"""
import asyncio
import sys
import traceback
-from typing import (
- Any,
- Coroutine,
- Optional,
- TypeVar,
- cast,
-)
+from typing import TypeVar, cast
T = TypeVar('T')
@@ -79,95 +69,6 @@ def bottom_half(func: T) -> T:
return func
-# -------------------------------
-# Section: Compatibility Wrappers
-# -------------------------------
-
-
-def create_task(coro: Coroutine[Any, Any, T],
- loop: Optional[asyncio.AbstractEventLoop] = None
- ) -> 'asyncio.Future[T]':
- """
- Python 3.6-compatible `asyncio.create_task` wrapper.
-
- :param coro: The coroutine to execute in a task.
- :param loop: Optionally, the loop to create the task in.
-
- :return: An `asyncio.Future` object.
- """
- if sys.version_info >= (3, 7):
- if loop is not None:
- return loop.create_task(coro)
- return asyncio.create_task(coro) # pylint: disable=no-member
-
- # Python 3.6:
- return asyncio.ensure_future(coro, loop=loop)
-
-
-def is_closing(writer: asyncio.StreamWriter) -> bool:
- """
- Python 3.6-compatible `asyncio.StreamWriter.is_closing` wrapper.
-
- :param writer: The `asyncio.StreamWriter` object.
- :return: `True` if the writer is closing, or closed.
- """
- if sys.version_info >= (3, 7):
- return writer.is_closing()
-
- # Python 3.6:
- transport = writer.transport
- assert isinstance(transport, asyncio.WriteTransport)
- return transport.is_closing()
-
-
-async def wait_closed(writer: asyncio.StreamWriter) -> None:
- """
- Python 3.6-compatible `asyncio.StreamWriter.wait_closed` wrapper.
-
- :param writer: The `asyncio.StreamWriter` to wait on.
- """
- if sys.version_info >= (3, 7):
- await writer.wait_closed()
- return
-
- # Python 3.6
- transport = writer.transport
- assert isinstance(transport, asyncio.WriteTransport)
-
- while not transport.is_closing():
- await asyncio.sleep(0)
-
- # This is an ugly workaround, but it's the best I can come up with.
- sock = transport.get_extra_info('socket')
-
- if sock is None:
- # Our transport doesn't have a socket? ...
- # Nothing we can reasonably do.
- return
-
- while sock.fileno() != -1:
- await asyncio.sleep(0)
-
-
-def asyncio_run(coro: Coroutine[Any, Any, T], *, debug: bool = False) -> T:
- """
- Python 3.6-compatible `asyncio.run` wrapper.
-
- :param coro: A coroutine to execute now.
- :return: The return value from the coroutine.
- """
- if sys.version_info >= (3, 7):
- return asyncio.run(coro, debug=debug)
-
- # Python 3.6
- loop = asyncio.get_event_loop()
- loop.set_debug(debug)
- ret = loop.run_until_complete(coro)
- loop.close()
-
- return ret
-
-
# ----------------------------
# Section: Logging & Debugging
# ----------------------------
diff --git a/python/tests/protocol.py b/python/tests/protocol.py
index 56c4d441f9..c254c77b17 100644
--- a/python/tests/protocol.py
+++ b/python/tests/protocol.py
@@ -8,7 +8,6 @@
from qemu.qmp import ConnectError, Runstate
from qemu.qmp.protocol import AsyncProtocol, StateError
-from qemu.qmp.util import asyncio_run, create_task
class NullProtocol(AsyncProtocol[None]):
@@ -124,7 +123,7 @@ async def _runner():
if allow_cancellation:
return
raise
- return create_task(_runner())
+ return asyncio.create_task(_runner())
@contextmanager
@@ -271,7 +270,7 @@ async def _watcher():
msg=f"Expected state '{state.name}'",
)
- self.runstate_watcher = create_task(_watcher())
+ self.runstate_watcher = asyncio.create_task(_watcher())
# Kick the loop and force the task to block on the event.
await asyncio.sleep(0)
@@ -589,7 +588,8 @@ async def _asyncTearDown(self):
async def testSmoke(self):
with TemporaryDirectory(suffix='.qmp') as tmpdir:
sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock")
- server_task = create_task(self.server.start_server_and_accept(sock))
+ server_task = asyncio.create_task(
+ self.server.start_server_and_accept(sock))
# give the server a chance to start listening [...]
await asyncio.sleep(0)
--
2.52.0

View File

@ -0,0 +1,162 @@
From 21a26a72ecde7527ece5fdef3ba23f10084fd43d Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Sat, 13 Jun 2026 23:03:37 +0300
Subject: [PATCH 49/52] qcow2: Fix corruption on discard during write with COW
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [42/45] 5569e7b55c4b1c45632b64057b774a4523c23ac1 (kmwolf/centos-qemu-kvm)
Most code in qcow2 that accesses (and potentially modifies) L2 tables
does so while holding s->lock.
There is one exception, which is allocating writes. They hold the lock
initially while allocating clusters, but drop it for writing the guest
payload before taking the lock again for updating the L2 tables. This
allows concurrent requests that touch other parts of the image file to
continue in parallel and is an important performance optimisation.
However, this means that other requests that run while the lock is
dropped for writing guest data must synchronise with the list of
allocating requests in s->cluster_allocs and wait if they would overlap.
For writes, this is done in handle_dependencies(), but discard and write
zeros operations neglect to synchronise with s->cluster_allocs.
This means that discard can free a cluster whose L2 entry will already
be modified in qcow2_alloc_cluster_link_l2() by a previously started
write. In the case of a pre-allocated zero cluster that is in the
process of being overwritten, this means that discard can lead to a
situation where the cluster is still mapped (because the write will
restore the L2 entry just without the zero flag), but its refcount has
been decreased, resulting in a corrupted image.
Add the missing synchronisation to qcow2_cluster_discard() and
qcow2_subcluster_zeroize() to fix the problem.
Cc: qemu-stable@nongnu.org
Reported-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260427170520.101242-4-kwolf@redhat.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Tested-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit b8bfb1478d61512f851badd0d912c6661a2efee7)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-54-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/qcow2-cluster.c | 52 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index ce8c0076b3..c20011d34c 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -1392,6 +1392,9 @@ count_single_write_clusters(BlockDriverState *bs, int nb_clusters,
* the same cluster. In this case we need to wait until the previous
* request has completed and updated the L2 table accordingly.
*
+ * If allow_shortening == true, instead of waiting for a dependency, *cur_bytes
+ * can be shortened so that the cluster allocations don't overlap.
+ *
* Returns:
* 0 if there was no dependency. *cur_bytes indicates the number of
* bytes from guest_offset that can be read before the next
@@ -1403,7 +1406,9 @@ count_single_write_clusters(BlockDriverState *bs, int nb_clusters,
*/
static int coroutine_fn handle_dependencies(BlockDriverState *bs,
uint64_t guest_offset,
- uint64_t *cur_bytes, QCowL2Meta **m)
+ uint64_t *cur_bytes,
+ bool allow_shortening,
+ QCowL2Meta **m)
{
BDRVQcow2State *s = bs->opaque;
QCowL2Meta *old_alloc;
@@ -1434,7 +1439,7 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
/* Conflict */
- if (start < old_start) {
+ if (start < old_start && allow_shortening) {
/* Stop at the start of a running allocation */
bytes = old_start - start;
} else {
@@ -1469,6 +1474,29 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
return 0;
}
+static void coroutine_mixed_fn wait_for_dependencies(BlockDriverState *bs,
+ uint64_t guest_offset,
+ uint64_t bytes)
+{
+ BDRVQcow2State *s = bs->opaque;
+ QCowL2Meta *m = NULL;
+ int ret;
+
+ /*
+ * Discard has some non-coroutine callers (creating internal snapshots and
+ * make empty). They are calling from qemu-img or in a drained section, so
+ * we know that no writes can be in progress.
+ */
+ if (!qemu_in_coroutine()) {
+ assert(QLIST_EMPTY(&s->cluster_allocs));
+ return;
+ }
+
+ do {
+ ret = handle_dependencies(bs, guest_offset, &bytes, false, &m);
+ } while (ret == -EAGAIN);
+}
+
/*
* Checks how many already allocated clusters that don't require a new
* allocation there are at the given guest_offset (up to *bytes).
@@ -1840,7 +1868,7 @@ again:
* the right synchronisation between the in-flight request and
* the new one.
*/
- ret = handle_dependencies(bs, start, &cur_bytes, m);
+ ret = handle_dependencies(bs, start, &cur_bytes, true, m);
if (ret == -EAGAIN) {
/* Currently handle_dependencies() doesn't yield if we already had
* an allocation. If it did, we would have to clean up the L2Meta
@@ -2002,6 +2030,15 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
int64_t cleared;
int ret;
+ /*
+ * If we're touching a cluster for which allocating writes are in flight,
+ * wait for them to complete to avoid conflicting metadata updates.
+ *
+ * We don't need to allocate a QCowL2Meta for the discard operation because
+ * s->lock is held for the duration of the whole operation.
+ */
+ wait_for_dependencies(bs, offset, bytes);
+
/* Caller must pass aligned values, except at image end */
assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
assert(QEMU_IS_ALIGNED(end_offset, s->cluster_size) ||
@@ -2164,6 +2201,15 @@ int coroutine_fn qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset,
int64_t cleared;
int ret;
+ /*
+ * If we're touching a cluster for which allocating writes are in flight,
+ * wait for them to complete to avoid conflicting metadata updates.
+ *
+ * We don't need to allocate a QCowL2Meta for the zeroize operation because
+ * s->lock is held for the duration of the whole operation.
+ */
+ wait_for_dependencies(bs, offset, bytes);
+
/* If we have to stay in sync with an external data file, zero out
* s->data_file first. */
if (data_file_is_raw(bs)) {
--
2.52.0

View File

@ -0,0 +1,205 @@
From aff7b26c2a2ad0c78e403a572a5cca52e1f2774e Mon Sep 17 00:00:00 2001
From: Thomas Lamprecht <t.lamprecht@proxmox.com>
Date: Sat, 13 Jun 2026 23:03:39 +0300
Subject: [PATCH 51/52] qcow2: Fix data loss on zero write with
detect-zeroes=unmap
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [44/45] 0d2cddb6cdde22f5842aeca19fcab691230f50c8 (kmwolf/centos-qemu-kvm)
Commit b8bfb1478d ("qcow2: Fix corruption on discard during write with
COW") added a wait_for_dependencies() at the start of
qcow2_subcluster_zeroize(). That fixes the inconsistency it set out to
fix, but turns the lock-protected pre-check in the caller,
qcow2_co_pwrite_zeroes(), into a stale one: the wait yields s->lock,
so an in-flight allocating write whose QCowL2Meta is already on
s->cluster_allocs (but whose L2 entry is not yet linked) gets to link
its entry during the yield. When the zeroize wakes, the cluster is now
NORMAL, and with BDRV_REQ_MAY_UNMAP the free path in zero_in_l2_slice()
unmaps the just-written cluster, silently dropping the data write's
payload.
This is reachable with detect-zeroes=unmap (the default for VirtIO
disks with discard on in Proxmox VE), under which the block layer
auto-promotes all-zero buffers to BDRV_REQ_ZERO_WRITE |
BDRV_REQ_MAY_UNMAP. A memory-constrained Debian guest running 'apt
full-upgrade' on such a disk reproduces it as random SIGSEGVs:
swapped-out code pages come back as zero.
Wait for in-flight dependencies before the lock-protected check in
qcow2_co_pwrite_zeroes(). If a write linked its L2 entry during the
wait, the type check now fails and the block layer falls back to a
bounce-buffered zero write that only touches the requested subrange,
preserving the racing write's data. Promote wait_for_dependencies() to
qcow2_wait_for_dependencies() so qcow2.c can call it.
Fixes: b8bfb1478d ("qcow2: Fix corruption on discard during write with COW")
Fixes: d85e00dd03 ("qcow2: Fix corruption on discard during write with COW") in 10.2.x series
Cc: qemu-stable@nongnu.org
Tested-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Message-ID: <20260522151318.238064-1-t.lamprecht@proxmox.com>
[kwolf: Reverted unnecessary change to 'nr' assignment]
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 1d47eb68983577a4e06fe1c165d90e128b191b86)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-56-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/qcow2-cluster.c | 10 +++++-----
block/qcow2.c | 8 +++++++-
block/qcow2.h | 4 ++++
tests/qemu-iotests/046 | 23 +++++++++++++++++++++++
tests/qemu-iotests/046.out | 10 ++++++++++
5 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index c20011d34c..23eeb9fc56 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -1474,9 +1474,9 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
return 0;
}
-static void coroutine_mixed_fn wait_for_dependencies(BlockDriverState *bs,
- uint64_t guest_offset,
- uint64_t bytes)
+void coroutine_mixed_fn qcow2_wait_for_dependencies(BlockDriverState *bs,
+ uint64_t guest_offset,
+ uint64_t bytes)
{
BDRVQcow2State *s = bs->opaque;
QCowL2Meta *m = NULL;
@@ -2037,7 +2037,7 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
* We don't need to allocate a QCowL2Meta for the discard operation because
* s->lock is held for the duration of the whole operation.
*/
- wait_for_dependencies(bs, offset, bytes);
+ qcow2_wait_for_dependencies(bs, offset, bytes);
/* Caller must pass aligned values, except at image end */
assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
@@ -2208,7 +2208,7 @@ int coroutine_fn qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset,
* We don't need to allocate a QCowL2Meta for the zeroize operation because
* s->lock is held for the duration of the whole operation.
*/
- wait_for_dependencies(bs, offset, bytes);
+ qcow2_wait_for_dependencies(bs, offset, bytes);
/* If we have to stay in sync with an external data file, zero out
* s->data_file first. */
diff --git a/block/qcow2.c b/block/qcow2.c
index a6adfa9f84..0f9818528d 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -4183,10 +4183,16 @@ qcow2_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes,
}
qemu_co_mutex_lock(&s->lock);
- /* We can have new write after previous check */
offset -= head;
bytes = s->subcluster_size;
nr = s->subcluster_size;
+ /*
+ * Wait for in-flight allocating writes first: otherwise the type
+ * check below could pass on UNALLOCATED while a yet-to-link_l2 write
+ * completes during qcow2_subcluster_zeroize()'s own wait, letting the
+ * resumed MAY_UNMAP discard the just-written data.
+ */
+ qcow2_wait_for_dependencies(bs, offset, bytes);
ret = qcow2_get_host_offset(bs, offset, &nr, &off, &type);
if (ret < 0 ||
(type != QCOW2_SUBCLUSTER_UNALLOCATED_PLAIN &&
diff --git a/block/qcow2.h b/block/qcow2.h
index 3e38bccd87..26fe12ab2a 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -961,6 +961,10 @@ int coroutine_fn GRAPH_RDLOCK
qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
int flags);
+void coroutine_mixed_fn
+qcow2_wait_for_dependencies(BlockDriverState *bs, uint64_t guest_offset,
+ uint64_t bytes);
+
int GRAPH_RDLOCK
qcow2_expand_zero_clusters(BlockDriverState *bs,
BlockDriverAmendStatusCB *status_cb,
diff --git a/tests/qemu-iotests/046 b/tests/qemu-iotests/046
index e03dd40147..0d84b5c1c7 100755
--- a/tests/qemu-iotests/046
+++ b/tests/qemu-iotests/046
@@ -226,6 +226,26 @@ aio_write -z 0x140000 0x10000
resume A
aio_flush
EOF
+
+# Start an allocating write to a previously unallocated cluster and, before
+# its L2 update is linked, issue a concurrent sub-cluster zero write with
+# MAY_UNMAP that targets a disjoint range within the same cluster. The zero
+# write's head/tail are zero (cluster is unallocated), so qcow2_co_pwrite_zeroes
+# would expand it to the full subcluster. Without waiting for dependencies
+# before the zero write's "unallocated" type check, that check passes,
+# qcow2_subcluster_zeroize then yields in wait_for_dependencies, the allocating
+# write links its L2 entry, and the resumed zeroize unmaps the cluster -
+# silently discarding the just-written data. Waiting first makes the zero write
+# fall back to a bounce-buffered real write, which only touches its own
+# subrange.
+cat <<EOF
+break write_aio A
+aio_write -P 180 0x200000 0x4000
+wait_break A
+aio_write -z -u 0x204000 0x4000
+resume A
+aio_flush
+EOF
}
overlay_io | $QEMU_IO blkdebug::"$TEST_IMG" | _filter_qemu_io |\
@@ -310,6 +330,9 @@ verify_io()
echo read -P 0 0x120000 0x10000
echo read -P 0 0x130000 0x10000
echo read -P 0 0x140000 0x10000
+
+ echo read -P 180 0x200000 0x4000
+ echo read -P 0 0x204000 0xc000
}
verify_io | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
diff --git a/tests/qemu-iotests/046.out b/tests/qemu-iotests/046.out
index 6341df335c..137cf527f1 100644
--- a/tests/qemu-iotests/046.out
+++ b/tests/qemu-iotests/046.out
@@ -169,6 +169,12 @@ wrote XXX/XXX bytes at offset XXX
XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
wrote XXX/XXX bytes at offset XXX
XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+blkdebug: Suspended request 'A'
+blkdebug: Resuming request 'A'
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote XXX/XXX bytes at offset XXX
+XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== Verify image content ==
read 65536/65536 bytes at offset 0
@@ -275,5 +281,9 @@ read 65536/65536 bytes at offset 1245184
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 65536/65536 bytes at offset 1310720
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 16384/16384 bytes at offset 2097152
+16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 49152/49152 bytes at offset 2113536
+48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
*** done
--
2.52.0

View File

@ -0,0 +1,160 @@
From 4ed80433ccb6c0009a23dd71159af596badec426 Mon Sep 17 00:00:00 2001
From: Fabiano Rosas <farosas@suse.de>
Date: Sat, 13 Jun 2026 23:03:40 +0300
Subject: [PATCH 18/52] qed: Don't try to flush during incoming migration
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [11/45] 2a884395bec8d06c70656664d87fe03e6e7ba902 (kmwolf/centos-qemu-kvm)
It's not possible to access the image file while there is an incoming
migration in progress, the QEMU process doesn't hold any locks to the
storage at this point so nodes are inactive. Attempting to flush leads
to an assert at bdrv_co_write_req_prepare():
assert(!(bs->open_flags & BDRV_O_INACTIVE))
The issue is reproducible by running iotest 181 on a host under cpu
load. The migration must coincide with the header already containing
the QED_F_NEED_CHECK flag.
The sequence of events is as follows, with the respective call stacks
referenced below:
During block device init, bdrv_qed_attach_aio_context() starts the
'need_check' timer. The timer will not fire during incoming migration
as it uses QEMU_CLOCK_VIRTUAL (to avoid this very issue, as the code
comment indicates). (0)
However, there's still bdrv_qed_drain_begin() which uses the fact that
the timer is live to decide whether to start the
qed_need_check_timer_entry() directly. (1)
The qed_need_check_timer_entry() eventually calls into
qed_write_header() -> bdrv_co_pwrite() leading to the assert. (2)
Skip creating the 'need_check' timer whenever the image is inactive.
The stacks:
(0) == issues timer_mod ==
#6 in qed_start_need_check_timer at ../block/qed.c:340
#7 in bdrv_qed_attach_aio_context at ../block/qed.c:373
#8 in bdrv_qed_do_open at ../block/qed.c:556
#9 in bdrv_qed_open_entry at ../block/qed.c:582
#10 in coroutine_trampoline at ../util/coroutine-ucontext.c:175
#0 in qemu_coroutine_switch<+120> at ../util/coroutine-ucontext.c:321
#1 in qemu_aio_coroutine_enter<+356> at ../util/qemu-coroutine.c:293
#2 in aio_co_enter<+179> at ../util/async.c:710
#3 in aio_co_wake<+53> at ../util/async.c:695
#4 in thread_pool_co_cb<+47> at ../util/thread-pool.c:283
#5 in thread_pool_completion_bh<+241> at ../util/thread-pool.c:202
#6 in aio_bh_call<+109> at ../util/async.c:173
#7 in aio_bh_poll<+299> at ../util/async.c:220
#8 in aio_poll<+690> at ../util/aio-posix.c:745
#9 in bdrv_qed_open<+392> at ../block/qed.c:607
#10 in bdrv_open_driver<+327> at ../block.c:1678
#11 in bdrv_open_common<+1619> at ../block.c:2008
#12 in bdrv_open_inherit<+2556> at ../block.c:4191
#13 in bdrv_open<+118> at ../block.c:4286
#14 in blk_new_open<+199> at ../block/block-backend.c:458
#15 in blockdev_init<+2011> at ../blockdev.c:612
#16 in drive_new<+3008> at ../blockdev.c:1008
#17 in drive_init_func<+51> at ../system/vl.c:662
#18 in qemu_opts_foreach<+227> at ../util/qemu-option.c:1148
#19 in configure_blockdev<+350> at ../system/vl.c:721
#20 in qemu_create_early_backends<+343> at ../system/vl.c:2076
#21 in qemu_init<+12483> at ../system/vl.c:3778
#22 in main<+46> at ../system/main.c:71
(1) == sees timer_pending ==
#6 in bdrv_qed_drain_begin at ../block/qed.c:391
#7 in bdrv_do_drained_begin at ../block/io.c:366
#8 in bdrv_do_drained_begin_quiesce at ../block/io.c:386
#9 in bdrv_child_cb_drained_begin at ../block.c:1207
#10 in bdrv_parent_drained_begin_single at ../block/io.c:133
#11 in bdrv_parent_drained_begin at ../block/io.c:64
#12 in bdrv_do_drained_begin at ../block/io.c:364
#13 in bdrv_drained_begin at ../block/io.c:393
#14 in blk_drain at ../block/block-backend.c:2101
#15 in blk_unref at ../block/block-backend.c:544
#16 in bdrv_open_inherit at ../block.c:4197
#17 in bdrv_open at ../block.c:4286
#18 in blk_new_open at ../block/block-backend.c:458
#19 in blockdev_init at ../blockdev.c:612
#20 in drive_new at ../blockdev.c:1008
#21 in drive_init_func at ../system/vl.c:662
#22 in qemu_opts_foreach at ../util/qemu-option.c:1148
#23 in configure_blockdev at ../system/vl.c:721
#24 in qemu_create_early_backends at ../system/vl.c:2076
#25 in qemu_init at ../system/vl.c:3778
#26 in main at ../system/main.c:71
(2) == crashes ==
#5 in __assert_fail (assertion="!(bs->open_flags & BDRV_O_INACTIVE)", file="../block/io.c", line=1977
#6 in bdrv_co_write_req_prepare at ../block/io.c:1977
#7 in bdrv_aligned_pwritev at ../block/io.c:2099
#8 in bdrv_co_pwritev_part at ../block/io.c:2316
#9 in bdrv_co_pwritev at ../block/io.c:2233
#10 in bdrv_co_pwrite at ../include/block/block_int-io.h:77
#11 in qed_write_header at ../block/qed.c:128
#12 in qed_need_check_timer at ../block/qed.c:305
#13 in qed_need_check_timer_entry at ../block/qed.c:319
Note that this issue is not exactly the same as what's been reported
in Gitlab, but given how easily this reproduces, I imagine it has to
be happening in that setup as well.
Link: https://gitlab.com/qemu-project/qemu/-/work_items/3515
Signed-off-by: Fabiano Rosas <farosas@suse.de>
Message-ID: <20260603193813.2327596-1-farosas@suse.de>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 7e573b660fefdebd21cb755d0d34bb5942fd3af3)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-57-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/qed.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/block/qed.c b/block/qed.c
index 4a36fb3929..917b8d91b5 100644
--- a/block/qed.c
+++ b/block/qed.c
@@ -351,16 +351,22 @@ static void bdrv_qed_detach_aio_context(BlockDriverState *bs)
{
BDRVQEDState *s = bs->opaque;
- qed_cancel_need_check_timer(s);
- timer_free(s->need_check_timer);
- s->need_check_timer = NULL;
+ if (s->need_check_timer) {
+ qed_cancel_need_check_timer(s);
+ timer_free(s->need_check_timer);
+ s->need_check_timer = NULL;
+ }
}
-static void bdrv_qed_attach_aio_context(BlockDriverState *bs,
- AioContext *new_context)
+static void GRAPH_RDLOCK bdrv_qed_attach_aio_context(BlockDriverState *bs,
+ AioContext *new_context)
{
BDRVQEDState *s = bs->opaque;
+ if (bdrv_is_inactive(bs)) {
+ return;
+ }
+
s->need_check_timer = aio_timer_new(new_context,
QEMU_CLOCK_VIRTUAL, SCALE_NS,
qed_need_check_timer_cb, s);
--
2.52.0

View File

@ -0,0 +1,49 @@
From a0454a8af25172f3f1f0f6eacc3e07d9c2b077fe Mon Sep 17 00:00:00 2001
From: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Date: Thu, 23 Oct 2025 17:10:58 +0900
Subject: [PATCH 22/52] qemu-img: Fix amend option parse error handling
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [15/45] 07da070d4ecdcb1ad22761fe68038b85dc0d0a42 (kmwolf/centos-qemu-kvm)
qemu_opts_del(opts) dereferences opts->list, which is the old amend_opts
pointer that can be dangling after executing
qemu_opts_append(amend_opts, bs->drv->create_opts) and cause
use-after-free.
Fix the potential use-after-free by moving the qemu_opts_del() call
before the qemu_opts_append() call.
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Message-ID: <20251023-iotests-v1-1-fab143ca4c2f@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit f00bcc833790c72c08bc5eed97845fdaa7542507)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
qemu-img.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qemu-img.c b/qemu-img.c
index a7791896c1..7a32d2d16c 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -4597,9 +4597,9 @@ static int img_amend(const img_cmd_t *ccmd, int argc, char **argv)
amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
if (!qemu_opts_do_parse(opts, options, NULL, &err)) {
+ qemu_opts_del(opts);
/* Try to parse options using the create options */
amend_opts = qemu_opts_append(amend_opts, bs->drv->create_opts);
- qemu_opts_del(opts);
opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
if (qemu_opts_do_parse(opts, options, NULL, NULL)) {
error_append_hint(&err,
--
2.52.0

View File

@ -0,0 +1,161 @@
From 21f29bc69a9ce910c6869453a48665087fddfa29 Mon Sep 17 00:00:00 2001
From: Alberto Garcia <berto@igalia.com>
Date: Fri, 7 Nov 2025 10:18:30 +0100
Subject: [PATCH 23/52] qemu-img rebase: don't exceed IO_BUF_SIZE in one
operation
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [16/45] c0b0720c3a75eeb7a1519e1df29ffb9a59adc531 (kmwolf/centos-qemu-kvm)
During a rebase operation data is copied from the backing chain into
the target image using a loop, and each iteration looks for a
contiguous region of allocated data of at most IO_BUF_SIZE (2 MB).
Once that region is found, and in order to avoid partial writes, its
boundaries are extended so they are aligned to the (sub)clusters of
the target image (see commit 12df580b).
This operation can however result in a region that exceeds the maximum
allowed IO_BUF_SIZE, crashing qemu-img.
This can be easily reproduced when the source image has a smaller
cluster size than the target image:
base <- int <- active
$ qemu-img create -f qcow2 base.qcow2 4M
$ qemu-img create -f qcow2 -F qcow2 -b base.qcow2 -o cluster_size=1M int.qcow2
$ qemu-img create -f qcow2 -F qcow2 -b int.qcow2 -o cluster_size=2M active.qcow2
$ qemu-io -c "write -P 0xff 1M 2M" int.qcow2
$ qemu-img rebase -F qcow2 -b base.qcow2 active.qcow2
qemu-img: qemu-img.c:4102: img_rebase: Assertion `written + pnum <= IO_BUF_SIZE' failed.
Aborted
Cc: qemu-stable <qemu-stable@nongnu.org>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3174
Fixes: 12df580b3b7f ("qemu-img: rebase: avoid unnecessary COW operations")
Signed-off-by: Alberto Garcia <berto@igalia.com>
Message-ID: <20251107091834.383781-1-berto@igalia.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 909852ba6b4a22fd2b6f9d8b88adb5fc47dfa781)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
qemu-img.c | 2 +-
tests/qemu-iotests/024 | 46 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/024.out | 26 +++++++++++++++++++++
3 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/qemu-img.c b/qemu-img.c
index 7a32d2d16c..c42dd4e995 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -4081,7 +4081,7 @@ static int img_rebase(const img_cmd_t *ccmd, int argc, char **argv)
n += offset - QEMU_ALIGN_DOWN(offset, write_align);
offset = QEMU_ALIGN_DOWN(offset, write_align);
n += QEMU_ALIGN_UP(offset + n, write_align) - (offset + n);
- n = MIN(n, size - offset);
+ n = MIN(n, MIN(size - offset, IO_BUF_SIZE));
assert(!bdrv_is_allocated(unfiltered_bs, offset, n, &n_alloc) &&
n_alloc == n);
diff --git a/tests/qemu-iotests/024 b/tests/qemu-iotests/024
index b29c76e161..021169b4a1 100755
--- a/tests/qemu-iotests/024
+++ b/tests/qemu-iotests/024
@@ -315,6 +315,52 @@ echo
$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
+# Check that the region to copy to the overlay during a rebase
+# operation does not exceed the I/O buffer size.
+#
+# backing_new <-- backing_old <-- overlay
+#
+# Backing (new): -- -- -- -- <-- Empty image, size 4MB
+# Backing (old):|--|ff|ff|--| <-- 4 clusters, 1MB each
+# Overlay: |-- --|-- --| <-- 2 clusters, 2MB each
+#
+# The data at [1MB, 3MB) must be copied from the old backing image to
+# the overlay. However the rebase code will extend that region to the
+# overlay's (sub)cluster boundaries to avoid CoW (see commit 12df580b).
+# This test checks that IO_BUF_SIZE (2 MB) is taken into account.
+
+echo
+echo "=== Test that the region to copy does not exceed 2MB (IO_BUF_SIZE) ==="
+echo
+
+echo "Creating backing chain"
+echo
+
+TEST_IMG=$BASE_NEW _make_test_img 4M
+TEST_IMG=$BASE_OLD CLUSTER_SIZE=1M _make_test_img -b "$BASE_NEW" -F $IMGFMT
+TEST_IMG=$OVERLAY CLUSTER_SIZE=2M _make_test_img -b "$BASE_OLD" -F $IMGFMT
+
+echo
+echo "Writing data to region [1MB, 3MB)"
+echo
+
+$QEMU_IO "$BASE_OLD" -c "write -P 0xff 1M 2M" | _filter_qemu_io
+
+echo
+echo "Rebasing"
+echo
+
+$QEMU_IMG rebase -b "$BASE_NEW" -F $IMGFMT "$OVERLAY"
+
+echo "Verifying the data"
+echo
+
+$QEMU_IO "$OVERLAY" -c "read -P 0x00 0 1M" | _filter_qemu_io
+$QEMU_IO "$OVERLAY" -c "read -P 0xff 1M 2M" | _filter_qemu_io
+$QEMU_IO "$OVERLAY" -c "read -P 0x00 3M 1M" | _filter_qemu_io
+
+$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
+
echo
# success, all done
diff --git a/tests/qemu-iotests/024.out b/tests/qemu-iotests/024.out
index 3d1e31927a..1b7522ba71 100644
--- a/tests/qemu-iotests/024.out
+++ b/tests/qemu-iotests/024.out
@@ -243,4 +243,30 @@ Offset Length File
0 0x20000 TEST_DIR/subdir/t.IMGFMT
0x40000 0x20000 TEST_DIR/subdir/t.IMGFMT
+=== Test that the region to copy does not exceed 2MB (IO_BUF_SIZE) ===
+
+Creating backing chain
+
+Formatting 'TEST_DIR/subdir/t.IMGFMT.base_new', fmt=IMGFMT size=4194304
+Formatting 'TEST_DIR/subdir/t.IMGFMT.base_old', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/subdir/t.IMGFMT.base_new backing_fmt=IMGFMT
+Formatting 'TEST_DIR/subdir/t.IMGFMT', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/subdir/t.IMGFMT.base_old backing_fmt=IMGFMT
+
+Writing data to region [1MB, 3MB)
+
+wrote 2097152/2097152 bytes at offset 1048576
+2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+Rebasing
+
+Verifying the data
+
+read 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 2097152/2097152 bytes at offset 1048576
+2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+Offset Length File
+0 0x400000 TEST_DIR/subdir/t.IMGFMT
+
*** done
--
2.52.0

View File

@ -0,0 +1,167 @@
From f598b8e008e2b9179acaa138d75bac390a204508 Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Sat, 13 Jun 2026 23:03:36 +0300
Subject: [PATCH 48/52] qemu-io: Add 'aio_discard' command
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [41/45] f10ea407dd2cb93550396230790802dc8a728774 (kmwolf/centos-qemu-kvm)
Testing interactions between multiple requests that include discard
requests require that qemu-io can do the discard asynchronously, like it
already does for reads and writes. To this effect, add an 'aio_discard'
command.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260427170520.101242-3-kwolf@redhat.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Tested-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 7f8466e2ce620e3c6a6e2f32d616367174d4dbe9)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Message-ID: <20260613200411.1808021-53-mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
qemu-io-cmds.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 115 insertions(+)
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index f6d077908f..de4c1966fe 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -2218,6 +2218,120 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
return 0;
}
+static void aio_discard_help(void)
+{
+ printf(
+"\n"
+" asynchronously discards a range of bytes from the given offset\n"
+"\n"
+" Example:\n"
+" 'aio_discard 512 1k' - discards 1 kilobyte from 512 bytes into the file\n"
+"\n"
+" Discards a segment of the currently open file.\n"
+" -C, -- report statistics in a machine parsable format\n"
+" -q, -- quiet mode, do not show I/O statistics\n"
+" The discard is performed asynchronously and the aio_flush command must be\n"
+" used to ensure all outstanding aio requests have been completed.\n"
+" Note that due to its asynchronous nature, this command will be\n"
+" considered successful once the request is submitted, independently\n"
+" of potential I/O errors.\n"
+"\n");
+}
+
+static int aio_discard_f(BlockBackend *blk, int argc, char **argv);
+
+static const cmdinfo_t aio_discard_cmd = {
+ .name = "aio_discard",
+ .cfunc = aio_discard_f,
+ .perm = BLK_PERM_WRITE,
+ .argmin = 2,
+ .argmax = -1,
+ .args = "[-Cq] off len",
+ .oneline = "asynchronously discards a number of bytes",
+ .help = aio_discard_help,
+};
+
+static void aio_discard_done(void *opaque, int ret)
+{
+ struct aio_ctx *ctx = opaque;
+ struct timespec t2;
+
+ clock_gettime(CLOCK_MONOTONIC, &t2);
+
+ if (ret < 0) {
+ printf("aio_discard failed: %s\n", strerror(-ret));
+ block_acct_failed(blk_get_stats(ctx->blk), &ctx->acct);
+ goto out;
+ }
+
+ block_acct_done(blk_get_stats(ctx->blk), &ctx->acct);
+
+ if (ctx->qflag) {
+ goto out;
+ }
+
+ /* Finally, report back -- -C gives a parsable format */
+ t2 = tsub(t2, ctx->t1);
+ print_report("discarded ", &t2, ctx->offset, ctx->qiov.size,
+ ctx->qiov.size, 1, ctx->Cflag);
+out:
+ g_free(ctx);
+}
+
+static int aio_discard_f(BlockBackend *blk, int argc, char **argv)
+{
+ int c, ret;
+ int64_t count;
+ struct aio_ctx *ctx = g_new0(struct aio_ctx, 1);
+
+ ctx->blk = blk;
+
+ while ((c = getopt(argc, argv, "Cq")) != -1) {
+ switch (c) {
+ case 'C':
+ ctx->Cflag = true;
+ break;
+ case 'q':
+ ctx->qflag = true;
+ break;
+ default:
+ g_free(ctx);
+ qemuio_command_usage(&aio_discard_cmd);
+ return -EINVAL;
+ }
+ }
+
+ if (optind != argc - 2) {
+ g_free(ctx);
+ qemuio_command_usage(&aio_discard_cmd);
+ return -EINVAL;
+ }
+
+ ctx->offset = cvtnum(argv[optind]);
+ if (ctx->offset < 0) {
+ ret = ctx->offset;
+ print_cvtnum_err(ret, argv[optind]);
+ g_free(ctx);
+ return ret;
+ }
+ optind++;
+
+ count = cvtnum(argv[optind]);
+ if (count < 0) {
+ print_cvtnum_err(count, argv[optind]);
+ g_free(ctx);
+ return count;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &ctx->t1);
+ ctx->qiov.size = count;
+ block_acct_start(blk_get_stats(blk), &ctx->acct, ctx->qiov.size,
+ BLOCK_ACCT_UNMAP);
+ blk_aio_pdiscard(blk, ctx->offset, count, aio_discard_done, ctx);
+
+ return 0;
+}
+
static int alloc_f(BlockBackend *blk, int argc, char **argv)
{
BlockDriverState *bs = blk_bs(blk);
@@ -2800,6 +2914,7 @@ static void __attribute((constructor)) init_qemuio_commands(void)
qemuio_add_command(&length_cmd);
qemuio_add_command(&info_cmd);
qemuio_add_command(&discard_cmd);
+ qemuio_add_command(&aio_discard_cmd);
qemuio_add_command(&alloc_cmd);
qemuio_add_command(&map_cmd);
qemuio_add_command(&reopen_cmd);
--
2.52.0

View File

@ -0,0 +1,60 @@
From 327ef6e8b3ea224047409dea90edba5c59f174ad Mon Sep 17 00:00:00 2001
From: Alberto Garcia <berto@igalia.com>
Date: Wed, 12 Nov 2025 18:09:57 +0100
Subject: [PATCH 29/52] tests/qemu-iotest: fix iotest 024 with qed images
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [22/45] ec515379b04946e4730812abccc7d72ff6c7df26 (kmwolf/centos-qemu-kvm)
Use 'qemu-io -c map' instead of 'qemu-img map' to get an output that
works with both image types.
Cc: qemu-stable <qemu-stable@nongnu.org>
Fixes: 909852ba6b4a ("qemu-img rebase: don't exceed IO_BUF_SIZE in one operation")
Fixes: 26b17e34bedb ("qemu-img rebase: don't exceed IO_BUF_SIZE in one operation") in 10.1.x
Signed-off-by: Alberto Garcia <berto@igalia.com>
Message-ID: <20251112170959.700840-1-berto@igalia.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Tested-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
(cherry picked from commit 4c91719a6a78a1c24d8bb854f7594e767962d0d9)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/024 | 2 +-
tests/qemu-iotests/024.out | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/tests/qemu-iotests/024 b/tests/qemu-iotests/024
index 021169b4a1..10be2bd845 100755
--- a/tests/qemu-iotests/024
+++ b/tests/qemu-iotests/024
@@ -359,7 +359,7 @@ $QEMU_IO "$OVERLAY" -c "read -P 0x00 0 1M" | _filter_qemu_io
$QEMU_IO "$OVERLAY" -c "read -P 0xff 1M 2M" | _filter_qemu_io
$QEMU_IO "$OVERLAY" -c "read -P 0x00 3M 1M" | _filter_qemu_io
-$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
+$QEMU_IO -c map "$OVERLAY" | _filter_qemu_io
echo
diff --git a/tests/qemu-iotests/024.out b/tests/qemu-iotests/024.out
index 1b7522ba71..da8fedc08b 100644
--- a/tests/qemu-iotests/024.out
+++ b/tests/qemu-iotests/024.out
@@ -266,7 +266,6 @@ read 2097152/2097152 bytes at offset 1048576
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 1048576/1048576 bytes at offset 3145728
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-Offset Length File
-0 0x400000 TEST_DIR/subdir/t.IMGFMT
+4 MiB (0x400000) bytes allocated at offset 0 bytes (0x0)
*** done
--
2.52.0

View File

@ -0,0 +1,49 @@
From 588224f69f23d255c577ddc6fabcdb4288c420b0 Mon Sep 17 00:00:00 2001
From: Thomas Huth <thuth@redhat.com>
Date: Mon, 8 Dec 2025 08:53:20 +0100
Subject: [PATCH 30/52] tests/qemu-iotests: Fix check for existing file in
_require_disk_usage()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [23/45] e757b9e7b5fb75c2093d1a7a85332188dd4daa38 (kmwolf/centos-qemu-kvm)
Looks like the "$" has been forgotten here to get the contents of
the FILENAME variable.
Fixes: c49dda7254d ("iotests: Filter out ZFS in several tests")
Signed-off-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20251208075320.35682-1-thuth@redhat.com>
Reviewed-by: Laurent Vivier <laurent@vivier.eu>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit ab0065e36adf8becd9c1ffceec37ee809ce683af)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/common.rc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index e977cb4eb6..d8fe476806 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -1008,7 +1008,7 @@ _require_disk_usage()
else
FILENAME="$TEST_IMG_FILE"
fi
- if [ -e "FILENAME" ]; then
+ if [ -e "$FILENAME" ]; then
echo "unwilling to overwrite existing file"
exit 1
fi
--
2.52.0

View File

@ -0,0 +1,179 @@
From 8340b5280edd665c0b1bc6d11c28099252f92d8a Mon Sep 17 00:00:00 2001
From: Alberto Garcia <berto@igalia.com>
Date: Thu, 12 Mar 2026 13:12:00 +0100
Subject: [PATCH 21/52] throttle-group: Fix race condition in
throttle_group_restart_queue()
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [14/45] 695ff7204a816025291b74409abfdff6963075f4 (kmwolf/centos-qemu-kvm)
When a timer is fired a pending I/O request is restarted and
tg->any_timer_armed is reset so other requests can be scheduled.
However we're resetting any_timer_armed first in timer_cb() before
the request is actually restarted, and there's a window between both
moments in which another thread can arm the same timer, hitting an
assertion in throttle_group_restart_queue().
This can be solved by deferring the reset of tg->any_timer_armed to
the moment when the queue is actually restarted, which is protected by
tg->lock, preventing other threads from arming the timer before that.
In addition to that, throttle_group_restart_tgm() is also updated to
hold tg->lock while the timer is being inspected. Here we consider
three different scenarios:
- If the tgm has a timer set, fire it immediately
- If another tgm has a timer set, restart the queue anyway
- If there is no timer set in this group then simulate a timer that
fires immediately, by setting tg->any_timer_armed in order to
prevent other threads from arming a timer in the meantime.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3194
Signed-off-by: Alberto Garcia <berto@igalia.com>
Message-Id: <825598ef34ad384d936da19d634eda75598508f7.1773316842.git.berto@igalia.com>
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
(cherry picked from commit 9c8430f5d65144b85ad76433369288182a1c7baa)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
block/throttle-groups.c | 79 +++++++++++++++++++++++++++++++----------
1 file changed, 60 insertions(+), 19 deletions(-)
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index 5329ff1fdb..4b1b1944c2 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -391,6 +391,7 @@ void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm
typedef struct {
ThrottleGroupMember *tgm;
ThrottleDirection direction;
+ bool reset_timer_armed;
} RestartData;
static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
@@ -403,6 +404,9 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
bool empty_queue;
qemu_mutex_lock(&tg->lock);
+ if (data->reset_timer_armed) {
+ tg->any_timer_armed[direction] = false;
+ }
empty_queue = !throttle_group_co_restart_queue(tgm, direction);
/* If the request queue was empty then we have to take care of
@@ -419,18 +423,23 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
}
static void throttle_group_restart_queue(ThrottleGroupMember *tgm,
- ThrottleDirection direction)
+ ThrottleDirection direction,
+ bool reset_timer_armed)
{
Coroutine *co;
RestartData *rd = g_new0(RestartData, 1);
rd->tgm = tgm;
rd->direction = direction;
+ rd->reset_timer_armed = reset_timer_armed;
- /* This function is called when a timer is fired or when
- * throttle_group_restart_tgm() is called. Either way, there can
+ /* If reset_timer_armed is set then this means that this function
+ * was called when a timer was fired (either from timer_cb() or
+ * from throttle_group_restart_tgm()). In this case there can
* be no timer pending on this tgm at this point */
- assert(!timer_pending(tgm->throttle_timers.timers[direction]));
+ if (reset_timer_armed) {
+ assert(!timer_pending(tgm->throttle_timers.timers[direction]));
+ }
qatomic_inc(&tgm->restart_pending);
@@ -444,15 +453,50 @@ void throttle_group_restart_tgm(ThrottleGroupMember *tgm)
if (tgm->throttle_state) {
for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
- QEMUTimer *t = tgm->throttle_timers.timers[dir];
+ QEMUTimer *t;
+ ThrottleState *ts = tgm->throttle_state;
+ ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
+ bool reset_timer_armed;
+
+ /*
+ * This function restarts the tgm's queue immediately.
+ * This is used for example for callers to drain all requests.
+ * There are three different scenarios depending on whether
+ * a timer is armed for this tg and which tgm owns the timer.
+ */
+
+ qemu_mutex_lock(&tg->lock);
+
+ t = tgm->throttle_timers.timers[dir];
if (timer_pending(t)) {
- /* If there's a pending timer on this tgm, fire it now */
+ /*
+ * Case 1: this tgm has a pending timer.
+ * We can fire the timer immediately.
+ */
timer_del(t);
- timer_cb(tgm, dir);
+ reset_timer_armed = true;
+ } else if (tg->any_timer_armed[dir]) {
+ /*
+ * Case 2: another tgm has a pending timer.
+ * In this case we can still restart the queue but we
+ * have to leave any_timer_armed untouched so the
+ * other tgm's timer is not disrupted.
+ */
+ reset_timer_armed = false;
} else {
- /* Else run the next request from the queue manually */
- throttle_group_restart_queue(tgm, dir);
+ /*
+ * Case 3: there is no timer set for this group.
+ * Here we can simulate a timer that fires immediately,
+ * so the queue is restarted but no other thread
+ * can arm a timer in the meantime.
+ */
+ tg->any_timer_armed[dir] = true;
+ reset_timer_armed = true;
}
+
+ qemu_mutex_unlock(&tg->lock);
+
+ throttle_group_restart_queue(tgm, dir, reset_timer_armed);
}
}
}
@@ -499,16 +543,13 @@ void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
*/
static void timer_cb(ThrottleGroupMember *tgm, ThrottleDirection direction)
{
- ThrottleState *ts = tgm->throttle_state;
- ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
-
- /* The timer has just been fired, so we can update the flag */
- qemu_mutex_lock(&tg->lock);
- tg->any_timer_armed[direction] = false;
- qemu_mutex_unlock(&tg->lock);
-
- /* Run the request that was waiting for this timer */
- throttle_group_restart_queue(tgm, direction);
+ /*
+ * Run the request that was waiting for this timer.
+ * tg->any_timer_armed needs to be cleared, but we'll do it later
+ * when the queue is restarted in order to prevent another thread
+ * from arming the timer before that.
+ */
+ throttle_group_restart_queue(tgm, direction, true);
}
static void read_timer_cb(void *opaque)
--
2.52.0

View File

@ -0,0 +1,79 @@
From e9d0e7db0e7e90fb8a71e3725cdceba7a83b665e Mon Sep 17 00:00:00 2001
From: Kevin Wolf <kwolf@redhat.com>
Date: Thu, 4 Dec 2025 18:26:57 +0100
Subject: [PATCH 37/52] virtio: Fix crash when sriov-pf is set for
non-PCI-Express device
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [30/45] 3987bd2c53e0f4cb44d10ad4724a42ccacc4a705 (kmwolf/centos-qemu-kvm)
Setting the sriov-pf property on devices that aren't PCI Express causes
an assertion failure:
$ qemu-system-x86_64 \
-blockdev null-co,node-name=null \
-blockdev null-co,node-name=null2 \
-device virtio-blk,drive=null,id=pf \
-device virtio-blk,sriov-pf=pf,drive=null2
qemu-system-x86_64: ../hw/pci/pcie.c:1062: void pcie_add_capability(PCIDevice *, uint16_t, uint8_t, uint16_t, uint16_t): Assertion `offset >= PCI_CONFIG_SPACE_SIZE' failed.
This is because proxy->last_pcie_cap_offset is only initialised to a
non-zero value in virtio_pci_realize() if it's a PCI Express device, and
then virtio_pci_device_plugged() still tries to use it.
To fix this, just skip the SR-IOV code for !pci_is_express(). Then the
next thing pci_qdev_realize() does is call pcie_sriov_register_device(),
which returns the appropriate error.
Cc: qemu-stable@nongnu.org
Fixes: d0c280d3fac6 ('pcie_sriov: Make a PCI device with user-created VF ARI-capable')
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-Id: <20251204172657.174391-1-kwolf@redhat.com>
(cherry picked from commit 623db856476806124e9ae45fbc39e75012261570)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/virtio/virtio-pci.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 937e22f08a..044ecb4f41 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -2172,15 +2172,17 @@ static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar);
}
- if (pci_is_vf(&proxy->pci_dev)) {
- pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset);
- proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF;
- } else {
- res = pcie_sriov_pf_init_from_user_created_vfs(
- &proxy->pci_dev, proxy->last_pcie_cap_offset, errp);
- if (res > 0) {
- proxy->last_pcie_cap_offset += res;
- virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV);
+ if (pci_is_express(&proxy->pci_dev)) {
+ if (pci_is_vf(&proxy->pci_dev)) {
+ pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset);
+ proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF;
+ } else {
+ res = pcie_sriov_pf_init_from_user_created_vfs(
+ &proxy->pci_dev, proxy->last_pcie_cap_offset, errp);
+ if (res > 0) {
+ proxy->last_pcie_cap_offset += res;
+ virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV);
+ }
}
}
}
--
2.52.0

View File

@ -0,0 +1,300 @@
From 488b339be87be50b40851e85aa42aa76adbe1782 Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@redhat.com>
Date: Fri, 10 Apr 2026 08:11:28 -0400
Subject: [PATCH 40/52] virtio-blk: fix zone report buffer out-of-memory
(CVE-2026-5761)
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [33/45] 28556c36d10da652192eb20874ce4b129b0a2b85 (kmwolf/centos-qemu-kvm)
An internal buffer is used when processing VIRTIO_BLK_T_ZONE_REPORT
requests. The buffer's size is controlled by the guest. A large value
can result in g_malloc() failure and the QEMU process aborts, resulting
in a Denial of Service (DoS) (most likely in cases where an untrusted
guest application or a nested guest with virtio-blk passthrough is able
to abort QEMU).
Modify the zone report implementation to work incrementally with a
bounded buffer size.
This is purely a QEMU implementation issue and no VIRTIO spec changes
are needed.
Mingyuan Luo found this bug and provided a reproducer which I haven't
put into tests/qtest/ because it requires a zoned storage device (e.g.
root and modprobe null_blk):
1) Prepare a zoned nullblk backend (/dev/nullb0):
sudo modprobe -r null_blk || true
sudo modprobe null_blk nr_devices=1 zoned=1
sudo chmod 0666 /dev/nullb0
cat /sys/block/nullb0/queue/zoned
2) Create qtest input:
cat >/tmp/vblk-zone-report-oom.qtest <<'EOF'
outl 0xcf8 0x80002004
outw 0xcfc 0x0007
outl 0xcf8 0x80002010
outl 0xcfc 0x0000c001
outb 0xc012 0x00
outb 0xc012 0x01
outb 0xc012 0x03
outl 0xc004 0x00000000
outw 0xc00e 0x0000
outl 0xc008 0x00000100
outb 0xc012 0x07
writel 0x00020000 0x00000010
writel 0x00020004 0x00000000
writeq 0x00020008 0x0000000000000000
writeq 0x00100000 0x0000000000020000
writel 0x00100008 0x00000010
writew 0x0010000c 0x0001
writew 0x0010000e 0x0001
EOF
for i in $(seq 1 1022); do
d=$((0x00100000 + i * 16))
n=$((i + 1))
printf 'writeq 0x%08x 0x0000000000200000\n' "$d" >> /tmp/vblk-zone-report-oom.qtest
printf 'writel 0x%08x 0x1fe00000\n' $((d + 8)) >> /tmp/vblk-zone-report-oom.qtest
printf 'writew 0x%08x 0x0003\n' $((d + 12)) >> /tmp/vblk-zone-report-oom.qtest
printf 'writew 0x%08x 0x%04x\n' $((d + 14)) "$n" >> /tmp/vblk-zone-report-oom.qtest
done
d=$((0x00100000 + 1023 * 16))
printf 'writeq 0x%08x 0x0000000000200000\n' "$d" >> /tmp/vblk-zone-report-oom.qtest
printf 'writel 0x%08x 0x1fe00000\n' $((d + 8)) >> /tmp/vblk-zone-report-oom.qtest
printf 'writew 0x%08x 0x0002\n' $((d + 12)) >> /tmp/vblk-zone-report-oom.qtest
printf 'writew 0x%08x 0x0000\n' $((d + 14)) >> /tmp/vblk-zone-report-oom.qtest
cat >> /tmp/vblk-zone-report-oom.qtest <<'EOF'
writew 0x00104000 0x0000
writew 0x00104002 0x0001
writew 0x00104004 0x0000
outw 0xc010 0x0000
EOF
3) Run the qtest input with ASAN build (compile qemu with --enable-asan):
build/qemu-system-x86_64 -display none \
-accel qtest -qtest stdio \
-machine pc -nodefaults -m 512M -monitor none -serial none \
-blockdev driver=host_device,node-name=disk0,filename=/dev/nullb0 \
-device virtio-blk-pci-transitional,drive=disk0,addr=04.0,queue-size=1024 \
< /tmp/vblk-zone-report-oom.qtest
Cc: Sam Li <faithilikerun@gmail.com>
Cc: Damien Le Moal <dlemoal@kernel.org>
Cc: Dmitry Fomichev <dmitry.fomichev@wdc.com>
Fixes: CVE-2026-5761
Fixes: 4f7366506a9 ("virtio-blk: add zoned storage emulation for zoned devices")
Reported-by: Mingyuan Luo <myluo24@m.fudan.edu.cn>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit 4913ae36f9796c55d434dcbfa6bdb9ebb3e5e4b1)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/block/virtio-blk.c | 100 ++++++++++++++++++++++++++++--------------
1 file changed, 67 insertions(+), 33 deletions(-)
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index a0678300b4..019b46fcf7 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -38,6 +38,9 @@
#include "hw/virtio/virtio-blk-common.h"
#include "qemu/coroutine.h"
+/* Internal buffer size limit for zone report */
+#define VIRTIO_BLK_MAX_ZONES_PER_BATCH 4096
+
static void virtio_blk_ioeventfd_attach(VirtIOBlock *s);
static void virtio_blk_init_request(VirtIOBlock *s, VirtQueue *vq,
@@ -457,15 +460,22 @@ err:
return err_status;
}
+typedef struct {
+ unsigned int total_nr_zones; /* max zones to fill in this request */
+ unsigned int nr_zones_done; /* how many zones have been filled in */
+ int64_t iov_offset; /* current byte position in in_iov[] */
+ int64_t offset; /* current zone report disk offset */
+ unsigned int nr_zones; /* for zone report calls */
+ unsigned int zones_per_batch; /* size of zone report buffer */
+ BlockZoneDescriptor *zones; /* zone report buffer */
+} ZoneReportData;
+
typedef struct ZoneCmdData {
VirtIOBlockReq *req;
struct iovec *in_iov;
unsigned in_num;
union {
- struct {
- unsigned int nr_zones;
- BlockZoneDescriptor *zones;
- } zone_report_data;
+ ZoneReportData zone_report_data;
struct {
int64_t offset;
} zone_append_data;
@@ -522,16 +532,15 @@ static bool check_zoned_request(VirtIOBlock *s, int64_t offset, int64_t len,
static void virtio_blk_zone_report_complete(void *opaque, int ret)
{
ZoneCmdData *data = opaque;
+ ZoneReportData *zrd = &data->zone_report_data;
VirtIOBlockReq *req = data->req;
VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
struct iovec *in_iov = data->in_iov;
unsigned in_num = data->in_num;
- int64_t zrp_size, n, j = 0;
- int64_t nz = data->zone_report_data.nr_zones;
+ int64_t n;
+ unsigned nz = zrd->nr_zones;
int8_t err_status = VIRTIO_BLK_S_OK;
- struct virtio_blk_zone_report zrp_hdr = (struct virtio_blk_zone_report) {
- .nr_zones = cpu_to_le64(nz),
- };
+ struct virtio_blk_zone_report zrp_hdr = {};
trace_virtio_blk_zone_report_complete(vdev, req, nz, ret);
if (ret) {
@@ -539,28 +548,18 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
goto out;
}
- zrp_size = sizeof(struct virtio_blk_zone_report)
- + sizeof(struct virtio_blk_zone_descriptor) * nz;
- n = iov_from_buf(in_iov, in_num, 0, &zrp_hdr, sizeof(zrp_hdr));
- if (n != sizeof(zrp_hdr)) {
- virtio_error(vdev, "Driver provided input buffer that is too small!");
- err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
- goto out;
- }
-
- for (size_t i = sizeof(zrp_hdr); i < zrp_size;
- i += sizeof(struct virtio_blk_zone_descriptor), ++j) {
+ for (unsigned j = 0; j < nz; j++) {
struct virtio_blk_zone_descriptor desc =
(struct virtio_blk_zone_descriptor) {
- .z_start = cpu_to_le64(data->zone_report_data.zones[j].start
+ .z_start = cpu_to_le64(zrd->zones[j].start
>> BDRV_SECTOR_BITS),
- .z_cap = cpu_to_le64(data->zone_report_data.zones[j].cap
+ .z_cap = cpu_to_le64(zrd->zones[j].cap
>> BDRV_SECTOR_BITS),
- .z_wp = cpu_to_le64(data->zone_report_data.zones[j].wp
+ .z_wp = cpu_to_le64(zrd->zones[j].wp
>> BDRV_SECTOR_BITS),
};
- switch (data->zone_report_data.zones[j].type) {
+ switch (zrd->zones[j].type) {
case BLK_ZT_CONV:
desc.z_type = VIRTIO_BLK_ZT_CONV;
break;
@@ -574,7 +573,7 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
g_assert_not_reached();
}
- switch (data->zone_report_data.zones[j].state) {
+ switch (zrd->zones[j].state) {
case BLK_ZS_RDONLY:
desc.z_state = VIRTIO_BLK_ZS_RDONLY;
break;
@@ -604,18 +603,47 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
}
/* TODO: it takes O(n^2) time complexity. Optimizations required. */
- n = iov_from_buf(in_iov, in_num, i, &desc, sizeof(desc));
+ n = iov_from_buf(in_iov, in_num, zrd->iov_offset, &desc, sizeof(desc));
if (n != sizeof(desc)) {
virtio_error(vdev, "Driver provided input buffer "
"for descriptors that is too small!");
err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
+ goto out;
}
+
+ zrd->iov_offset += sizeof(desc);
+ }
+
+ if (nz > 0) {
+ BlockZoneDescriptor *zone = &zrd->zones[nz - 1];
+ zrd->offset = zone->start + zone->length;
+ }
+
+ zrd->nr_zones_done += nz;
+
+ /* Call zone report again if the end hasn't been reached yet */
+ if (nz == zrd->zones_per_batch &&
+ zrd->nr_zones_done < zrd->total_nr_zones) {
+ zrd->nr_zones = MIN(zrd->zones_per_batch,
+ zrd->total_nr_zones - zrd->nr_zones_done);
+ blk_aio_zone_report(req->dev->blk, zrd->offset, &zrd->nr_zones,
+ zrd->zones, virtio_blk_zone_report_complete, data);
+ return;
+ }
+
+ /* Fill in header now that all zones have been reported */
+ zrp_hdr.nr_zones = cpu_to_le64(zrd->nr_zones_done);
+ n = iov_from_buf(in_iov, in_num, 0, &zrp_hdr, sizeof(zrp_hdr));
+ if (n != sizeof(zrp_hdr)) {
+ virtio_error(vdev, "Driver provided input buffer that is too small!");
+ err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
+ goto out;
}
out:
virtio_blk_req_complete(req, err_status);
g_free(req);
- g_free(data->zone_report_data.zones);
+ g_free(zrd->zones);
g_free(data);
}
@@ -627,7 +655,8 @@ static void virtio_blk_handle_zone_report(VirtIOBlockReq *req,
VirtIODevice *vdev = VIRTIO_DEVICE(s);
unsigned int nr_zones;
ZoneCmdData *data;
- int64_t zone_size, offset;
+ ZoneReportData *zrd;
+ int64_t offset;
uint8_t err_status;
if (req->in_len < sizeof(struct virtio_blk_inhdr) +
@@ -649,16 +678,21 @@ static void virtio_blk_handle_zone_report(VirtIOBlockReq *req,
trace_virtio_blk_handle_zone_report(vdev, req,
offset >> BDRV_SECTOR_BITS, nr_zones);
- zone_size = sizeof(BlockZoneDescriptor) * nr_zones;
data = g_malloc(sizeof(ZoneCmdData));
data->req = req;
data->in_iov = in_iov;
data->in_num = in_num;
- data->zone_report_data.nr_zones = nr_zones;
- data->zone_report_data.zones = g_malloc(zone_size),
- blk_aio_zone_report(s->blk, offset, &data->zone_report_data.nr_zones,
- data->zone_report_data.zones,
+ zrd = &data->zone_report_data;
+ zrd->total_nr_zones = nr_zones;
+ zrd->nr_zones_done = 0;
+ zrd->iov_offset = sizeof(struct virtio_blk_zone_report);
+ zrd->offset = offset;
+ zrd->zones_per_batch = MIN(nr_zones, VIRTIO_BLK_MAX_ZONES_PER_BATCH);
+ zrd->zones = g_malloc(zrd->zones_per_batch * sizeof(BlockZoneDescriptor));
+
+ zrd->nr_zones = zrd->zones_per_batch;
+ blk_aio_zone_report(s->blk, offset, &zrd->nr_zones, zrd->zones,
virtio_blk_zone_report_complete, data);
return;
out:
--
2.52.0

View File

@ -0,0 +1,131 @@
From c1a573c922a2c99060dfb63413cd47db2c784ca6 Mon Sep 17 00:00:00 2001
From: Paolo Bonzini <pbonzini@redhat.com>
Date: Fri, 27 Mar 2026 22:00:09 +0100
Subject: [PATCH 38/52] virtio-scsi: pass the same cdb_size to
virtio_scsi_pop_req and virtio_scsi_handle_cmd_req_prepare
RH-Author: Kevin Wolf <kwolf@redhat.com>
RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
RH-Jira: RHEL-186384
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [31/45] 6fe89d004c1a32f220b7104f5f7b2d9ee116d5f1 (kmwolf/centos-qemu-kvm)
Ensure that there is no allocation/usage mismatch when requests
are processed in virtio_scsi_handle_cmd_vq. To do this,
retrieve the value once and pass it to both functions.
For other calls to virtio_scsi_pop_req the extra size
can be 0, because control and event requests fit
entirely in VirtIOSCSIReq.
Reported-by: Jihe Wang <wangjihe.mail@gmail.com>
Tested-by: Jihe Wang <wangjihe.mail@gmail.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Fixes: CVE-2026-5763
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
(cherry picked from commit 79971302935472232a68073faddb085177e3ca54)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
hw/scsi/virtio-scsi.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
index 34ae14f7bf..0ce4718407 100644
--- a/hw/scsi/virtio-scsi.c
+++ b/hw/scsi/virtio-scsi.c
@@ -231,16 +231,16 @@ static int virtio_scsi_parse_req(VirtIOSCSIReq *req,
return 0;
}
-static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq, QemuMutex *vq_lock)
+static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq, size_t extra_req_size,
+ QemuMutex *vq_lock)
{
- VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
VirtIOSCSIReq *req;
if (vq_lock) {
qemu_mutex_lock(vq_lock);
}
- req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + vs->cdb_size);
+ req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + extra_req_size);
if (vq_lock) {
qemu_mutex_unlock(vq_lock);
@@ -674,7 +674,7 @@ static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq)
{
VirtIOSCSIReq *req;
- while ((req = virtio_scsi_pop_req(s, vq, &s->ctrl_lock))) {
+ while ((req = virtio_scsi_pop_req(s, vq, 0, &s->ctrl_lock))) {
virtio_scsi_handle_ctrl_req(s, req);
}
}
@@ -842,13 +842,14 @@ static void virtio_scsi_fail_cmd_req(VirtIOSCSIReq *req)
virtio_scsi_complete_cmd_req(req);
}
-static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
+static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req,
+ size_t cdb_size)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
SCSIDevice *d;
int rc;
- rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
+ rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + cdb_size,
sizeof(VirtIOSCSICmdResp) + vs->sense_size);
if (rc < 0) {
if (rc == -ENOTSUP) {
@@ -870,7 +871,7 @@ static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
}
req->sreq = scsi_req_new(d, req->req.cmd.tag,
virtio_scsi_get_lun(req->req.cmd.lun),
- req->req.cmd.cdb, vs->cdb_size, req);
+ req->req.cmd.cdb, cdb_size, req);
if (req->sreq->cmd.mode != SCSI_XFER_NONE
&& (req->sreq->cmd.mode != req->mode ||
@@ -905,12 +906,15 @@ static void virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq)
QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
do {
+ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
+ size_t cdb_size = qatomic_read(&vs->cdb_size);
+
if (suppress_notifications) {
virtio_queue_set_notification(vq, 0);
}
- while ((req = virtio_scsi_pop_req(s, vq, NULL))) {
- ret = virtio_scsi_handle_cmd_req_prepare(s, req);
+ while ((req = virtio_scsi_pop_req(s, vq, cdb_size, NULL))) {
+ ret = virtio_scsi_handle_cmd_req_prepare(s, req, cdb_size);
if (!ret) {
QTAILQ_INSERT_TAIL(&reqs, req, next);
} else if (ret == -EINVAL) {
@@ -981,7 +985,7 @@ static void virtio_scsi_set_config(VirtIODevice *vdev,
}
vs->sense_size = virtio_ldl_p(vdev, &scsiconf->sense_size);
- vs->cdb_size = virtio_ldl_p(vdev, &scsiconf->cdb_size);
+ qatomic_set(&vs->cdb_size, virtio_ldl_p(vdev, &scsiconf->cdb_size));
}
static uint64_t virtio_scsi_get_features(VirtIODevice *vdev,
@@ -1042,7 +1046,7 @@ static void virtio_scsi_push_event(VirtIOSCSI *s,
return;
}
- req = virtio_scsi_pop_req(s, vs->event_vq, &s->event_lock);
+ req = virtio_scsi_pop_req(s, vs->event_vq, 0, &s->event_lock);
WITH_QEMU_LOCK_GUARD(&s->event_lock) {
if (!req) {
s->events_dropped = true;
--
2.52.0

View File

@ -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: 21%{?rcrel}%{?dist}%{?cc_suffix}
Release: 22%{?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)
@ -702,6 +702,110 @@ Patch272: kvm-vfio-Add-Error-parameter-to-vfio_region_setup.patch
Patch273: kvm-hw-vfio-align-mmap-to-power-of-2-of-region-size-for-.patch
# For RHEL-184530 - CVE-2026-48914 qemu-kvm: Heap buffer overflow in virtio-blk SCSI request handling [rhel-10.3]
Patch274: kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch275: kvm-blkdebug-Add-delay-ns-option.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch276: kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch277: kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch278: kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch279: kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch280: kvm-ide-test-Factor-out-wait_dma_completion.patch
# For RHEL-121686 - qemu-kvm hung during drain after double pause
Patch281: kvm-ide-test-Test-reset-during-TRIM.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch282: kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch283: kvm-block-curl-fix-curl-internal-handles-handling.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch284: kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch285: kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch286: kvm-block-curl-fix-concurrent-completion-handling.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch287: kvm-block-curl-free-s-password-in-cleanup-paths.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch288: kvm-nvme-Kick-and-check-completions-in-BDS-context.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch289: kvm-nvme-Note-in-which-AioContext-some-functions-run.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch290: kvm-block-remove-detached-header-option-from-opts-after-.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch291: kvm-block-fix-luks-amend-when-run-in-coroutine.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch292: kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch293: kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch294: kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch295: kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch296: kvm-qemu-img-Fix-amend-option-parse-error-handling.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch297: kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch298: kvm-python-backport-drop-Python3.6-workarounds.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch299: kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch300: kvm-python-backport-avoid-creating-additional-event-loop.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch301: kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch302: kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch303: kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch304: kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch305: kvm-async-access-bottom-half-flags-with-qatomic_read.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch306: kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch307: kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch308: kvm-file-posix-populate-pwrite_zeroes_alignment.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch309: kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch310: kvm-iotests-add-Linux-loop-device-image-creation-test.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch311: kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch312: kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch313: kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch314: kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch315: kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch316: kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch317: kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch318: kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch319: kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch320: kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch321: kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch322: kvm-qemu-io-Add-aio_discard-command.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch323: kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch324: kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch325: kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch
# For RHEL-186384 - virt-storage: Backport stable branch fixes
Patch326: kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch
%if %{have_clang}
BuildRequires: clang
@ -1781,6 +1885,64 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \
%endif
%changelog
* Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22
- kvm-blkdebug-Add-delay-ns-option.patch [RHEL-121686]
- kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch [RHEL-121686]
- kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch [RHEL-121686]
- kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch [RHEL-121686]
- kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch [RHEL-121686]
- kvm-ide-test-Factor-out-wait_dma_completion.patch [RHEL-121686]
- kvm-ide-test-Test-reset-during-TRIM.patch [RHEL-121686]
- kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch [RHEL-186384]
- kvm-block-curl-fix-curl-internal-handles-handling.patch [RHEL-186384]
- kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch [RHEL-186384]
- kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch [RHEL-186384]
- kvm-block-curl-fix-concurrent-completion-handling.patch [RHEL-186384]
- kvm-block-curl-free-s-password-in-cleanup-paths.patch [RHEL-186384]
- kvm-nvme-Kick-and-check-completions-in-BDS-context.patch [RHEL-186384]
- kvm-nvme-Note-in-which-AioContext-some-functions-run.patch [RHEL-186384]
- kvm-block-remove-detached-header-option-from-opts-after-.patch [RHEL-186384]
- kvm-block-fix-luks-amend-when-run-in-coroutine.patch [RHEL-186384]
- kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch [RHEL-186384]
- kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch [RHEL-186384]
- kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch [RHEL-186384]
- kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch [RHEL-186384]
- kvm-qemu-img-Fix-amend-option-parse-error-handling.patch [RHEL-186384]
- kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch [RHEL-186384]
- kvm-python-backport-drop-Python3.6-workarounds.patch [RHEL-186384]
- kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch [RHEL-186384]
- kvm-python-backport-avoid-creating-additional-event-loop.patch [RHEL-186384]
- kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch [RHEL-186384]
- kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch [RHEL-186384]
- kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch [RHEL-186384]
- kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch [RHEL-186384]
- kvm-async-access-bottom-half-flags-with-qatomic_read.patch [RHEL-186384]
- kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch [RHEL-186384]
- kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch [RHEL-186384]
- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-186384]
- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-186384]
- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-186384]
- kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch [RHEL-186384]
- kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch [RHEL-186384]
- kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch [RHEL-186384]
- kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch [RHEL-186384]
- kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch [RHEL-186384]
- kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch [RHEL-186384]
- kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch [RHEL-186384]
- kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch [RHEL-186384]
- kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch [RHEL-186384]
- kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch [RHEL-186384]
- kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch [RHEL-186384]
- kvm-qemu-io-Add-aio_discard-command.patch [RHEL-186384]
- kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch [RHEL-186384]
- kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch [RHEL-186384]
- kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch [RHEL-186384]
- kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch [RHEL-186384]
- Resolves: RHEL-121686
(qemu-kvm hung during drain after double pause)
- Resolves: RHEL-186384
(virt-storage: Backport stable branch fixes)
* Fri Jun 19 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-21
- kvm-hw-vfio-sort-and-validate-sparse-mmap-regions-by-off.patch [RHEL-150900]
- kvm-vfio-Add-Error-parameter-to-vfio_region_setup.patch [RHEL-150900]