diff --git a/kvm-async-access-bottom-half-flags-with-qatomic_read.patch b/kvm-async-access-bottom-half-flags-with-qatomic_read.patch new file mode 100644 index 0000000..efb742e --- /dev/null +++ b/kvm-async-access-bottom-half-flags-with-qatomic_read.patch @@ -0,0 +1,69 @@ +From 649398182d4897325f37cee57f81ae9f22272d66 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +(cherry picked from commit 5142397c79330aab9bef3230991c8ac0c251110f) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-blkdebug-Add-delay-ns-option.patch b/kvm-blkdebug-Add-delay-ns-option.patch new file mode 100644 index 0000000..aa9a1cc --- /dev/null +++ b/kvm-blkdebug-Add-delay-ns-option.patch @@ -0,0 +1,121 @@ +From 6386e658adc798b2af7400a53941523d21dd581d Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:26 +0200 +Subject: [PATCH 01/52] blkdebug: Add 'delay-ns' option + +RH-Author: Kevin Wolf +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-2-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit d5e4090177ad382e01084a1594a1a60a69f4c1cd) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch b/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch new file mode 100644 index 0000000..86647e0 --- /dev/null +++ b/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch @@ -0,0 +1,203 @@ +From c452d113b1d71de097e3b87f7d40d6865731b275 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-3-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 34a67637767d3ed1ac813c44effe827bbfba5996) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch b/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch new file mode 100644 index 0000000..f69bc48 --- /dev/null +++ b/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch @@ -0,0 +1,174 @@ +From 9a2d6f97d94f24ad7bfc83aea06d3324cbd46424 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:28 +0200 +Subject: [PATCH 03/52] block: Add flags parameter to blk_*_pdiscard() + +RH-Author: Kevin Wolf +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-4-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 53074ba0330ae8831abbae2521c012e1d9072ed3) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch b/kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch new file mode 100644 index 0000000..7cf51a8 --- /dev/null +++ b/kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch @@ -0,0 +1,56 @@ +From 748bbe57c3467eae94de533fb4099e89b8f52cc9 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Kevin Wolf +Message-ID: <20260410152314.86412-3-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit f27aea1896338f4dd085a0e2cb2ab3797c5fe3e9) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch b/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch new file mode 100644 index 0000000..5bdab59 --- /dev/null +++ b/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch @@ -0,0 +1,68 @@ +From d3c16be7233df4e4103cb048f392d4cb1ffa6e53 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Fri, 10 Apr 2026 17:23:13 +0200 +Subject: [PATCH 42/52] block: Create DEFAULT_BLOCK_CONF macro + +RH-Author: Kevin Wolf +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260410152314.86412-2-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit a1310cc6281d22ac948f4aa198dcc55d58fc039d) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch b/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch new file mode 100644 index 0000000..69e1ab5 --- /dev/null +++ b/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch @@ -0,0 +1,65 @@ +From a694d76d085f091f21d5062c4c0328db7b5eb188 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 < +Message-ID: <20260331102608.60882-1-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit a55402d5c3a8c63c801de86896f86c9abeda0ca8) +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-curl-fix-concurrent-completion-handling.patch b/kvm-block-curl-fix-concurrent-completion-handling.patch new file mode 100644 index 0000000..6f70497 --- /dev/null +++ b/kvm-block-curl-fix-concurrent-completion-handling.patch @@ -0,0 +1,71 @@ +From 974fa918950f21b9ee6134af75c551fbc9a61905 Mon Sep 17 00:00:00 2001 +From: Antoine Damhet +Date: Thu, 12 Feb 2026 17:27:24 +0100 +Subject: [PATCH 12/52] block/curl: fix concurrent completion handling + +RH-Author: Kevin Wolf +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260212162730.440855-2-adamhet@scaleway.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 6f7b0a23a6ea0cc72ad222ab37936248d99d4256) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-curl-fix-curl-internal-handles-handling.patch b/kvm-block-curl-fix-curl-internal-handles-handling.patch new file mode 100644 index 0000000..73aa175 --- /dev/null +++ b/kvm-block-curl-fix-curl-internal-handles-handling.patch @@ -0,0 +1,71 @@ +From e61f10f2711f4fee1c4ef0611b89d85cf3141f26 Mon Sep 17 00:00:00 2001 +From: Michael Tokarev +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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é +Signed-off-by: Michael Tokarev +(cherry picked from commit 606978500c3d18fb89a49844f253097b17f757de) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-curl-free-s-password-in-cleanup-paths.patch b/kvm-block-curl-free-s-password-in-cleanup-paths.patch new file mode 100644 index 0000000..6037434 --- /dev/null +++ b/kvm-block-curl-free-s-password-in-cleanup-paths.patch @@ -0,0 +1,57 @@ +From 36b96d00d6e51e02eca8bd2b4a11536376187f54 Mon Sep 17 00:00:00 2001 +From: GuoHan Zhao +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260320063016.262954-1-zhaoguohan_salmon@163.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 51fc8443c122fedf4d4891bbc3a1ff25dd8bacdf) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch b/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch new file mode 100644 index 0000000..8a6fa17 --- /dev/null +++ b/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch @@ -0,0 +1,44 @@ +From e3921ea32fdc43a74a639e215a861e464ec3da80 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Richard W.M. Jones +Message-ID: <20251013124127.604401-1-rjones@redhat.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit ad97769e9dcf4dbdaae6d859176e5f37fd6a7c66) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch b/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch new file mode 100644 index 0000000..fa22b55 --- /dev/null +++ b/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch @@ -0,0 +1,90 @@ +From a03e26bc3b51997e4584fe95c4da0feba85c076a Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Chenxi Mao +Reviewed-by: Daniel P. Berrangé +Reviewed-by: Akihiko Odaki +Reviewed-by: Thomas Huth +Reviewed-by: Richard Henderson +Signed-off-by: Richard Henderson +Message-ID: <20251009141026.4042021-2-rjones@redhat.com> +(cherry picked from commit ed26056d90ddff21351f3efd2cb47fea4f0e1d45) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-fix-luks-amend-when-run-in-coroutine.patch b/kvm-block-fix-luks-amend-when-run-in-coroutine.patch new file mode 100644 index 0000000..9b3645b --- /dev/null +++ b/kvm-block-fix-luks-amend-when-run-in-coroutine.patch @@ -0,0 +1,131 @@ +From fa0ed7060fac1b81af1f4b4390ad3f4ddbd03270 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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=, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 + #1 0x00007fad18b73f63 in __pthread_kill_internal (threadid=, 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=, assertion=, file=, line=, function=) at assert.c:118 + #5 0x00007fad18b120af in __assert_fail (assertion=, file=, line=, function=) + 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=) + at /usr/src/debug/qemu-9.2.4-1.fc42.x86_64/include/block/graph-lock.h:266 + #8 block_crypto_read_func (block=, 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 , opaque=opaque@entry=0x55ffb5edcc30, errp=0x55ffb6f00700) + at ../crypto/block-luks.c:927 + #10 0x000055ff7543b90f in qcrypto_block_luks_find_key + (block=, password=, masterkey=, readfunc=, opaque=, errp=) at ../crypto/block-luks.c:1045 + #11 qcrypto_block_luks_amend_add_keyslot + (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 , writefunc=0x55ff75485100 , opaque=0x55ffb5edcc3, opts_luks=0x7fad1715aef8, force=, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1673 + #12 qcrypto_block_luks_amend_options + (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 , writefunc=0x55ff75485100 , opaque=0x55ffb5edcc30, options=0x7fad1715aef0, force=, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1865 + #13 0x000055ff75485b95 in block_crypto_amend_options_generic_luks + (bs=, amend_options=, force=, errp=) at ../block/crypto.c:949 + #14 0x000055ff75485c28 in block_crypto_co_amend_luks (bs=, opts=, force=, errp=) + 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=, i1=) 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é +Message-ID: <20250919112213.1530079-1-berrange@redhat.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit c86488abaf017ed3f5a636c3247cd640a93d3b08) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch b/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch new file mode 100644 index 0000000..72c1f9c --- /dev/null +++ b/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch @@ -0,0 +1,107 @@ +From 319a9f69ecdd6bbe003503eb66aa4c30ffef3d40 Mon Sep 17 00:00:00 2001 +From: "Denis V. Lunev" +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Cc: Kevin Wolf +Cc: Hanna Reitz +Cc: Stefan Hajnoczi +Cc: Fiona Ebner +Message-ID: <20260424103917.248668-2-den@openvz.org> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit e3082ab3b38538ebdbc5cd62b4c476b673c5e515) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch b/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch new file mode 100644 index 0000000..a78b853 --- /dev/null +++ b/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch @@ -0,0 +1,66 @@ +From dfe7857ca3612b7c52b052735c8d1a96ecf1102d Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +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 +(cherry picked from commit b4e28c304bc58325f8f712cb25e5d700826caa25) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-51-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch b/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch new file mode 100644 index 0000000..8051503 --- /dev/null +++ b/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch @@ -0,0 +1,140 @@ +From 9a6c4bad7f575826796a4c690a0fa6bbcda1f5ad Mon Sep 17 00:00:00 2001 +From: "Denis V. Lunev" +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +CC: Kevin Wolf +CC: Hanna Reitz +CC: Stefan Hajnoczi +CC: Paolo Bonzini +Message-ID: <20260520142503.251959-2-den@openvz.org> +Signed-off-by: Stefan Hajnoczi +(cherry picked from commit 6864bec553b2e37699739615e604fc3c7bae0e1d) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-25-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch b/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch new file mode 100644 index 0000000..6663d1b --- /dev/null +++ b/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch @@ -0,0 +1,74 @@ +From 86b2caec2f830d34a03f4f55028a525bcf11d515 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260112152544.261923-1-f.ebner@proxmox.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Signed-off-by: Vladimir Sementsov-Ogievskiy +(cherry picked from commit 4a7b1bd18d2e1a6b3796e177ae5df9b198264a0b) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch b/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch new file mode 100644 index 0000000..ab63b65 --- /dev/null +++ b/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch @@ -0,0 +1,75 @@ +From 65523fcaa292ec714b66fa2b91ce8f50cd27ab64 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-Id: <20260311145717.668492-2-f.ebner@proxmox.com> +Reviewed-by: Hanna Czenczek +Signed-off-by: Hanna Czenczek +(cherry picked from commit 9ac85f4cc7995217db8f736733b990d6addcb036) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-remove-detached-header-option-from-opts-after-.patch b/kvm-block-remove-detached-header-option-from-opts-after-.patch new file mode 100644 index 0000000..b8c449b --- /dev/null +++ b/kvm-block-remove-detached-header-option-from-opts-after-.patch @@ -0,0 +1,72 @@ +From 3984b109d0b4e1a86c46d3b79641b08043e5d64a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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é + 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 +Signed-off-by: Daniel P. Berrangé +Message-ID: <20250919103810.1513109-1-berrange@redhat.com> +Reviewed-by: Eric Blake +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 6eda39a87f4fda78befa4085e3644e4440afc1dd) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch b/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch new file mode 100644 index 0000000..db0a441 --- /dev/null +++ b/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch @@ -0,0 +1,146 @@ +From 1c6a0b9352bf6eda335bd661869a46bc56d0c2fc Mon Sep 17 00:00:00 2001 +From: Dmitry Guryanov +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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' , __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 +Message-ID: <20251208085528.890098-1-dmitry.guryanov@gmail.com> +Reviewed-by: Hanna Czenczek +Signed-off-by: Kevin Wolf +(cherry picked from commit d4816177654d59e26ce212c436513f01842eb410) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch b/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch new file mode 100644 index 0000000..b121dfd --- /dev/null +++ b/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch @@ -0,0 +1,103 @@ +From ca326a462336a694d317d03793b5f741125633b7 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Buglink: https://gitlab.com/qemu-project/qemu/-/issues/3127 +Reviewed-by: Vladimir Sementsov-Ogievskiy +Signed-off-by: Stefan Hajnoczi +Message-ID: <20251007141700.71891-3-stefanha@redhat.com> +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit d704a13d2c025779bc91d04e127427347ddcf3b3) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch b/kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch new file mode 100644 index 0000000..76f36f1 --- /dev/null +++ b/kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch @@ -0,0 +1,51 @@ +From 9aac9cce4de8006a02800a3a20caaf11e87fc767 Mon Sep 17 00:00:00 2001 +From: "Halil Oktay (oblivionsage)" +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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) +Signed-off-by: Halil Oktay (oblivionsage) +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit cfda94eddb6c9c49b66461c950b22845a46a75c9) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch b/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch new file mode 100644 index 0000000..8af4c58 --- /dev/null +++ b/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch @@ -0,0 +1,85 @@ +From 43fe4a491f9843019d780bfc41db44e86602f184 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260427170520.101242-2-kwolf@redhat.com> +Reviewed-by: Denis V. Lunev +Tested-by: Denis V. Lunev +Signed-off-by: Kevin Wolf +(cherry picked from commit f0d9ccd46cf8fc576ab7d514f10f766546cdbc14) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-file-posix-populate-pwrite_zeroes_alignment.patch b/kvm-file-posix-populate-pwrite_zeroes_alignment.patch new file mode 100644 index 0000000..08ddd32 --- /dev/null +++ b/kvm-file-posix-populate-pwrite_zeroes_alignment.patch @@ -0,0 +1,65 @@ +From 746244e9df305293f9518dcb1dd636a7f8def4ac Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +Date: Tue, 7 Oct 2025 10:16:58 -0400 +Subject: [PATCH 34/52] file-posix: populate pwrite_zeroes_alignment + +RH-Author: Kevin Wolf +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20251007141700.71891-2-stefanha@redhat.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 98e788b91ad037193b1fb375561ef7e0fef3c2fd) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch b/kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch new file mode 100644 index 0000000..9d3076d --- /dev/null +++ b/kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch @@ -0,0 +1,93 @@ +From 71535e86e4aa44d7a2eefe15c0aaba1bbc0c1a10 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Fiona Ebner +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 +(cherry picked from commit 6910f04aa646f63a0257f77201ad8ea15992b816) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch b/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch new file mode 100644 index 0000000..82fba34 --- /dev/null +++ b/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch @@ -0,0 +1,163 @@ +From 377ac72f84cb83972c65a190cc5bc4fdf9bdab9b Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-6-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit c1c71a7e167fdabaa9827d00c0be3aeafebdd921) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch b/kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch new file mode 100644 index 0000000..ae7c295 --- /dev/null +++ b/kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch @@ -0,0 +1,57 @@ +From ba430cce181388723ec2d3174195e1d468ed18b2 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Kevin Wolf +Message-ID: <20260326165124.138593-1-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 59c1d31136688415e5d682a87942292dbb3caaeb) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch b/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch new file mode 100644 index 0000000..e7296a5 --- /dev/null +++ b/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch @@ -0,0 +1,141 @@ +From b2cc73c49eeee7f25626c4fdbb9aeaf561ffcdfd Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-5-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 095c08a7ba68cabaa6e0ce7a8a0804a949542c4c) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-ide-test-Factor-out-wait_dma_completion.patch b/kvm-ide-test-Factor-out-wait_dma_completion.patch new file mode 100644 index 0000000..18107b6 --- /dev/null +++ b/kvm-ide-test-Factor-out-wait_dma_completion.patch @@ -0,0 +1,91 @@ +From 03274f77317b494c8a5b29c0b3bf7112592d5d8a Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:31 +0200 +Subject: [PATCH 06/52] ide-test: Factor out wait_dma_completion() + +RH-Author: Kevin Wolf +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +RH-Commit: [6/7] 640daa938349847f3531cfc90e9c9d4d2b05fcc7 (kmwolf/centos-qemu-kvm) + +Signed-off-by: Kevin Wolf +Message-ID: <20260421161132.99878-7-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 92854c9c7539bdbf4f9c1abb33dd3ba59ff91e58) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-ide-test-Test-reset-during-TRIM.patch b/kvm-ide-test-Test-reset-during-TRIM.patch new file mode 100644 index 0000000..995ea60 --- /dev/null +++ b/kvm-ide-test-Test-reset-during-TRIM.patch @@ -0,0 +1,181 @@ +From 1d7ff7d45d25d64fc17087a7efadca12fb9ee4e9 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Tue, 21 Apr 2026 18:11:32 +0200 +Subject: [PATCH 07/52] ide-test: Test reset during TRIM + +RH-Author: Kevin Wolf +RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain +RH-Jira: RHEL-121686 +RH-Acked-by: Hanna Czenczek +RH-Acked-by: Stefan Hajnoczi +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 +Message-ID: <20260421161132.99878-8-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 2fa24e9755994f76f08ea2452215eb50f26f4c21) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch b/kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch new file mode 100644 index 0000000..61846cc --- /dev/null +++ b/kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch @@ -0,0 +1,153 @@ +From 37596bc8c96fb8261c13fc4f78d0f95ea8ab1397 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260427170520.101242-5-kwolf@redhat.com> +Reviewed-by: Denis V. Lunev +Tested-by: Denis V. Lunev +Signed-off-by: Kevin Wolf +(cherry picked from commit 389f5bcc744d3ddc127d550a57261aed9bbba1f3) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-55-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 < +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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é +(cherry picked from commit d4d0ebfcc926c11d16320d0d5accf22e3441c115) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch b/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch new file mode 100644 index 0000000..58dc1b1 --- /dev/null +++ b/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch @@ -0,0 +1,49 @@ +From 45efe35d9113cbd76d4652ee56a805cbde58f6b1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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é +(cherry picked from commit 2b2fb25c2aaf5b2e8172d845db39cc50a951a12e) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-iotests-add-Linux-loop-device-image-creation-test.patch b/kvm-iotests-add-Linux-loop-device-image-creation-test.patch new file mode 100644 index 0000000..8d38be2 --- /dev/null +++ b/kvm-iotests-add-Linux-loop-device-image-creation-test.patch @@ -0,0 +1,116 @@ +From 915f7779592a69d1243427ed8e5e95285b6daa4f Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +RH-Commit: [29/45] b46da09b4329cc3b3c3263f2ac62b3386035de8d (kmwolf/centos-qemu-kvm) + +This qemu-iotests test case is based on the reproducer that Jean-Louis +Dupond shared in +https://gitlab.com/qemu-project/qemu/-/issues/3127. + +Signed-off-by: Stefan Hajnoczi +Message-ID: <20251007141700.71891-4-stefanha@redhat.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Tested-by: Vladimir Sementsov-Ogievskiy +Tested-by: Fiona Ebner +Reviewed-by: Fiona Ebner +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 59a1cf0cd31597d2f6e2c18dc400a1de8427d47d) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch b/kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch new file mode 100644 index 0000000..d7f101f --- /dev/null +++ b/kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch @@ -0,0 +1,77 @@ +From 86d893766713ddb454a9e7bf9e3069a41cf77b1e Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260120113859.251743-1-f.ebner@proxmox.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Tested-by: Vladimir Sementsov-Ogievskiy +Signed-off-by: Vladimir Sementsov-Ogievskiy +(cherry picked from commit 267d7ae99a1d3b5be9d3421db3bdf651cc18c7ab) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch b/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch new file mode 100644 index 0000000..d78f395 --- /dev/null +++ b/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch @@ -0,0 +1,75 @@ +From c9239fed6d3b7f14b847f10d53fd7c057e08cafd Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 aren’t there. + +The context in which we kick does not matter, but let’s just keep kick +and process_completion together for simplicity’s sake. + +(For what it’s 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 wouldn’t take more from those results than that it doesn’t really seem +to matter either way.) + +Cc: qemu-stable@nongnu.org +Signed-off-by: Hanna Czenczek +Message-ID: <20251110154854.151484-8-hreitz@redhat.com> +Reviewed-by: Stefan Hajnoczi +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 7a501bbd51941fb1867d78e6b0d1dc69e396b9e2) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch b/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch new file mode 100644 index 0000000..04467fe --- /dev/null +++ b/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch @@ -0,0 +1,273 @@ +From af936e3ce66c2b8ab98fe6a529cea73d86f78ee9 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Hanna Czenczek +Message-ID: <20251110154854.151484-10-hreitz@redhat.com> +Reviewed-by: Stefan Hajnoczi +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(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 +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch b/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch new file mode 100644 index 0000000..926a99d --- /dev/null +++ b/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch @@ -0,0 +1,104 @@ +From d8bdd0cbf122d1f01acaec8c9846b9cdbf925d3c Mon Sep 17 00:00:00 2001 +From: John Snow +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +cherry picked from commit python-qemu-qmp@aa1ff9907603a3033296027e1bd021133df86ef1 +Signed-off-by: John Snow +Reviewed-by: Daniel P. Berrangé +(cherry picked from commit 5d99044d09db0fa8c2b3294e301927118f9effc9) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-python-backport-avoid-creating-additional-event-loop.patch b/kvm-python-backport-avoid-creating-additional-event-loop.patch new file mode 100644 index 0000000..a2b0a52 --- /dev/null +++ b/kvm-python-backport-avoid-creating-additional-event-loop.patch @@ -0,0 +1,210 @@ +From 3739feb5280370c3439030e1f1fe0ec0d0eb2ccb Mon Sep 17 00:00:00 2001 +From: John Snow +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Reported-by: Daniel P. Berrangé +Signed-off-by: John Snow +cherry picked from commit python-qemu-qmp@21ce2ee4f2df87efe84a27b9c5112487f4670622 +cherry picked from commit python-qemu-qmp@c08fb82b38212956ccffc03fc6d015c3979f42fe +Signed-off-by: John Snow +Reviewed-by: Daniel P. Berrangé +(cherry picked from commit 85f223e5b031eb8ab63fbca314a4fb296a3a2632) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-python-backport-drop-Python3.6-workarounds.patch b/kvm-python-backport-drop-Python3.6-workarounds.patch new file mode 100644 index 0000000..bba142e --- /dev/null +++ b/kvm-python-backport-drop-Python3.6-workarounds.patch @@ -0,0 +1,300 @@ +From 5de86fbbb5f6c0353bd2668c0424da28bf786264 Mon Sep 17 00:00:00 2001 +From: John Snow +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +cherry picked from commit python-qemu-qmp@3e8e34e594cfc6b707e6f67959166acde4b421b8 +Signed-off-by: John Snow +Reviewed-by: Daniel P. Berrangé +(cherry picked from commit f9d2e0a3bd7ba2a693a892881f91cf53fa90cc71) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch b/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch new file mode 100644 index 0000000..d62a04d --- /dev/null +++ b/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch @@ -0,0 +1,162 @@ +From 21a26a72ecde7527ece5fdef3ba23f10084fd43d Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Signed-off-by: Kevin Wolf +Message-ID: <20260427170520.101242-4-kwolf@redhat.com> +Reviewed-by: Denis V. Lunev +Tested-by: Denis V. Lunev +Signed-off-by: Kevin Wolf +(cherry picked from commit b8bfb1478d61512f851badd0d912c6661a2efee7) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-54-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch b/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch new file mode 100644 index 0000000..f94c11d --- /dev/null +++ b/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch @@ -0,0 +1,205 @@ +From aff7b26c2a2ad0c78e403a572a5cca52e1f2774e Mon Sep 17 00:00:00 2001 +From: Thomas Lamprecht +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Reviewed-by: Fiona Ebner +Signed-off-by: Thomas Lamprecht +Message-ID: <20260522151318.238064-1-t.lamprecht@proxmox.com> +[kwolf: Reverted unnecessary change to 'nr' assignment] +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 1d47eb68983577a4e06fe1c165d90e128b191b86) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-56-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 < +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260603193813.2327596-1-farosas@suse.de> +Reviewed-by: Stefan Hajnoczi +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 7e573b660fefdebd21cb755d0d34bb5942fd3af3) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-57-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch b/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch new file mode 100644 index 0000000..de2c975 --- /dev/null +++ b/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch @@ -0,0 +1,49 @@ +From a0454a8af25172f3f1f0f6eacc3e07d9c2b077fe Mon Sep 17 00:00:00 2001 +From: Akihiko Odaki +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20251023-iotests-v1-1-fab143ca4c2f@rsg.ci.i.u-tokyo.ac.jp> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit f00bcc833790c72c08bc5eed97845fdaa7542507) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch b/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch new file mode 100644 index 0000000..44f3541 --- /dev/null +++ b/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch @@ -0,0 +1,161 @@ +From 21f29bc69a9ce910c6869453a48665087fddfa29 Mon Sep 17 00:00:00 2001 +From: Alberto Garcia +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3174 +Fixes: 12df580b3b7f ("qemu-img: rebase: avoid unnecessary COW operations") +Signed-off-by: Alberto Garcia +Message-ID: <20251107091834.383781-1-berto@igalia.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit 909852ba6b4a22fd2b6f9d8b88adb5fc47dfa781) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-qemu-io-Add-aio_discard-command.patch b/kvm-qemu-io-Add-aio_discard-command.patch new file mode 100644 index 0000000..14b744d --- /dev/null +++ b/kvm-qemu-io-Add-aio_discard-command.patch @@ -0,0 +1,167 @@ +From f598b8e008e2b9179acaa138d75bac390a204508 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Sat, 13 Jun 2026 23:03:36 +0300 +Subject: [PATCH 48/52] qemu-io: Add 'aio_discard' command + +RH-Author: Kevin Wolf +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20260427170520.101242-3-kwolf@redhat.com> +Reviewed-by: Denis V. Lunev +Tested-by: Denis V. Lunev +Signed-off-by: Kevin Wolf +(cherry picked from commit 7f8466e2ce620e3c6a6e2f32d616367174d4dbe9) +Signed-off-by: Michael Tokarev + +Message-ID: <20260613200411.1808021-53-mjt@tls.msk.ru> +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch b/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch new file mode 100644 index 0000000..7abc314 --- /dev/null +++ b/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch @@ -0,0 +1,60 @@ +From 327ef6e8b3ea224047409dea90edba5c59f174ad Mon Sep 17 00:00:00 2001 +From: Alberto Garcia +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +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 +Message-ID: <20251112170959.700840-1-berto@igalia.com> +Reviewed-by: Eric Blake +Tested-by: Thomas Huth +Signed-off-by: Eric Blake +(cherry picked from commit 4c91719a6a78a1c24d8bb854f7594e767962d0d9) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch b/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch new file mode 100644 index 0000000..1d961f6 --- /dev/null +++ b/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch @@ -0,0 +1,49 @@ +From 588224f69f23d255c577ddc6fabcdb4288c420b0 Mon Sep 17 00:00:00 2001 +From: Thomas Huth +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-ID: <20251208075320.35682-1-thuth@redhat.com> +Reviewed-by: Laurent Vivier +Reviewed-by: Philippe Mathieu-Daudé +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit ab0065e36adf8becd9c1ffceec37ee809ce683af) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch b/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch new file mode 100644 index 0000000..87a359e --- /dev/null +++ b/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch @@ -0,0 +1,179 @@ +From 8340b5280edd665c0b1bc6d11c28099252f92d8a Mon Sep 17 00:00:00 2001 +From: Alberto Garcia +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Message-Id: <825598ef34ad384d936da19d634eda75598508f7.1773316842.git.berto@igalia.com> +Signed-off-by: Hanna Czenczek +(cherry picked from commit 9c8430f5d65144b85ad76433369288182a1c7baa) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch b/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch new file mode 100644 index 0000000..0ea10be --- /dev/null +++ b/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch @@ -0,0 +1,79 @@ +From e9d0e7db0e7e90fb8a71e3725cdceba7a83b665e Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Reviewed-by: Stefan Hajnoczi +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20251204172657.174391-1-kwolf@redhat.com> +(cherry picked from commit 623db856476806124e9ae45fbc39e75012261570) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch b/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch new file mode 100644 index 0000000..42915fb --- /dev/null +++ b/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch @@ -0,0 +1,300 @@ +From 488b339be87be50b40851e85aa42aa76adbe1782 Mon Sep 17 00:00:00 2001 +From: Stefan Hajnoczi +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Cc: Damien Le Moal +Cc: Dmitry Fomichev +Fixes: CVE-2026-5761 +Fixes: 4f7366506a9 ("virtio-blk: add zoned storage emulation for zoned devices") +Reported-by: Mingyuan Luo +Reviewed-by: Damien Le Moal +Signed-off-by: Stefan Hajnoczi +(cherry picked from commit 4913ae36f9796c55d434dcbfa6bdb9ebb3e5e4b1) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch b/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch new file mode 100644 index 0000000..fad2908 --- /dev/null +++ b/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch @@ -0,0 +1,131 @@ +From c1a573c922a2c99060dfb63413cd47db2c784ca6 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +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 +RH-MergeRequest: 504: virt-storage: Backport stable branch fixes +RH-Jira: RHEL-186384 +RH-Acked-by: Stefan Hajnoczi +RH-Acked-by: Miroslav Rezanina +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 +Tested-by: Jihe Wang +Reviewed-by: Stefan Hajnoczi +Fixes: CVE-2026-5763 +Signed-off-by: Paolo Bonzini +(cherry picked from commit 79971302935472232a68073faddb085177e3ca54) +Signed-off-by: Michael Tokarev +Signed-off-by: Kevin Wolf +--- + 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 + diff --git a/qemu-kvm.spec b/qemu-kvm.spec index b64ee5d..37a3913 100644 --- a/qemu-kvm.spec +++ b/qemu-kvm.spec @@ -143,7 +143,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \ Summary: QEMU is a machine emulator and virtualizer Name: qemu-kvm Version: 10.1.0 -Release: 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 - 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 - 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]