* Fri Aug 21 2026 Jon Maloy <jmaloy@redhat.com> - 10.1.0-24

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -149,7 +149,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \
Summary: QEMU is a machine emulator and virtualizer
Name: qemu-kvm
Version: 10.1.0
Release: 23%{?rcrel}%{?dist}%{?cc_suffix}
Release: 24%{?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)
@ -435,6 +435,96 @@ Patch145: kvm-s390x-sclp-prevent-re-reading-the-sclp-header.patch
Patch146: kvm-s390x-sclpcpi-check-event-length-field-before-readin.patch
# For RHEL-193033 - RHEL9.4 - qemu s390x: interface harding fixes
Patch147: kvm-s390x-css-firm-up-handling-of-chained-TIC-CCWs.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch148: kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch149: kvm-block-curl-fix-curl-internal-handles-handling.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch150: kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch151: kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch152: kvm-block-curl-fix-concurrent-completion-handling.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch153: kvm-block-curl-free-s-password-in-cleanup-paths.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch154: kvm-nvme-Kick-and-check-completions-in-BDS-context.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch155: kvm-nvme-Note-in-which-AioContext-some-functions-run.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch156: kvm-block-remove-detached-header-option-from-opts-after-.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch157: kvm-block-fix-luks-amend-when-run-in-coroutine.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch158: kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch159: kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch160: kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch161: kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch162: kvm-qemu-img-Fix-amend-option-parse-error-handling.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch163: kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch164: kvm-python-backport-drop-Python3.6-workarounds.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch165: kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch166: kvm-python-backport-avoid-creating-additional-event-loop.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch167: kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch168: kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch169: kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch170: kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch171: kvm-async-access-bottom-half-flags-with-qatomic_read.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch172: kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch173: kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch174: kvm-file-posix-populate-pwrite_zeroes_alignment.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch175: kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch176: kvm-iotests-add-Linux-loop-device-image-creation-test.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch177: kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch178: kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch179: kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch180: kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch181: kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch182: kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch183: kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch184: kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch185: kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch186: kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch187: kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch188: kvm-qemu-io-Add-aio_discard-command.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch189: kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch190: kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch191: kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch
# For RHEL-216952 - virt-storage: Backport stable branch fixes [rhel-9]
Patch192: kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch
# For RHEL-11424 - [IBM 9.6 FEAT] KVM: Full boot order support - qemu part
@ -2153,6 +2243,55 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \
%endif
%changelog
* Fri Aug 21 2026 Jon Maloy <jmaloy@redhat.com> - 10.1.0-24
- kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch [RHEL-216952]
- kvm-block-curl-fix-curl-internal-handles-handling.patch [RHEL-216952]
- kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch [RHEL-216952]
- kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch [RHEL-216952]
- kvm-block-curl-fix-concurrent-completion-handling.patch [RHEL-216952]
- kvm-block-curl-free-s-password-in-cleanup-paths.patch [RHEL-216952]
- kvm-nvme-Kick-and-check-completions-in-BDS-context.patch [RHEL-216952]
- kvm-nvme-Note-in-which-AioContext-some-functions-run.patch [RHEL-216952]
- kvm-block-remove-detached-header-option-from-opts-after-.patch [RHEL-216952]
- kvm-block-fix-luks-amend-when-run-in-coroutine.patch [RHEL-216952]
- kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch [RHEL-216952]
- kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch [RHEL-216952]
- kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch [RHEL-216952]
- kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch [RHEL-216952]
- kvm-qemu-img-Fix-amend-option-parse-error-handling.patch [RHEL-216952]
- kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch [RHEL-216952]
- kvm-python-backport-drop-Python3.6-workarounds.patch [RHEL-216952]
- kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch [RHEL-216952]
- kvm-python-backport-avoid-creating-additional-event-loop.patch [RHEL-216952]
- kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch [RHEL-216952]
- kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch [RHEL-216952]
- kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch [RHEL-216952]
- kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch [RHEL-216952]
- kvm-async-access-bottom-half-flags-with-qatomic_read.patch [RHEL-216952]
- kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch [RHEL-216952]
- kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch [RHEL-216952]
- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-216952]
- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-216952]
- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-216952]
- kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch [RHEL-216952]
- kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch [RHEL-216952]
- kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch [RHEL-216952]
- kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch [RHEL-216952]
- kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch [RHEL-216952]
- kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch [RHEL-216952]
- kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch [RHEL-216952]
- kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch [RHEL-216952]
- kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch [RHEL-216952]
- kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch [RHEL-216952]
- kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch [RHEL-216952]
- kvm-qemu-io-Add-aio_discard-command.patch [RHEL-216952]
- kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch [RHEL-216952]
- kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch [RHEL-216952]
- kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch [RHEL-216952]
- kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch [RHEL-216952]
- Resolves: RHEL-216952
(virt-storage: Backport stable branch fixes [rhel-9])
* Wed Aug 05 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-23
- kvm-scsi-change-buf_size-to-unsigned-int-in-scsi_SG_IO.patch [RHEL-158244]
- kvm-scsi-handle-reservation-changes-across-migration.patch [RHEL-158244]