From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id A49AD1FF13F for ; Thu, 12 Mar 2026 13:13:27 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9FD86134AC; Thu, 12 Mar 2026 13:13:20 +0100 (CET) From: Fiona Ebner To: pve-devel@lists.proxmox.com Subject: [PATCH qemu 2/2] stable fixes for QEMU 10.2.1 Date: Thu, 12 Mar 2026 12:44:03 +0100 Message-ID: <20260312114417.82984-3-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260312114417.82984-1-f.ebner@proxmox.com> References: <20260312114417.82984-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1773315826517 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.274 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LOTSOFHASH 0.25 Emails with lots of hash-like gibberish POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes RCVD_IN_MSPIKE_H2 0.001 Average reputation (+2) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: f.ebner@proxmox.com X-Mailman-Rule-Hits: max-size X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; news-moderation; no-subject; digests; suspicious-header Message-ID-Hash: LV4TPTN7VAHGMFVM7YLMMOJT2OGV5KSI X-Message-ID-Hash: LV4TPTN7VAHGMFVM7YLMMOJT2OGV5KSI X-Mailman-Approved-At: Thu, 12 Mar 2026 13:13:31 +0100 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Fixes for a very bad performance regression for io_uring that could happen in combination with 'ide-hd'. Fix for a long-standing race in mirror startup when block allocation status change could be missed. Fix for an out-of-bounds read with maliciously crafted VMDK images. Fixes for newly-in-10.2.1 regressions in CPU migration, VirtIO-GPU-Virgl and ARM emulation. Fixes for fuse, throttle groups and block error handling. Note that Proxmox VE does not expose virtio-snd, but the issues look pretty bad, so still include them for people who manually use that via custom args. Signed-off-by: Fiona Ebner --- ...d-support-for-sync-bitmap-mode-never.patch | 42 +-- ...-support-for-conditional-and-always-.patch | 10 +- ...-to-bdrv_dirty_bitmap_merge_internal.patch | 6 +- .../0006-mirror-move-some-checks-to-qmp.patch | 4 +- ...ck-range-when-setting-zero-bitmap-fo.patch | 7 +- ...mdk-fix-OOB-read-in-vmdk_read_extent.patch | 38 +++ ...roups-fix-deadlock-with-iolimits-and.patch | 133 +++++++++ ...-BLOCK_IO_ERROR-with-action-stop-for.patch | 88 ++++++ ...d-dirty-bitmap-writes-during-startup.patch | 152 +++++++++++ ...-Add-virtio-gpu-virgl-hostmem-region.patch | 174 ++++++++++++ ...e-BHs-are-invoked-only-from-main-loo.patch | 123 +++++++++ ...c-Fix-out-of-bounds-read-in-I2C-MMIO.patch | 136 +++++++++ ...nt-for-SME-in-aarch64_sve_narrow_vq-.patch | 62 +++++ ...eature-check-in-DO_SVE2_RRX-DO_SVE2_.patch | 47 ++++ ...llow-SVE-RAX1-in-SME2p1-streaming-mo.patch | 44 +++ ...t-arm-Don-t-let-sme-on-downgrade-SME.patch | 98 +++++++ ...t-the-correct-TI-bits-for-WFIT-traps.patch | 35 +++ ...otify-main-loop-when-SQEs-are-queued.patch | 119 ++++++++ ...heck-CQ-ring-directly-in-gsource_che.patch | 49 ++++ ...-add-compat-for-migrating-error-code.patch | 75 +++++ ...0020-virtio-snd-remove-TODO-comments.patch | 93 +++++++ ...andle-5.14.6.2-for-PCM_INFO-properly.patch | 89 ++++++ ...ix-max_size-bounds-check-in-input-cb.patch | 44 +++ ...tio-snd-tighten-read-amount-in-in_cb.patch | 51 ++++ ...l-Fix-incorrect-trace-event-in-read-.patch | 41 +++ ...ate-x86_decode-Actually-use-stream-i.patch | 52 ++++ ...ing-of-tasks-from-marking-them-as-co.patch | 258 ++++++++++++++++++ ...or-TLS-I-O-source-data-on-cancellati.patch | 176 ++++++++++++ ...or-websock-I-O-source-data-on-cancel.patch | 143 ++++++++++ ..._printable_name-consistently-return-.patch | 142 ++++++++++ ...-write-buffer-content-before-polling.patch | 114 ++++++++ ...he-CPU-model-to-kvm64-32-instead-of-.patch | 4 +- ...add-the-zeroinit-block-driver-filter.patch | 2 +- ...le-posix-make-locking-optiono-on-cre.patch | 2 +- ...ckup-Proxmox-backup-patches-for-QEMU.patch | 2 +- ...k-driver-to-map-backup-archives-into.patch | 2 +- ...igrate-dirty-bitmap-state-via-savevm.patch | 2 +- .../0038-block-add-alloc-track-driver.patch | 2 +- .../0039-PVE-backup-add-fleecing-option.patch | 2 +- ...ment-backup-access-setup-and-teardow.patch | 2 +- ...se-migration-blocker-check-for-snaps.patch | 2 +- debian/patches/series | 26 ++ 42 files changed, 2650 insertions(+), 43 deletions(-) create mode 100644 debian/patches/extra/0005-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch create mode 100644 debian/patches/extra/0006-block-throttle-groups-fix-deadlock-with-iolimits-and.patch create mode 100644 debian/patches/extra/0007-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch create mode 100644 debian/patches/extra/0008-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch create mode 100644 debian/patches/extra/0009-virtio-gpu-virgl-Add-virtio-gpu-virgl-hostmem-region.patch create mode 100644 debian/patches/extra/0010-virtio-gpu-Ensure-BHs-are-invoked-only-from-main-loo.patch create mode 100644 debian/patches/extra/0011-hw-i2c-aspeed_i2c-Fix-out-of-bounds-read-in-I2C-MMIO.patch create mode 100644 debian/patches/extra/0012-target-arm-Account-for-SME-in-aarch64_sve_narrow_vq-.patch create mode 100644 debian/patches/extra/0013-target-arm-Fix-feature-check-in-DO_SVE2_RRX-DO_SVE2_.patch create mode 100644 debian/patches/extra/0014-target-arm-tcg-Allow-SVE-RAX1-in-SME2p1-streaming-mo.patch create mode 100644 debian/patches/extra/0015-target-arm-Don-t-let-sme-on-downgrade-SME.patch create mode 100644 debian/patches/extra/0016-target-arm-set-the-correct-TI-bits-for-WFIT-traps.patch create mode 100644 debian/patches/extra/0017-aio-posix-notify-main-loop-when-SQEs-are-queued.patch create mode 100644 debian/patches/extra/0018-fdmon-io_uring-check-CQ-ring-directly-in-gsource_che.patch create mode 100644 debian/patches/extra/0019-target-i386-add-compat-for-migrating-error-code.patch create mode 100644 debian/patches/extra/0020-virtio-snd-remove-TODO-comments.patch create mode 100644 debian/patches/extra/0021-virtio-snd-handle-5.14.6.2-for-PCM_INFO-properly.patch create mode 100644 debian/patches/extra/0022-virtio-snd-fix-max_size-bounds-check-in-input-cb.patch create mode 100644 debian/patches/extra/0023-virtio-snd-tighten-read-amount-in-in_cb.patch create mode 100644 debian/patches/extra/0024-hw-misc-virt_ctrl-Fix-incorrect-trace-event-in-read-.patch create mode 100644 debian/patches/extra/0025-target-i386-emulate-x86_decode-Actually-use-stream-i.patch create mode 100644 debian/patches/extra/0026-io-separate-freeing-of-tasks-from-marking-them-as-co.patch create mode 100644 debian/patches/extra/0027-io-fix-cleanup-for-TLS-I-O-source-data-on-cancellati.patch create mode 100644 debian/patches/extra/0028-io-fix-cleanup-for-websock-I-O-source-data-on-cancel.patch create mode 100644 debian/patches/extra/0029-hw-Make-qdev_get_printable_name-consistently-return-.patch create mode 100644 debian/patches/extra/0030-fuse-Copy-write-buffer-content-before-polling.patch diff --git a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch index b3906bd..81ca0fa 100644 --- a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch +++ b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch @@ -27,7 +27,7 @@ Signed-off-by: Ma Haocong Signed-off-by: John Snow Signed-off-by: Fabian Grünbichler Signed-off-by: Thomas Lamprecht -[FE: rebased for 10.1.0] +[FE: rebased for 10.2.1] Signed-off-by: Fiona Ebner --- block/mirror.c | 87 +++++++++++++++++++++----- @@ -38,7 +38,7 @@ Signed-off-by: Fiona Ebner 5 files changed, 135 insertions(+), 21 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index bc982cb99a..99805e7a9d 100644 +index fa1d975eb9..d09479789f 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -74,6 +74,8 @@ typedef struct MirrorBlockJob { @@ -50,7 +50,7 @@ index bc982cb99a..99805e7a9d 100644 BdrvDirtyBitmap *dirty_bitmap; BdrvDirtyBitmapIter *dbi; uint8_t *buf; -@@ -871,6 +873,16 @@ static void mirror_abort(Job *job) +@@ -872,6 +874,16 @@ static void mirror_abort(Job *job) assert(ret == 0); } @@ -67,7 +67,7 @@ index bc982cb99a..99805e7a9d 100644 static void coroutine_fn mirror_throttle(MirrorBlockJob *s) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); -@@ -1110,7 +1122,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) +@@ -1111,7 +1123,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) mirror_free_init(s); s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); @@ -77,7 +77,7 @@ index bc982cb99a..99805e7a9d 100644 ret = mirror_dirty_init(s); if (ret < 0 || job_is_cancelled(&s->common.job)) { goto immediate_exit; -@@ -1400,6 +1413,7 @@ static const BlockJobDriver mirror_job_driver = { +@@ -1401,6 +1414,7 @@ static const BlockJobDriver mirror_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -85,7 +85,7 @@ index bc982cb99a..99805e7a9d 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = mirror_cancel, -@@ -1418,6 +1432,7 @@ static const BlockJobDriver commit_active_job_driver = { +@@ -1419,6 +1433,7 @@ static const BlockJobDriver commit_active_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -93,7 +93,7 @@ index bc982cb99a..99805e7a9d 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = commit_active_cancel, -@@ -1838,6 +1853,8 @@ static BlockJob *mirror_start_job( +@@ -1841,6 +1856,8 @@ static BlockJob *mirror_start_job( BlockCompletionFunc *cb, void *opaque, const BlockJobDriver *driver, @@ -102,7 +102,7 @@ index bc982cb99a..99805e7a9d 100644 BlockDriverState *base, bool auto_complete, const char *filter_node_name, bool is_mirror, MirrorCopyMode copy_mode, -@@ -1853,10 +1870,39 @@ static BlockJob *mirror_start_job( +@@ -1856,10 +1873,39 @@ static BlockJob *mirror_start_job( GLOBAL_STATE_CODE(); @@ -144,7 +144,7 @@ index bc982cb99a..99805e7a9d 100644 assert(is_power_of_2(granularity)); if (buf_size < 0) { -@@ -1998,6 +2044,8 @@ static BlockJob *mirror_start_job( +@@ -2023,6 +2069,8 @@ static BlockJob *mirror_start_job( s->on_source_error = on_source_error; s->on_target_error = on_target_error; s->sync_mode = sync_mode; @@ -153,9 +153,9 @@ index bc982cb99a..99805e7a9d 100644 s->backing_mode = backing_mode; s->target_is_zero = target_is_zero; qatomic_set(&s->copy_mode, copy_mode); -@@ -2023,6 +2071,18 @@ static BlockJob *mirror_start_job( - */ - bdrv_disable_dirty_bitmap(s->dirty_bitmap); +@@ -2037,6 +2085,18 @@ static BlockJob *mirror_start_job( + } + bdrv_graph_rdunlock_main_loop(); + if (s->sync_bitmap) { + bdrv_dirty_bitmap_set_busy(s->sync_bitmap, true); @@ -172,17 +172,17 @@ index bc982cb99a..99805e7a9d 100644 bdrv_graph_wrlock_drained(); ret = block_job_add_bdrv(&s->common, "source", bs, 0, BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | -@@ -2105,6 +2165,9 @@ fail: - if (s->dirty_bitmap) { - bdrv_release_dirty_bitmap(s->dirty_bitmap); - } +@@ -2116,6 +2176,9 @@ fail: + g_free(s->replaces); + blk_unref(s->target); + bs_opaque->job = NULL; + if (s->sync_bitmap) { + bdrv_dirty_bitmap_set_busy(s->sync_bitmap, false); + } job_early_fail(&s->common.job); } -@@ -2127,7 +2190,10 @@ void mirror_start(const char *job_id, BlockDriverState *bs, +@@ -2139,7 +2202,10 @@ void mirror_start(const char *job_id, BlockDriverState *bs, BlockDriverState *target, const char *replaces, int creation_flags, int64_t speed, uint32_t granularity, int64_t buf_size, @@ -194,7 +194,7 @@ index bc982cb99a..99805e7a9d 100644 bool target_is_zero, BlockdevOnError on_source_error, BlockdevOnError on_target_error, -@@ -2138,13 +2204,6 @@ void mirror_start(const char *job_id, BlockDriverState *bs, +@@ -2150,13 +2216,6 @@ void mirror_start(const char *job_id, BlockDriverState *bs, GLOBAL_STATE_CODE(); @@ -208,7 +208,7 @@ index bc982cb99a..99805e7a9d 100644 bdrv_graph_rdlock_main_loop(); base = mode == MIRROR_SYNC_MODE_TOP ? bdrv_backing_chain_next(bs) : NULL; bdrv_graph_rdunlock_main_loop(); -@@ -2152,8 +2211,8 @@ void mirror_start(const char *job_id, BlockDriverState *bs, +@@ -2164,8 +2223,8 @@ void mirror_start(const char *job_id, BlockDriverState *bs, mirror_start_job(job_id, bs, creation_flags, target, replaces, speed, granularity, buf_size, mode, backing_mode, target_is_zero, on_source_error, on_target_error, unmap, @@ -219,7 +219,7 @@ index bc982cb99a..99805e7a9d 100644 } BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, -@@ -2180,7 +2239,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, +@@ -2192,7 +2251,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, job_id, bs, creation_flags, base, NULL, speed, 0, 0, MIRROR_SYNC_MODE_TOP, MIRROR_LEAVE_BACKING_CHAIN, false, on_error, on_error, true, cb, opaque, @@ -333,7 +333,7 @@ index e7c8f1a856..d5aa68caeb 100644 BlockdevOnError on_source_error, BlockdevOnError on_target_error, diff --git a/qapi/block-core.json b/qapi/block-core.json -index b82af74256..64f2befdf5 100644 +index 4118d884f4..d4a5765dc4 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2275,6 +2275,15 @@ diff --git a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch index 8258adc..f413554 100644 --- a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch +++ b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch @@ -24,10 +24,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 99805e7a9d..7dae4d6e8a 100644 +index d09479789f..0050d4372f 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -734,8 +734,6 @@ static int mirror_exit_common(Job *job) +@@ -735,8 +735,6 @@ static int mirror_exit_common(Job *job) bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs); } @@ -36,7 +36,7 @@ index 99805e7a9d..7dae4d6e8a 100644 /* Make sure that the source BDS doesn't go away during bdrv_replace_node, * before we can call bdrv_drained_end */ bdrv_ref(src); -@@ -851,6 +849,18 @@ static int mirror_exit_common(Job *job) +@@ -852,6 +850,18 @@ static int mirror_exit_common(Job *job) bdrv_drained_end(target_bs); bdrv_unref(target_bs); @@ -55,7 +55,7 @@ index 99805e7a9d..7dae4d6e8a 100644 bs_opaque->job = NULL; bdrv_drained_end(src); -@@ -1880,10 +1890,6 @@ static BlockJob *mirror_start_job( +@@ -1883,10 +1893,6 @@ static BlockJob *mirror_start_job( " sync mode", MirrorSyncMode_str(sync_mode)); return NULL; @@ -66,7 +66,7 @@ index 99805e7a9d..7dae4d6e8a 100644 } } else if (bitmap) { error_setg(errp, -@@ -1900,6 +1906,12 @@ static BlockJob *mirror_start_job( +@@ -1903,6 +1909,12 @@ static BlockJob *mirror_start_job( return NULL; } granularity = bdrv_dirty_bitmap_granularity(bitmap); diff --git a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch index 4f9cdbd..6023b78 100644 --- a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch +++ b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch @@ -16,10 +16,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 7dae4d6e8a..0f96c8b5ce 100644 +index 0050d4372f..ee745f8ec4 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -855,8 +855,8 @@ static int mirror_exit_common(Job *job) +@@ -856,8 +856,8 @@ static int mirror_exit_common(Job *job) job->ret == 0 && ret == 0)) { /* Success; synchronize copy back to sync. */ bdrv_clear_dirty_bitmap(s->sync_bitmap, NULL); @@ -30,7 +30,7 @@ index 7dae4d6e8a..0f96c8b5ce 100644 } } bdrv_release_dirty_bitmap(s->dirty_bitmap); -@@ -2088,11 +2088,8 @@ static BlockJob *mirror_start_job( +@@ -2102,11 +2102,8 @@ static BlockJob *mirror_start_job( } if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) { diff --git a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch index 687a256..76b9d40 100644 --- a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch +++ b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch @@ -21,10 +21,10 @@ Signed-off-by: Fiona Ebner 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 0f96c8b5ce..5340a695b1 100644 +index ee745f8ec4..e71f8f3cdf 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -1880,31 +1880,13 @@ static BlockJob *mirror_start_job( +@@ -1883,31 +1883,13 @@ static BlockJob *mirror_start_job( GLOBAL_STATE_CODE(); diff --git a/debian/patches/extra/0003-block-mirror-check-range-when-setting-zero-bitmap-fo.patch b/debian/patches/extra/0003-block-mirror-check-range-when-setting-zero-bitmap-fo.patch index 908d721..9af7ac5 100644 --- a/debian/patches/extra/0003-block-mirror-check-range-when-setting-zero-bitmap-fo.patch +++ b/debian/patches/extra/0003-block-mirror-check-range-when-setting-zero-bitmap-fo.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Fiona Ebner -Date: Mon, 12 Jan 2026 15:32:52 +0100 +Date: Mon, 12 Jan 2026 16:23:51 +0100 Subject: [PATCH] block/mirror: check range when setting zero bitmap for sync write @@ -32,6 +32,11 @@ bitmap too, which uses the same narrower range. Cc: qemu-stable@nongnu.org Fixes: 7e277545b9 ("mirror: Skip writing zeroes when target is already zero") Signed-off-by: Fiona Ebner +Message-ID: <20260112152544.261923-1-f.ebner@proxmox.com> +Reviewed-by: Vladimir Sementsov-Ogievskiy +Signed-off-by: Vladimir Sementsov-Ogievskiy +(cherry picked from commit 4a7b1bd18d2e1a6b3796e177ae5df9b198264a0b) +Signed-off-by: Fiona Ebner --- block/mirror.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/debian/patches/extra/0005-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch b/debian/patches/extra/0005-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch new file mode 100644 index 0000000..c458de4 --- /dev/null +++ b/debian/patches/extra/0005-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Oblivionsage +Date: Tue, 10 Feb 2026 13:33:25 +0100 +Subject: [PATCH] block/vmdk: fix OOB read in vmdk_read_extent() + +Bounds check for marker.size doesn't account for the 12-byte marker +header, allowing zlib to read past the allocated buffer. + +Move the check inside the has_marker block and subtract the marker size. + +Fixes: CVE-2026-2243 +Reported-by: Halil Oktay (oblivionsage) +Signed-off-by: Halil Oktay (oblivionsage) +(picked from https://lore.kernel.org/qemu-devel/CAJ9qJssSwxkmEVethg57-Ph6maEfButSaV-r07ma9_x1sp6wYg@mail.gmail.com/ ) +Signed-off-by: Fiona Ebner +--- + block/vmdk.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/block/vmdk.c b/block/vmdk.c +index 89e89cd10e..cd8b4ec7c8 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) { diff --git a/debian/patches/extra/0006-block-throttle-groups-fix-deadlock-with-iolimits-and.patch b/debian/patches/extra/0006-block-throttle-groups-fix-deadlock-with-iolimits-and.patch new file mode 100644 index 0000000..1485bfd --- /dev/null +++ b/debian/patches/extra/0006-block-throttle-groups-fix-deadlock-with-iolimits-and.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dmitry Guryanov +Date: Mon, 8 Dec 2025 11:55:28 +0300 +Subject: [PATCH] block/throttle-groups: fix deadlock with iolimits and muliple + iothreads + +Details: https://gitlab.com/qemu-project/qemu/-/issues/3144 + +The function schedule_next_request is called with tg->lock held and +it may call throttle_group_co_restart_queue, which takes +tgm->throttled_reqs_lock, qemu_co_mutex_lock may leave current +coroutine if other iothread has taken the lock. If the next +coroutine will call throttle_group_co_io_limits_intercept - it +will try to take the mutex tg->lock which will never be released. + +Here is the backtrace of the iothread: +Thread 30 (Thread 0x7f8aad1fd6c0 (LWP 24240) "IO iothread2"): + #0 futex_wait (futex_word=0x5611adb7d828, expected=2, private=0) at ../sysdeps/nptl/futex-internal.h:146 + #1 __GI___lll_lock_wait (futex=futex@entry=0x5611adb7d828, private=0) at lowlevellock.c:49 + #2 0x00007f8ab5a97501 in lll_mutex_lock_optimized (mutex=0x5611adb7d828) at pthread_mutex_lock.c:48 + #3 ___pthread_mutex_lock (mutex=0x5611adb7d828) at pthread_mutex_lock.c:93 + #4 0x00005611823f5482 in qemu_mutex_lock_impl (mutex=0x5611adb7d828, file=0x56118289daca "../block/throttle-groups.c", line=372) at ../util/qemu-thread-posix.c:94 + #5 0x00005611822b0b39 in throttle_group_co_io_limits_intercept (tgm=0x5611af1bb4d8, bytes=4096, direction=THROTTLE_READ) at ../block/throttle-groups.c:372 + #6 0x00005611822473b1 in blk_co_do_preadv_part (blk=0x5611af1bb490, offset=15972311040, bytes=4096, qiov=0x7f8aa4000f98, qiov_offset=0, flags=BDRV_REQ_REGISTERED_BUF) at ../block/block-backend.c:1354 + #7 0x0000561182247fa0 in blk_aio_read_entry (opaque=0x7f8aa4005910) at ../block/block-backend.c:1619 + #8 0x000056118241952e in coroutine_trampoline (i0=-1543497424, i1=32650) at ../util/coroutine-ucontext.c:175 + #9 0x00007f8ab5a56f70 in ?? () at ../sysdeps/unix/sysv/linux/x86_64/__start_context.S:66 from target:/lib64/libc.so.6 + #10 0x00007f8aad1ef190 in ?? () + #11 0x0000000000000000 in ?? () + +The lock is taken in line 386: +(gdb) p tg.lock +$1 = {lock = {__data = {__lock = 2, __count = 0, __owner = 24240, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, + __size = "\002\000\000\000\000\000\000\000\260^\000\000\001", '\000' , __align = 2}, file = 0x56118289daca "../block/throttle-groups.c", + line = 386, initialized = true} + +The solution is to use tg->lock to protect both ThreadGroup fields and +ThrottleGroupMember.throttled_reqs. It doesn't seem to be possible +to use separate locks because we need to first manipulate ThrottleGroup +fields, then schedule next coroutine using throttled_reqs and after than +update token field from ThrottleGroup depending on the throttled_reqs +state. + +Signed-off-by: Dmitry Guryanov +Message-ID: <20251208085528.890098-1-dmitry.guryanov@gmail.com> +Reviewed-by: Hanna Czenczek +Signed-off-by: Kevin Wolf +--- + 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 diff --git a/debian/patches/extra/0007-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch b/debian/patches/extra/0007-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch new file mode 100644 index 0000000..b530219 --- /dev/null +++ b/debian/patches/extra/0007-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Wed, 4 Mar 2026 13:28:00 +0100 +Subject: [PATCH] block: Never drop BLOCK_IO_ERROR with action=stop for rate + limiting + +Commit 2155d2dd introduced rate limiting for BLOCK_IO_ERROR to emit an +event only once a second. This makes sense for cases in which the guest +keeps running and can submit more requests that would possibly also fail +because there is a problem with the backend. + +However, if the error policy is configured so that the VM is stopped on +errors, this is both unnecessary because stopping the VM means that the +guest can't issue more requests and in fact harmful because stopping the +VM is an important state change that management tools need to keep track +of even if it happens more than once in a given second. If an event is +dropped, the management tool would see a VM randomly going to paused +state without an associated error, so it has a hard time deciding how to +handle the situation. + +This patch disables rate limiting for action=stop by not relying on the +event type alone any more in monitor_qapi_event_queue_no_reenter(), but +checking action for BLOCK_IO_ERROR, too. If the error is reported to the +guest or ignored, the rate limiting stays in place. + +Fixes: 2155d2dd7f73 ('block-backend: per-device throttling of BLOCK_IO_ERROR reports') +Signed-off-by: Kevin Wolf +Message-ID: <20260304122800.51923-1-kwolf@redhat.com> +Signed-off-by: Kevin Wolf +(cherry picked from commit 544ddbb6373d61292a0e2dc269809cd6bd5edec6) +Signed-off-by: Fiona Ebner +--- + monitor/monitor.c | 21 ++++++++++++++++++++- + qapi/block-core.json | 2 +- + 2 files changed, 21 insertions(+), 2 deletions(-) + +diff --git a/monitor/monitor.c b/monitor/monitor.c +index 07775784d4..58ddee50d8 100644 +--- a/monitor/monitor.c ++++ b/monitor/monitor.c +@@ -378,14 +378,33 @@ monitor_qapi_event_queue_no_reenter(QAPIEvent event, QDict *qdict) + { + MonitorQAPIEventConf *evconf; + MonitorQAPIEventState *evstate; ++ bool throttled; + + assert(event < QAPI_EVENT__MAX); + evconf = &monitor_qapi_event_conf[event]; + trace_monitor_protocol_event_queue(event, qdict, evconf->rate); ++ throttled = evconf->rate; ++ ++ /* ++ * Rate limit BLOCK_IO_ERROR only for action != "stop". ++ * ++ * If the VM is stopped after an I/O error, this is important information ++ * for the management tool to keep track of the state of QEMU and we can't ++ * merge any events. At the same time, stopping the VM means that the guest ++ * can't send additional requests and the number of events is already ++ * limited, so we can do without rate limiting. ++ */ ++ if (event == QAPI_EVENT_BLOCK_IO_ERROR) { ++ QDict *data = qobject_to(QDict, qdict_get(qdict, "data")); ++ const char *action = qdict_get_str(data, "action"); ++ if (!strcmp(action, "stop")) { ++ throttled = false; ++ } ++ } + + QEMU_LOCK_GUARD(&monitor_lock); + +- if (!evconf->rate) { ++ if (!throttled) { + /* Unthrottled event */ + monitor_qapi_event_emit(event, qdict); + } else { +diff --git a/qapi/block-core.json b/qapi/block-core.json +index b82af74256..4118d884f4 100644 +--- a/qapi/block-core.json ++++ b/qapi/block-core.json +@@ -5789,7 +5789,7 @@ + # .. note:: If action is "stop", a `STOP` event will eventually follow + # the `BLOCK_IO_ERROR` event. + # +-# .. note:: This event is rate-limited. ++# .. note:: This event is rate-limited, except if action is "stop". + # + # Since: 0.13 + # diff --git a/debian/patches/extra/0008-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch b/debian/patches/extra/0008-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch new file mode 100644 index 0000000..b8ee24b --- /dev/null +++ b/debian/patches/extra/0008-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Wolf +Date: Thu, 19 Feb 2026 21:24:46 +0100 +Subject: [PATCH] mirror: Fix missed dirty bitmap writes during startup + +Currently, mirror disables the block layer's dirty bitmap before its own +replacement is working. This means that during startup, there is a +window in which the allocation status of blocks in the source has +already been checked, but new writes coming in aren't tracked yet, +resulting in a corrupted copy: + +1. Dirty bitmap is disabled in mirror_start_job() +2. Some request are started in mirror_top_bs while s->job == NULL +3. mirror_dirty_init() -> bdrv_co_is_allocated_above() runs and because + the request hasn't completed yet, the block isn't allocated +4. The request completes, still sees s->job == NULL and skips the + bitmap, and nothing else will mark it dirty either + +One ingredient is that mirror_top_opaque->job is only set after the +job is fully initialized. For the rationale, see commit 32125b1460 +("mirror: Fix access of uninitialised fields during start"). + +Fix this by giving mirror_top_bs access to dirty_bitmap and enabling it +to track writes from the beginning. Disabling the block layer's tracking +and enabling the mirror_top_bs one happens in a drained section, so +there is no danger of races with in-flight requests any more. All of +this happens well before the block allocation status is checked, so we +can be sure that no writes will be missed. + +Cc: qemu-stable@nongnu.org +Closes: https://gitlab.com/qemu-project/qemu/-/issues/3273 +Fixes: 32125b14606a ('mirror: Fix access of uninitialised fields during start') +Signed-off-by: Kevin Wolf +Message-ID: <20260219202446.312493-1-kwolf@redhat.com> +Reviewed-by: Fiona Ebner +Tested-by: Jean-Louis Dupond +Signed-off-by: Kevin Wolf +(cherry picked from commit 167ef239fbdcc4bde126e47668bfc4839b873b19) +Signed-off-by: Fiona Ebner +--- + block/mirror.c | 52 +++++++++++++++++++++++++++++++------------------- + 1 file changed, 32 insertions(+), 20 deletions(-) + +diff --git a/block/mirror.c b/block/mirror.c +index bc982cb99a..fa1d975eb9 100644 +--- a/block/mirror.c ++++ b/block/mirror.c +@@ -99,6 +99,7 @@ typedef struct MirrorBlockJob { + + typedef struct MirrorBDSOpaque { + MirrorBlockJob *job; ++ BdrvDirtyBitmap *dirty_bitmap; + bool stop; + bool is_commit; + } MirrorBDSOpaque; +@@ -1675,9 +1676,11 @@ bdrv_mirror_top_do_write(BlockDriverState *bs, MirrorMethod method, + abort(); + } + +- if (!copy_to_target && s->job && s->job->dirty_bitmap) { +- qatomic_set(&s->job->actively_synced, false); +- bdrv_set_dirty_bitmap(s->job->dirty_bitmap, offset, bytes); ++ if (!copy_to_target) { ++ if (s->job) { ++ qatomic_set(&s->job->actively_synced, false); ++ } ++ bdrv_set_dirty_bitmap(s->dirty_bitmap, offset, bytes); + } + + if (ret < 0) { +@@ -1904,13 +1907,35 @@ static BlockJob *mirror_start_job( + + bdrv_drained_begin(bs); + ret = bdrv_append(mirror_top_bs, bs, errp); +- bdrv_drained_end(bs); +- + if (ret < 0) { ++ bdrv_drained_end(bs); ++ bdrv_unref(mirror_top_bs); ++ return NULL; ++ } ++ ++ bs_opaque->dirty_bitmap = bdrv_create_dirty_bitmap(mirror_top_bs, ++ granularity, ++ NULL, errp); ++ if (!bs_opaque->dirty_bitmap) { ++ bdrv_drained_end(bs); + bdrv_unref(mirror_top_bs); + return NULL; + } + ++ /* ++ * The mirror job doesn't use the block layer's dirty tracking because it ++ * needs to be able to switch seemlessly between background copy mode (which ++ * does need dirty tracking) and write blocking mode (which doesn't) and ++ * doing that would require draining the node. Instead, mirror_top_bs takes ++ * care of updating the dirty bitmap as appropriate. ++ * ++ * Note that write blocking mode only becomes effective after mirror_run() ++ * sets mirror_top_opaque->job (see should_copy_to_target()). Until then, ++ * we're still in background copy mode irrespective of @copy_mode. ++ */ ++ bdrv_disable_dirty_bitmap(bs_opaque->dirty_bitmap); ++ bdrv_drained_end(bs); ++ + /* Make sure that the source is not resized while the job is running */ + s = block_job_create(job_id, driver, NULL, mirror_top_bs, + BLK_PERM_CONSISTENT_READ, +@@ -2005,24 +2030,13 @@ static BlockJob *mirror_start_job( + s->base_overlay = bdrv_find_overlay(bs, base); + s->granularity = granularity; + s->buf_size = ROUND_UP(buf_size, granularity); ++ s->dirty_bitmap = bs_opaque->dirty_bitmap; + s->unmap = unmap; + if (auto_complete) { + s->should_complete = true; + } + bdrv_graph_rdunlock_main_loop(); + +- s->dirty_bitmap = bdrv_create_dirty_bitmap(s->mirror_top_bs, granularity, +- NULL, errp); +- if (!s->dirty_bitmap) { +- goto fail; +- } +- +- /* +- * The dirty bitmap is set by bdrv_mirror_top_do_write() when not in active +- * mode. +- */ +- bdrv_disable_dirty_bitmap(s->dirty_bitmap); +- + bdrv_graph_wrlock_drained(); + ret = block_job_add_bdrv(&s->common, "source", bs, 0, + BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | +@@ -2102,9 +2116,6 @@ fail: + g_free(s->replaces); + blk_unref(s->target); + bs_opaque->job = NULL; +- if (s->dirty_bitmap) { +- bdrv_release_dirty_bitmap(s->dirty_bitmap); +- } + job_early_fail(&s->common.job); + } + +@@ -2118,6 +2129,7 @@ fail: + bdrv_graph_wrunlock(); + bdrv_drained_end(bs); + ++ bdrv_release_dirty_bitmap(bs_opaque->dirty_bitmap); + bdrv_unref(mirror_top_bs); + + return NULL; diff --git a/debian/patches/extra/0009-virtio-gpu-virgl-Add-virtio-gpu-virgl-hostmem-region.patch b/debian/patches/extra/0009-virtio-gpu-virgl-Add-virtio-gpu-virgl-hostmem-region.patch new file mode 100644 index 0000000..10fbda0 --- /dev/null +++ b/debian/patches/extra/0009-virtio-gpu-virgl-Add-virtio-gpu-virgl-hostmem-region.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Akihiko Odaki +Date: Sat, 14 Feb 2026 13:33:36 +0900 +Subject: [PATCH] virtio-gpu-virgl: Add virtio-gpu-virgl-hostmem-region type + +Commit e27194e087ae ("virtio-gpu-virgl: correct parent for blob memory +region") made the name member of MemoryRegion unset, causing a NULL +pointer dereference[1]: +> Thread 2 "qemu-system-x86" received signal SIGSEGV, Segmentation fault. +> (gdb) bt +> #0 0x00007ffff56565e2 in __strcmp_evex () at /lib64/libc.so.6 +> #1 0x0000555555841bdb in find_fd (head=0x5555572337d0 , +> name=0x0, id=0) at ../migration/cpr.c:68 +> #2 cpr_delete_fd (name=name@entry=0x0, id=id@entry=0) at +> ../migration/cpr.c:77 +> #3 0x000055555582290a in qemu_ram_free (block=0x7ff7e93aa7f0) at +> ../system/physmem.c:2615 +> #4 0x000055555581ae02 in memory_region_finalize (obj=) +> at ../system/memory.c:1816 +> #5 0x0000555555a70ab9 in object_deinit (obj=, +> type=) at ../qom/object.c:715 +> #6 object_finalize (data=0x7ff7e936eff0) at ../qom/object.c:729 +> #7 object_unref (objptr=0x7ff7e936eff0) at ../qom/object.c:1232 +> #8 0x0000555555814fae in memory_region_unref (mr=) at +> ../system/memory.c:1848 +> #9 flatview_destroy (view=0x555559ed6c40) at ../system/memory.c:301 +> #10 0x0000555555bfc122 in call_rcu_thread (opaque=) at +> ../util/rcu.c:324 +> #11 0x0000555555bf17a7 in qemu_thread_start (args=0x555557b99520) at +> ../util/qemu-thread-posix.c:393 +> #12 0x00007ffff556f464 in start_thread () at /lib64/libc.so.6 +> #13 0x00007ffff55f25ac in __clone3 () at /lib64/libc.so.6 + +The intention of the aforementioned commit is to prevent a MemoryRegion +from parenting itself while its references is counted indendependently +of the device. To achieve the same goal, add a type of QOM objects that +count references and parent MemoryRegions. + +[1] https://lore.kernel.org/qemu-devel/4eb93d7a-1fa9-4b3c-8ad7-a2eb64f025a0@collabora.com/ + +Cc: qemu-stable@nongnu.org +Fixes: e27194e087ae ("virtio-gpu-virgl: correct parent for blob memory region") +Signed-off-by: Akihiko Odaki +Tested-by: Dmitry Osipenko +Tested-by: Joelle van Dyne +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260214-region-v1-1-229f00ae1f38@rsg.ci.i.u-tokyo.ac.jp> +(cherry picked from commit b2a279094c3b86667969cc645f7fb1087e08dd19) +Signed-off-by: Fiona Ebner +--- + hw/display/virtio-gpu-virgl.c | 54 +++++++++++++++++++++++++---------- + 1 file changed, 39 insertions(+), 15 deletions(-) + +diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c +index 741728cabb..4e515c4ef6 100644 +--- a/hw/display/virtio-gpu-virgl.c ++++ b/hw/display/virtio-gpu-virgl.c +@@ -52,11 +52,17 @@ virgl_get_egl_display(G_GNUC_UNUSED void *cookie) + + #if VIRGL_VERSION_MAJOR >= 1 + struct virtio_gpu_virgl_hostmem_region { ++ Object parent_obj; + MemoryRegion mr; + struct VirtIOGPU *g; + bool finish_unmapping; + }; + ++#define TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION "virtio-gpu-virgl-hostmem-region" ++ ++OBJECT_DECLARE_SIMPLE_TYPE(virtio_gpu_virgl_hostmem_region, ++ VIRTIO_GPU_VIRGL_HOSTMEM_REGION) ++ + static struct virtio_gpu_virgl_hostmem_region * + to_hostmem_region(MemoryRegion *mr) + { +@@ -70,14 +76,22 @@ static void virtio_gpu_virgl_resume_cmdq_bh(void *opaque) + virtio_gpu_process_cmdq(g); + } + +-static void virtio_gpu_virgl_hostmem_region_free(void *obj) ++/* ++ * MR could outlive the resource if MR's reference is held outside of ++ * virtio-gpu. In order to prevent unmapping resource while MR is alive, ++ * and thus, making the data pointer invalid, we will block virtio-gpu ++ * command processing until MR is fully unreferenced and freed. ++ */ ++static void virtio_gpu_virgl_hostmem_region_finalize(Object *obj) + { +- MemoryRegion *mr = MEMORY_REGION(obj); +- struct virtio_gpu_virgl_hostmem_region *vmr; ++ struct virtio_gpu_virgl_hostmem_region *vmr = VIRTIO_GPU_VIRGL_HOSTMEM_REGION(obj); + VirtIOGPUBase *b; + VirtIOGPUGL *gl; + +- vmr = to_hostmem_region(mr); ++ if (!vmr->g) { ++ return; ++ } ++ + vmr->finish_unmapping = true; + + b = VIRTIO_GPU_BASE(vmr->g); +@@ -92,11 +106,26 @@ static void virtio_gpu_virgl_hostmem_region_free(void *obj) + qemu_bh_schedule(gl->cmdq_resume_bh); + } + ++static const TypeInfo virtio_gpu_virgl_hostmem_region_info = { ++ .parent = TYPE_OBJECT, ++ .name = TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION, ++ .instance_size = sizeof(struct virtio_gpu_virgl_hostmem_region), ++ .instance_finalize = virtio_gpu_virgl_hostmem_region_finalize ++}; ++ ++static void virtio_gpu_virgl_types(void) ++{ ++ type_register_static(&virtio_gpu_virgl_hostmem_region_info); ++} ++ ++type_init(virtio_gpu_virgl_types) ++ + static int + virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, + struct virtio_gpu_virgl_resource *res, + uint64_t offset) + { ++ g_autofree char *name = NULL; + struct virtio_gpu_virgl_hostmem_region *vmr; + VirtIOGPUBase *b = VIRTIO_GPU_BASE(g); + MemoryRegion *mr; +@@ -117,21 +146,16 @@ virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, + } + + vmr = g_new0(struct virtio_gpu_virgl_hostmem_region, 1); ++ name = g_strdup_printf("blob[%" PRIu32 "]", res->base.resource_id); ++ object_initialize_child(OBJECT(g), name, vmr, ++ TYPE_VIRTIO_GPU_VIRGL_HOSTMEM_REGION); + vmr->g = g; + + mr = &vmr->mr; +- memory_region_init_ram_ptr(mr, OBJECT(mr), NULL, size, data); ++ memory_region_init_ram_ptr(mr, OBJECT(vmr), "mr", size, data); + memory_region_add_subregion(&b->hostmem, offset, mr); + memory_region_set_enabled(mr, true); + +- /* +- * MR could outlive the resource if MR's reference is held outside of +- * virtio-gpu. In order to prevent unmapping resource while MR is alive, +- * and thus, making the data pointer invalid, we will block virtio-gpu +- * command processing until MR is fully unreferenced and freed. +- */ +- OBJECT(mr)->free = virtio_gpu_virgl_hostmem_region_free; +- + res->mr = mr; + + trace_virtio_gpu_cmd_res_map_blob(res->base.resource_id, vmr, mr); +@@ -163,7 +187,7 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, + * 1. Begin async unmapping with memory_region_del_subregion() + * and suspend/block cmd processing. + * 2. Wait for res->mr to be freed and cmd processing resumed +- * asynchronously by virtio_gpu_virgl_hostmem_region_free(). ++ * asynchronously by virtio_gpu_virgl_hostmem_region_finalize(). + * 3. Finish the unmapping with final virgl_renderer_resource_unmap(). + */ + if (vmr->finish_unmapping) { +@@ -186,7 +210,7 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, + /* memory region owns self res->mr object and frees it by itself */ + memory_region_set_enabled(mr, false); + memory_region_del_subregion(&b->hostmem, mr); +- object_unref(OBJECT(mr)); ++ object_unparent(OBJECT(vmr)); + } + + return 0; diff --git a/debian/patches/extra/0010-virtio-gpu-Ensure-BHs-are-invoked-only-from-main-loo.patch b/debian/patches/extra/0010-virtio-gpu-Ensure-BHs-are-invoked-only-from-main-loo.patch new file mode 100644 index 0000000..dc53ad1 --- /dev/null +++ b/debian/patches/extra/0010-virtio-gpu-Ensure-BHs-are-invoked-only-from-main-loo.patch @@ -0,0 +1,123 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dmitry Osipenko +Date: Wed, 4 Mar 2026 16:50:31 +0000 +Subject: [PATCH] virtio-gpu: Ensure BHs are invoked only from main-loop thread +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +QEMU's display GL core is tied to main-loop thread and virtio-gpu +interacts with display while processing GPU commands. Virtio-gpu BHs +work in generic AIO context that can be invoked on vCPU thread, while +GL and UI toolkits are bound to the main-loop thread. + +Make virtio-gpu BHs use iohandler AIO context that is handled in a +main-loop thread only. + + 0 SDL_GL_MakeCurrent() (libSDL3) + 1 SDL_GL_MakeCurrent_REAL() (libSDL2) + 2 sdl2_gl_make_context_current() (ui/sdl2-gl.c:201) + 3 make_current() (virglrenderer.c:639) + 4 vrend_finish_context_switch() (vrend_renderer.c:11630) + 5 vrend_hw_switch_context() (vrend_renderer.c:11613) + 6 vrend_renderer_force_ctx_0() (vrend_renderer.c:12986) + 7 virgl_renderer_force_ctx_0() (virglrenderer.c:460) + 8 virtio_gpu_virgl_process_cmd() (virtio-gpu-virgl.c:1013) + 9 virtio_gpu_process_cmdq() (virtio-gpu.c:1050) + 10 virtio_gpu_gl_handle_ctrl() (virtio-gpu-gl.c:86) + 11 aio_bh_poll() (util/async.c) + 12 aio_poll() (util/aio-posix.c) + 13 blk_pwrite() (block/block-gen.c:1985) + 14 pflash_update() (pflash_cfi01.c:396) + 15 pflash_write() (pflash_cfi01.c:541) + 16 memory_region_dispatch_write() (system/memory.c:1554) + 17 flatview_write() (system/physmem.c:3333) + 18 address_space_write() (system/physmem.c:3453) + 19 kvm_cpu_exec() (accel/kvm/kall-all.c:3248) + 20 kvm_vcpu_thread_fn() (accel/kvm/kaccel-ops.c:53) + +Cc: qemu-stable@nongnu.org +Signed-off-by: Dmitry Osipenko +Message-ID: <20260303151422.977399-8-dmitry.osipenko@collabora.com> +Message-ID: <20260304165043.1437519-10-alex.bennee@linaro.org> +Signed-off-by: Alex Bennée +(cherry picked from commit 235f9b36383e4cc7a790bca51eddbe38edd5438c) +Signed-off-by: Fiona Ebner +--- + hw/display/virtio-gpu-virgl.c | 6 +++--- + hw/display/virtio-gpu.c | 6 +++--- + hw/virtio/virtio.c | 10 ++++++++++ + include/hw/virtio/virtio.h | 10 ++++++++++ + 4 files changed, 26 insertions(+), 6 deletions(-) + +diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c +index 4e515c4ef6..1129301d91 100644 +--- a/hw/display/virtio-gpu-virgl.c ++++ b/hw/display/virtio-gpu-virgl.c +@@ -1203,9 +1203,9 @@ int virtio_gpu_virgl_init(VirtIOGPU *g) + } + + #if VIRGL_VERSION_MAJOR >= 1 +- gl->cmdq_resume_bh = aio_bh_new(qemu_get_aio_context(), +- virtio_gpu_virgl_resume_cmdq_bh, +- g); ++ gl->cmdq_resume_bh = virtio_bh_io_new_guarded(DEVICE(g), ++ virtio_gpu_virgl_resume_cmdq_bh, ++ g); + #endif + + return 0; +diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c +index 43e88a4daf..ad1ebc0fcd 100644 +--- a/hw/display/virtio-gpu.c ++++ b/hw/display/virtio-gpu.c +@@ -1526,9 +1526,9 @@ void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) + + g->ctrl_vq = virtio_get_queue(vdev, 0); + g->cursor_vq = virtio_get_queue(vdev, 1); +- g->ctrl_bh = virtio_bh_new_guarded(qdev, virtio_gpu_ctrl_bh, g); +- g->cursor_bh = virtio_bh_new_guarded(qdev, virtio_gpu_cursor_bh, g); +- g->reset_bh = qemu_bh_new(virtio_gpu_reset_bh, g); ++ g->ctrl_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_ctrl_bh, g); ++ g->cursor_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_cursor_bh, g); ++ g->reset_bh = virtio_bh_io_new_guarded(qdev, virtio_gpu_reset_bh, g); + qemu_cond_init(&g->reset_cond); + QTAILQ_INIT(&g->reslist); + QTAILQ_INIT(&g->cmdq); +diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c +index 257cda506a..683026adc4 100644 +--- a/hw/virtio/virtio.c ++++ b/hw/virtio/virtio.c +@@ -4475,3 +4475,13 @@ QEMUBH *virtio_bh_new_guarded_full(DeviceState *dev, + return qemu_bh_new_full(cb, opaque, name, + &transport->mem_reentrancy_guard); + } ++ ++QEMUBH *virtio_bh_io_new_guarded_full(DeviceState *dev, ++ QEMUBHFunc *cb, void *opaque, ++ const char *name) ++{ ++ DeviceState *transport = qdev_get_parent_bus(dev)->parent; ++ ++ return aio_bh_new_full(iohandler_get_aio_context(), cb, opaque, name, ++ &transport->mem_reentrancy_guard); ++} +diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h +index d97529c3f1..d5bd921581 100644 +--- a/include/hw/virtio/virtio.h ++++ b/include/hw/virtio/virtio.h +@@ -547,4 +547,14 @@ QEMUBH *virtio_bh_new_guarded_full(DeviceState *dev, + #define virtio_bh_new_guarded(dev, cb, opaque) \ + virtio_bh_new_guarded_full((dev), (cb), (opaque), (stringify(cb))) + ++/* ++ * The "_io" variant runs BH only on a main-loop thread, while generic BH ++ * may run on a vCPU thread. ++ */ ++QEMUBH *virtio_bh_io_new_guarded_full(DeviceState *dev, ++ QEMUBHFunc *cb, void *opaque, ++ const char *name); ++#define virtio_bh_io_new_guarded(dev, cb, opaque) \ ++ virtio_bh_io_new_guarded_full((dev), (cb), (opaque), (stringify(cb))) ++ + #endif diff --git a/debian/patches/extra/0011-hw-i2c-aspeed_i2c-Fix-out-of-bounds-read-in-I2C-MMIO.patch b/debian/patches/extra/0011-hw-i2c-aspeed_i2c-Fix-out-of-bounds-read-in-I2C-MMIO.patch new file mode 100644 index 0000000..3fdc98c --- /dev/null +++ b/debian/patches/extra/0011-hw-i2c-aspeed_i2c-Fix-out-of-bounds-read-in-I2C-MMIO.patch @@ -0,0 +1,136 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jamin Lin +Date: Tue, 10 Feb 2026 02:43:32 +0000 +Subject: [PATCH] hw/i2c/aspeed_i2c: Fix out-of-bounds read in I2C MMIO + handlers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The ASPEED I2C controller exposes a per-bus MMIO window of 0x80 bytes on +AST2600/AST1030/AST2700, but the backing regs[] array was sized for only +28 dwords (0x70 bytes). This allows guest reads in the range [0x70..0x7f] +to index past the end of regs[]. + +Fix this by: +- Sizing ASPEED_I2C_NEW_NUM_REG to match the 0x80-byte window + (0x80 >> 2 = 32 dwords). +- Avoiding an unconditional pre-read from regs[] in the legacy/new read + handlers. Initialize the return value to -1 and only read regs[] for + offsets that are explicitly handled/valid, leaving invalid offsets to + return -1 with a guest error log. + +Signed-off-by: Jamin Lin +Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3290 +Reviewed-by: Cédric Le Goater +Link: https://lore.kernel.org/qemu-devel/20260210024331.3984696-2-jamin_lin@aspeedtech.com +Signed-off-by: Cédric Le Goater +(cherry picked from commit c2c5beec42bf9872b37e78b9e259132df7435cb5) +Signed-off-by: Fiona Ebner +--- + hw/i2c/aspeed_i2c.c | 22 ++++++++++------------ + include/hw/i2c/aspeed_i2c.h | 3 +-- + 2 files changed, 11 insertions(+), 14 deletions(-) + +diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c +index c48fa2050b..c455c3eb7c 100644 +--- a/hw/i2c/aspeed_i2c.c ++++ b/hw/i2c/aspeed_i2c.c +@@ -94,7 +94,7 @@ static uint64_t aspeed_i2c_bus_old_read(AspeedI2CBus *bus, hwaddr offset, + unsigned size) + { + AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); +- uint64_t value = bus->regs[offset / sizeof(*bus->regs)]; ++ uint64_t value = -1; + + switch (offset) { + case A_I2CD_FUN_CTRL: +@@ -105,7 +105,7 @@ static uint64_t aspeed_i2c_bus_old_read(AspeedI2CBus *bus, hwaddr offset, + case A_I2CD_DEV_ADDR: + case A_I2CD_POOL_CTRL: + case A_I2CD_BYTE_BUF: +- /* Value is already set, don't do anything. */ ++ value = bus->regs[offset / sizeof(*bus->regs)]; + break; + case A_I2CD_CMD: + value = SHARED_FIELD_DP32(value, BUS_BUSY_STS, i2c_bus_busy(bus->bus)); +@@ -113,21 +113,20 @@ static uint64_t aspeed_i2c_bus_old_read(AspeedI2CBus *bus, hwaddr offset, + case A_I2CD_DMA_ADDR: + if (!aic->has_dma) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__); +- value = -1; + break; + } ++ value = bus->regs[offset / sizeof(*bus->regs)]; + break; + case A_I2CD_DMA_LEN: + if (!aic->has_dma) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__); +- value = -1; ++ break; + } ++ value = bus->regs[offset / sizeof(*bus->regs)]; + break; +- + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset); +- value = -1; + break; + } + +@@ -139,7 +138,7 @@ static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset, + unsigned size) + { + AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller); +- uint64_t value = bus->regs[offset / sizeof(*bus->regs)]; ++ uint64_t value = -1; + + switch (offset) { + case A_I2CC_FUN_CTRL: +@@ -159,13 +158,12 @@ static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset, + case A_I2CS_CMD: + case A_I2CS_INTR_CTRL: + case A_I2CS_DMA_LEN_STS: +- /* Value is already set, don't do anything. */ ++ case A_I2CS_INTR_STS: ++ value = bus->regs[offset / sizeof(*bus->regs)]; + break; + case A_I2CC_DMA_ADDR: + value = extract64(bus->dma_dram_offset, 0, 32); + break; +- case A_I2CS_INTR_STS: +- break; + case A_I2CM_CMD: + value = SHARED_FIELD_DP32(value, BUS_BUSY_STS, i2c_bus_busy(bus->bus)); + break; +@@ -176,13 +174,13 @@ static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset, + if (!aic->has_dma64) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA 64 bits support\n", + __func__); +- value = -1; ++ break; + } ++ value = bus->regs[offset / sizeof(*bus->regs)]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset); +- value = -1; + break; + } + +diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h +index 2daacc10ce..efe8b1a0c5 100644 +--- a/include/hw/i2c/aspeed_i2c.h ++++ b/include/hw/i2c/aspeed_i2c.h +@@ -36,8 +36,7 @@ OBJECT_DECLARE_TYPE(AspeedI2CState, AspeedI2CClass, ASPEED_I2C) + #define ASPEED_I2C_NR_BUSSES 16 + #define ASPEED_I2C_SHARE_POOL_SIZE 0x800 + #define ASPEED_I2C_BUS_POOL_SIZE 0x20 +-#define ASPEED_I2C_OLD_NUM_REG 11 +-#define ASPEED_I2C_NEW_NUM_REG 28 ++#define ASPEED_I2C_NEW_NUM_REG (0x80 >> 2) + + #define A_I2CD_M_STOP_CMD BIT(5) + #define A_I2CD_M_RX_CMD BIT(3) diff --git a/debian/patches/extra/0012-target-arm-Account-for-SME-in-aarch64_sve_narrow_vq-.patch b/debian/patches/extra/0012-target-arm-Account-for-SME-in-aarch64_sve_narrow_vq-.patch new file mode 100644 index 0000000..ed8c4ae --- /dev/null +++ b/debian/patches/extra/0012-target-arm-Account-for-SME-in-aarch64_sve_narrow_vq-.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Peter Maydell +Date: Wed, 18 Feb 2026 18:40:13 +0000 +Subject: [PATCH] target/arm: Account for SME in aarch64_sve_narrow_vq() + assertion +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In aarch64_sve_narrow_vq() we assert that the new VQ is within +the maximum supported range for the CPU. We forgot to update +this to account for SME, which might have a different maximum. + +Update the assert to permit any VQ which is valid for either +SVE or SME. + +Cc: qemu-stable@nongnu.org +Signed-off-by: Peter Maydell +Reviewed-by: Alex Bennée +Reviewed-by: Philippe Mathieu-Daudé +Reviewed-by: Richard Henderson +Message-id: 20260202133353.2231685-2-peter.maydell@linaro.org +(cherry picked from commit 42eab40a12f12f044a5ca7b7d889d9a1f0d172ee) +Signed-off-by: Fiona Ebner +--- + target/arm/helper.c | 2 +- + target/arm/internals.h | 9 +++++++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/target/arm/helper.c b/target/arm/helper.c +index 633d314edf..5d31c551e1 100644 +--- a/target/arm/helper.c ++++ b/target/arm/helper.c +@@ -10058,7 +10058,7 @@ void aarch64_sve_narrow_vq(CPUARMState *env, unsigned vq) + uint64_t pmask; + + assert(vq >= 1 && vq <= ARM_MAX_VQ); +- assert(vq <= env_archcpu(env)->sve_max_vq); ++ assert(vq <= arm_max_vq(env_archcpu(env))); + + /* Zap the high bits of the zregs. */ + for (i = 0; i < 32; i++) { +diff --git a/target/arm/internals.h b/target/arm/internals.h +index 75677945af..d5f6d6546f 100644 +--- a/target/arm/internals.h ++++ b/target/arm/internals.h +@@ -1807,6 +1807,15 @@ static inline uint64_t arm_mdcr_el2_eff(CPUARMState *env) + ((1 << (1 - 1)) | (1 << (2 - 1)) | \ + (1 << (4 - 1)) | (1 << (8 - 1)) | (1 << (16 - 1))) + ++/* ++ * Return the maximum SVE/SME VQ for this CPU. This defines ++ * the maximum possible size of the Zn vector registers. ++ */ ++static inline int arm_max_vq(ARMCPU *cpu) ++{ ++ return MAX(cpu->sve_max_vq, cpu->sme_max_vq); ++} ++ + /* + * Return true if it is possible to take a fine-grained-trap to EL2. + */ diff --git a/debian/patches/extra/0013-target-arm-Fix-feature-check-in-DO_SVE2_RRX-DO_SVE2_.patch b/debian/patches/extra/0013-target-arm-Fix-feature-check-in-DO_SVE2_RRX-DO_SVE2_.patch new file mode 100644 index 0000000..880965c --- /dev/null +++ b/debian/patches/extra/0013-target-arm-Fix-feature-check-in-DO_SVE2_RRX-DO_SVE2_.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Peter Maydell +Date: Wed, 18 Feb 2026 18:40:13 +0000 +Subject: [PATCH] target/arm: Fix feature check in DO_SVE2_RRX, DO_SVE2_RRX_TB + +In the macros DO_SVE2_RRX and DO_SVE2_RRX_TB we use the +feature check aa64_sve, thus exposing this set of instructions +in SVE as well as SVE2. Use aa64_sve2 instead, so they UNDEF +on an SVE1-only CPU as they should. + +Strictly, the condition here should be "SVE2 or SME"; but we +will correct that in a following commit with all the other +missing "or SME" checks. + +Cc: qemu-stable@nongnu.org +Signed-off-by: Peter Maydell +Reviewed-by: Richard Henderson +Reviewed-by: Manos Pitsidianakis +Message-id: 20260202133353.2231685-4-peter.maydell@linaro.org +(cherry picked from commit ee5bf0962ed6e0eb42d6bc9bfb3687f2408e3580) +Signed-off-by: Fiona Ebner +--- + target/arm/tcg/translate-sve.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/target/arm/tcg/translate-sve.c b/target/arm/tcg/translate-sve.c +index 07b827fa8e..d69a2f5d75 100644 +--- a/target/arm/tcg/translate-sve.c ++++ b/target/arm/tcg/translate-sve.c +@@ -3769,7 +3769,7 @@ TRANS_FEAT(UDOT_zzxw_2s, aa64_sme2_or_sve2p1, gen_gvec_ool_arg_zzxz, + gen_helper_gvec_udot_idx_2h, a) + + #define DO_SVE2_RRX(NAME, FUNC) \ +- TRANS_FEAT(NAME, aa64_sve, gen_gvec_ool_zzz, FUNC, \ ++ TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_zzz, FUNC, \ + a->rd, a->rn, a->rm, a->index) + + DO_SVE2_RRX(MUL_zzx_h, gen_helper_gvec_mul_idx_h) +@@ -3787,7 +3787,7 @@ DO_SVE2_RRX(SQRDMULH_zzx_d, gen_helper_sve2_sqrdmulh_idx_d) + #undef DO_SVE2_RRX + + #define DO_SVE2_RRX_TB(NAME, FUNC, TOP) \ +- TRANS_FEAT(NAME, aa64_sve, gen_gvec_ool_zzz, FUNC, \ ++ TRANS_FEAT(NAME, aa64_sve2, gen_gvec_ool_zzz, FUNC, \ + a->rd, a->rn, a->rm, (a->index << 1) | TOP) + + DO_SVE2_RRX_TB(SQDMULLB_zzx_s, gen_helper_sve2_sqdmull_idx_s, false) diff --git a/debian/patches/extra/0014-target-arm-tcg-Allow-SVE-RAX1-in-SME2p1-streaming-mo.patch b/debian/patches/extra/0014-target-arm-tcg-Allow-SVE-RAX1-in-SME2p1-streaming-mo.patch new file mode 100644 index 0000000..d24758e --- /dev/null +++ b/debian/patches/extra/0014-target-arm-tcg-Allow-SVE-RAX1-in-SME2p1-streaming-mo.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Peter Maydell +Date: Wed, 18 Feb 2026 18:40:13 +0000 +Subject: [PATCH] target/arm/tcg: Allow SVE RAX1 in SME2p1 streaming mode + +The SVE RAX1 instruction is permitted in SME streaming mode starting +from SME2p1. We forgot to allow this relaxation when we implemented +SME2p1. + +Cc: qemu-stable@nongnu.org +Fixes: 7b1613a1020d2 ("target/arm: Enable FEAT_SME2p1 on -cpu max") +Signed-off-by: Peter Maydell +Reviewed-by: Richard Henderson +Message-id: 20260202133353.2231685-5-peter.maydell@linaro.org +(cherry picked from commit 433097a2242120918090201129e5fbb8e16b3e34) +Signed-off-by: Fiona Ebner +--- + target/arm/tcg/translate-sve.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/target/arm/tcg/translate-sve.c b/target/arm/tcg/translate-sve.c +index d69a2f5d75..76e4a6c52c 100644 +--- a/target/arm/tcg/translate-sve.c ++++ b/target/arm/tcg/translate-sve.c +@@ -7803,8 +7803,17 @@ TRANS_FEAT_NONSTREAMING(SM4E, aa64_sve2_sm4, gen_gvec_ool_arg_zzz, + TRANS_FEAT_NONSTREAMING(SM4EKEY, aa64_sve2_sm4, gen_gvec_ool_arg_zzz, + gen_helper_crypto_sm4ekey, a, 0) + +-TRANS_FEAT_NONSTREAMING(RAX1, aa64_sve2_sha3, gen_gvec_fn_arg_zzz, +- gen_gvec_rax1, a) ++static bool trans_RAX1(DisasContext *s, arg_RAX1 *a) ++{ ++ if (!dc_isar_feature(aa64_sve2_sha3, s)) { ++ return false; ++ } ++ if (!dc_isar_feature(aa64_sme2p1, s)) { ++ /* SME2p1 adds this as valid in streaming SVE mode */ ++ s->is_nonstreaming = true; ++ } ++ return gen_gvec_fn_arg_zzz(s, gen_gvec_rax1, a); ++} + + TRANS_FEAT(FCVTNT_sh, aa64_sve2, gen_gvec_fpst_arg_zpz, + gen_helper_sve2_fcvtnt_sh, a, 0, FPST_A64) diff --git a/debian/patches/extra/0015-target-arm-Don-t-let-sme-on-downgrade-SME.patch b/debian/patches/extra/0015-target-arm-Don-t-let-sme-on-downgrade-SME.patch new file mode 100644 index 0000000..b6859b0 --- /dev/null +++ b/debian/patches/extra/0015-target-arm-Don-t-let-sme-on-downgrade-SME.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Peter Maydell +Date: Wed, 18 Feb 2026 18:40:13 +0000 +Subject: [PATCH] target/arm: Don't let 'sme=on' downgrade SME + +In our handling of the boolean 'sme' CPU property, we write this 0/1 +value directly to ID_AA64PFR1_EL1.SME. This worked when the only +valid values in that field were 0 (for no SME) and 1 (for SME1). +However, with the addition of SME2 the SME field can now also read 2. +This means that "-cpu max,sme=on" will result in an inconsistent set +of ID registers, where ID_AA64PFR1_EL1.SME claims SME1 but +ID_AA64SMFR0_EL1.SMEver claims SME2p1. This isn't a valid thing to +report, and confuses Linux into reporting SME2 to userspace but not +actually enabling userspace access for it. + +Fix this bug by having arm_cpu_sme_finalize() fix up the +ID_AA64PFR1_EL1.SME field to match ID_AA64SMFR0.SMEver. This means +the "sme" property's semantics are "off" for "no SME" and "on" for +"enable at whatever the default SME version this CPU provides is". + +Update the documentation to clarify what 'sve=on' and 'sme=on' do. +(We don't have the equivalent bug for 'sve=on' because +ID_AA64PFR0_EL1.SVE only has 0 and 1 as valid values, but the +semantics of the property are the same.) + +Cc: qemu-stable@nongnu.org +Signed-off-by: Peter Maydell +Reviewed-by: Richard Henderson +Reviewed-by: Manos Pitsidianakis +Message-id: 20260202133353.2231685-6-peter.maydell@linaro.org +(cherry picked from commit aeb3c147fc4a1eb9a73f9f10923fc06def088aeb) +Signed-off-by: Fiona Ebner +--- + docs/system/arm/cpu-features.rst | 10 ++++++++++ + target/arm/cpu64.c | 15 +++++++++++++++ + 2 files changed, 25 insertions(+) + +diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst +index 37d5dfd15b..024119449c 100644 +--- a/docs/system/arm/cpu-features.rst ++++ b/docs/system/arm/cpu-features.rst +@@ -318,6 +318,11 @@ SVE CPU Property Parsing Semantics + provided an error will be generated. To avoid this error, one must + enable at least one vector length prior to enabling SVE. + ++ 10) Enabling SVE (with ``sve=on`` or by default) enables all the SVE ++ sub-features that the CPU supports (for example, it may also ++ enable SVE2). There are not generally any lower-level controls ++ for disabling specific SVE sub-features. ++ + SVE CPU Property Examples + ------------------------- + +@@ -430,6 +435,11 @@ and all vector lengths must be powers of 2. The maximum vector + length supported by qemu is 2048 bits. Otherwise, there are no + additional constraints on the set of vector lengths supported by SME. + ++As with SVE, ``sme=on`` enables all the SME sub-features the CPU ++supports (for example, it may also enable SME2), and there are ++no lower-level controls for fine-grained disabling of specific ++SME sub-features. ++ + SME User-mode Default Vector Length Property + -------------------------------------------- + +diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c +index ae84d8e420..2082672dea 100644 +--- a/target/arm/cpu64.c ++++ b/target/arm/cpu64.c +@@ -363,6 +363,16 @@ void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp) + + cpu->sme_vq.map = vq_map; + cpu->sme_max_vq = 32 - clz32(vq_map); ++ ++ /* ++ * The "sme" property setter writes a bool value into ID_AA64PFR1_EL1.SME ++ * (and at this point we know it's not 0). Correct that value to report ++ * the same SME version as ID_AA64SMFR0_EL1.SMEver. ++ */ ++ if (FIELD_EX64_IDREG(&cpu->isar, ID_AA64SMFR0, SMEVER) != 0) { ++ /* SME2 or better */ ++ FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR1, SME, 2); ++ } + } + + static bool cpu_arm_get_sme(Object *obj, Error **errp) +@@ -375,6 +385,11 @@ static void cpu_arm_set_sme(Object *obj, bool value, Error **errp) + { + ARMCPU *cpu = ARM_CPU(obj); + ++ /* ++ * For now, write 0 for "off" and 1 for "on" into the PFR1 field. ++ * We will correct this value to report the right SME ++ * level (SME vs SME2) in arm_cpu_sme_finalize() later. ++ */ + FIELD_DP64_IDREG(&cpu->isar, ID_AA64PFR1, SME, value); + } + diff --git a/debian/patches/extra/0016-target-arm-set-the-correct-TI-bits-for-WFIT-traps.patch b/debian/patches/extra/0016-target-arm-set-the-correct-TI-bits-for-WFIT-traps.patch new file mode 100644 index 0000000..9b10110 --- /dev/null +++ b/debian/patches/extra/0016-target-arm-set-the-correct-TI-bits-for-WFIT-traps.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Alex=20Benn=C3=A9e?= +Date: Thu, 26 Feb 2026 11:27:18 +0000 +Subject: [PATCH] target/arm: set the correct TI bits for WFIT traps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The WFIT trap should be reported as 0b10. + +Cc: qemu-stable@nongnu.org +Signed-off-by: Alex Bennée +Reviewed-by: Gustavo Romero +Message-id: 20260220171945.1065102-1-alex.bennee@linaro.org +Reviewed-by: Peter Maydell +Signed-off-by: Peter Maydell +(cherry picked from commit 662fd548a027c9362df71ebfc0c9cdd7b1f349fb) +Signed-off-by: Fiona Ebner +--- + target/arm/tcg/op_helper.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c +index 4fbd219555..9c0651f000 100644 +--- a/target/arm/tcg/op_helper.c ++++ b/target/arm/tcg/op_helper.c +@@ -448,7 +448,7 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) + + if (target_el) { + env->pc -= 4; +- raise_exception(env, excp, syn_wfx(1, 0xe, 0, false), target_el); ++ raise_exception(env, excp, syn_wfx(1, 0xe, 2, false), target_el); + } + + if (uadd64_overflow(timeout, offset, &nexttick)) { diff --git a/debian/patches/extra/0017-aio-posix-notify-main-loop-when-SQEs-are-queued.patch b/debian/patches/extra/0017-aio-posix-notify-main-loop-when-SQEs-are-queued.patch new file mode 100644 index 0000000..ddc6803 --- /dev/null +++ b/debian/patches/extra/0017-aio-posix-notify-main-loop-when-SQEs-are-queued.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jens Axboe +Date: Wed, 18 Feb 2026 15:09:58 -0500 +Subject: [PATCH] aio-posix: notify main loop when SQEs are queued + +When a vCPU thread handles MMIO (holding BQL), aio_co_enter() runs the +block I/O coroutine inline on the vCPU thread because +qemu_get_current_aio_context() returns the main AioContext when BQL is +held. The coroutine calls luring_co_submit() which queues an SQE via +fdmon_io_uring_add_sqe(), but the actual io_uring_submit() only happens +in gsource_prepare() on the main loop thread. + +Since the coroutine ran inline (not via aio_co_schedule()), no BH is +scheduled and aio_notify() is never called. The main loop remains asleep +in ppoll() with up to a 499ms timeout, leaving the SQE unsubmitted until +the next timer fires. + +Fix this by calling aio_notify() after queuing the SQE. This wakes the +main loop via the eventfd so it can run gsource_prepare() and submit the +pending SQE promptly. + +This is a generic fix that benefits all devices using aio=io_uring. +Without it, AHCI/SATA devices see MUCH worse I/O latency since they use +MMIO (not ioeventfd like virtio) and have no other mechanism to wake the +main loop after queuing block I/O. + +This is usually a bit hard to detect, as it also relies on the ppoll +loop not waking up for other activity, and micro benchmarks tend not to +see it because they don't have any real processing time. With a +synthetic test case that has a few usleep() to simulate processing of +read data, it's very noticeable. The below example reads 128MB with +O_DIRECT in 128KB chunks in batches of 16, and has a 1ms delay before +each batch submit, and a 1ms delay after processing each completion. +Running it on /dev/sda yields: + +time sudo ./iotest /dev/sda + +________________________________________________________ +Executed in 25.76 secs fish external + usr time 6.19 millis 783.00 micros 5.41 millis + sys time 12.43 millis 642.00 micros 11.79 millis + +while on a virtio-blk or NVMe device we get: + +time sudo ./iotest /dev/vdb + +________________________________________________________ +Executed in 1.25 secs fish external + usr time 1.40 millis 0.30 millis 1.10 millis + sys time 17.61 millis 1.43 millis 16.18 millis + +time sudo ./iotest /dev/nvme0n1 + +________________________________________________________ +Executed in 1.26 secs fish external + usr time 6.11 millis 0.52 millis 5.59 millis + sys time 13.94 millis 1.50 millis 12.43 millis + +where the latter are consistent. If we run the same test but keep the +socket for the ssh connection active by having activity there, then +the sda test looks as follows: + +time sudo ./iotest /dev/sda + +________________________________________________________ +Executed in 1.23 secs fish external + usr time 2.70 millis 39.00 micros 2.66 millis + sys time 4.97 millis 977.00 micros 3.99 millis + +as now the ppoll loop is woken all the time anyway. + +After this fix, on an idle system: + +time sudo ./iotest /dev/sda + +________________________________________________________ +Executed in 1.30 secs fish external + usr time 2.14 millis 0.14 millis 2.00 millis + sys time 16.93 millis 1.16 millis 15.76 millis + +Signed-off-by: Jens Axboe +Message-Id: <07d701b9-3039-4f9b-99a2-abeae51146a5@kernel.dk> +Reviewed-by: Kevin Wolf +[Generalize the comment since this applies to all vCPU thread activity, +not just coroutines, as suggested by Kevin Wolf . +--Stefan] +Signed-off-by: Stefan Hajnoczi +(cherry picked from commit 2ae361ef1d7d526b07ff88d854552e2d009bfb1b) +Signed-off-by: Fiona Ebner +--- + util/aio-posix.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/util/aio-posix.c b/util/aio-posix.c +index e24b955fd9..488d964611 100644 +--- a/util/aio-posix.c ++++ b/util/aio-posix.c +@@ -23,6 +23,7 @@ + #include "qemu/rcu_queue.h" + #include "qemu/sockets.h" + #include "qemu/cutils.h" ++#include "system/iothread.h" + #include "trace.h" + #include "aio-posix.h" + +@@ -813,5 +814,13 @@ void aio_add_sqe(void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque), + { + AioContext *ctx = qemu_get_current_aio_context(); + ctx->fdmon_ops->add_sqe(ctx, prep_sqe, opaque, cqe_handler); ++ ++ /* ++ * Wake the main loop if it is sleeping in ppoll(). When a vCPU thread ++ * queues SQEs, the actual io_uring_submit() only happens in ++ * gsource_prepare() in the main loop thread. Without this notify, the ++ * main loop thread's ppoll() can sleep up to 499ms before submitting. ++ */ ++ aio_notify(ctx); + } + #endif /* CONFIG_LINUX_IO_URING */ diff --git a/debian/patches/extra/0018-fdmon-io_uring-check-CQ-ring-directly-in-gsource_che.patch b/debian/patches/extra/0018-fdmon-io_uring-check-CQ-ring-directly-in-gsource_che.patch new file mode 100644 index 0000000..263e145 --- /dev/null +++ b/debian/patches/extra/0018-fdmon-io_uring-check-CQ-ring-directly-in-gsource_che.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jens Axboe +Date: Fri, 13 Feb 2026 07:26:37 -0700 +Subject: [PATCH] fdmon-io_uring: check CQ ring directly in gsource_check + +gsource_check() only looks at the ppoll revents for the io_uring fd, +but CQEs can be posted during gsource_prepare()'s io_uring_submit() +call via kernel task_work processing on syscall exit. These completions +are already sitting in the CQ ring but the ring fd may not be signaled +yet, causing gsource_check() to return false. + +Add a fallback io_uring_cq_ready() check so completions that arrive +during submission are dispatched immediately rather than waiting for +the next ppoll() cycle. + +Signed-off-by: Jens Axboe +Message-ID: <20260213143225.161043-3-axboe@kernel.dk> +Signed-off-by: Stefan Hajnoczi +(cherry picked from commit 961fcc0f22768e7c3432fc645b93dc7cd4932fae) +Signed-off-by: Fiona Ebner +--- + util/fdmon-io_uring.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c +index d0b56127c6..b81e412402 100644 +--- a/util/fdmon-io_uring.c ++++ b/util/fdmon-io_uring.c +@@ -344,7 +344,19 @@ static void fdmon_io_uring_gsource_prepare(AioContext *ctx) + static bool fdmon_io_uring_gsource_check(AioContext *ctx) + { + gpointer tag = ctx->io_uring_fd_tag; +- return g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN; ++ ++ /* Check ppoll revents (normal path) */ ++ if (g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN) { ++ return true; ++ } ++ ++ /* ++ * Also check for CQEs that may have been posted during prepare's ++ * io_uring_submit() via task_work on syscall exit. Without this, ++ * the main loop can miss completions and sleep in ppoll() until the ++ * next timer fires. ++ */ ++ return io_uring_cq_ready(&ctx->fdmon_io_uring); + } + + /* Dispatch CQE handlers that are ready */ diff --git a/debian/patches/extra/0019-target-i386-add-compat-for-migrating-error-code.patch b/debian/patches/extra/0019-target-i386-add-compat-for-migrating-error-code.patch new file mode 100644 index 0000000..37a1d3a --- /dev/null +++ b/debian/patches/extra/0019-target-i386-add-compat-for-migrating-error-code.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +Date: Tue, 3 Mar 2026 15:29:22 +0100 +Subject: [PATCH] target/i386: add compat for migrating error code + +If cpu->env.has_error_code is true, backwards migration of a VM from +a QEMU binary with commit 27535e9cca to a QEMU binary without commit +27535e9cca will fail: + +> kvm: error while loading state for instance 0x0 of device 'cpu' + +In practice, wrongly setting the error code to 0 on the target is +often unproblematic, so additionally checking error_code != 0 in +cpu_errcode_needed() is not enough to mitigate the issue. Instead, add +proper machine version compat handling. + +Cc: qemu-stable@nongnu.org +Fixes: 27535e9cca ("target/i386: Add support for save/load of exception error code") +Signed-off-by: Fiona Ebner +--- + hw/i386/pc.c | 1 + + target/i386/cpu.c | 1 + + target/i386/cpu.h | 1 + + target/i386/machine.c | 2 +- + 4 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/hw/i386/pc.c b/hw/i386/pc.c +index f8b919cb6c..48e153c93f 100644 +--- a/hw/i386/pc.c ++++ b/hw/i386/pc.c +@@ -83,6 +83,7 @@ + + GlobalProperty pc_compat_10_1[] = { + { "mch", "extended-tseg-mbytes", "16" }, ++ { TYPE_X86_CPU, "x-migrate-error-code", "false" }, + }; + const size_t pc_compat_10_1_len = G_N_ELEMENTS(pc_compat_10_1); + +diff --git a/target/i386/cpu.c b/target/i386/cpu.c +index 6417775786..78308a82a0 100644 +--- a/target/i386/cpu.c ++++ b/target/i386/cpu.c +@@ -10024,6 +10024,7 @@ static const Property x86_cpu_properties[] = { + DEFINE_PROP_BOOL("tcg-cpuid", X86CPU, expose_tcg, true), + DEFINE_PROP_BOOL("x-migrate-smi-count", X86CPU, migrate_smi_count, + true), ++ DEFINE_PROP_BOOL("x-migrate-error-code", X86CPU, migrate_error_code, true), + /* + * lecacy_cache defaults to true unless the CPU model provides its + * own cache information (see x86_cpu_load_def()). +diff --git a/target/i386/cpu.h b/target/i386/cpu.h +index cee1f692a1..ee3ed5ae6d 100644 +--- a/target/i386/cpu.h ++++ b/target/i386/cpu.h +@@ -2211,6 +2211,7 @@ struct ArchCPU { + bool expose_tcg; + bool migratable; + bool migrate_smi_count; ++ bool migrate_error_code; + uint32_t apic_id; + + /* Enables publishing of TSC increment and Local APIC bus frequencies to +diff --git a/target/i386/machine.c b/target/i386/machine.c +index 45b7cea80a..21531f8ba2 100644 +--- a/target/i386/machine.c ++++ b/target/i386/machine.c +@@ -466,7 +466,7 @@ static bool cpu_errcode_needed(void *opaque) + { + X86CPU *cpu = opaque; + +- return cpu->env.has_error_code != 0; ++ return cpu->env.has_error_code != 0 && cpu->migrate_error_code; + } + + static const VMStateDescription vmstate_error_code = { diff --git a/debian/patches/extra/0020-virtio-snd-remove-TODO-comments.patch b/debian/patches/extra/0020-virtio-snd-remove-TODO-comments.patch new file mode 100644 index 0000000..eddb849 --- /dev/null +++ b/debian/patches/extra/0020-virtio-snd-remove-TODO-comments.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Manos Pitsidianakis +Date: Fri, 20 Feb 2026 11:40:13 +0200 +Subject: [PATCH] virtio-snd: remove TODO comments + +Replying with a VIRTIO_SND_S_BAD_MSG error does not warrant a device +reset. Instead, a device reset happens when the driver requests it from the +transport. + +Signed-off-by: Manos Pitsidianakis +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260220-virtio-snd-series-v1-2-207c4f7200a2@linaro.org> +(cherry picked from commit 34238f078a04f24b91199249b83846ab082b4e05) +Signed-off-by: Fiona Ebner +--- + hw/audio/virtio-snd.c | 21 --------------------- + 1 file changed, 21 deletions(-) + +diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c +index 9101560f38..fd03efc120 100644 +--- a/hw/audio/virtio-snd.c ++++ b/hw/audio/virtio-snd.c +@@ -168,9 +168,6 @@ static void virtio_snd_handle_pcm_info(VirtIOSound *s, + sizeof(virtio_snd_query_info)); + + if (msg_sz != sizeof(virtio_snd_query_info)) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virtio-snd command size incorrect %zu vs \ + %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info)); +@@ -184,9 +181,6 @@ static void virtio_snd_handle_pcm_info(VirtIOSound *s, + + if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) < + sizeof(virtio_snd_hdr) + size * count) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + error_report("pcm info: buffer too small, got: %zu, needed: %zu", + iov_size(cmd->elem->in_sg, cmd->elem->in_num), + sizeof(virtio_snd_pcm_info)); +@@ -244,9 +238,6 @@ uint32_t virtio_snd_set_pcm_params(VirtIOSound *s, + virtio_snd_pcm_set_params *st_params; + + if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n"); + return cpu_to_le32(VIRTIO_SND_S_BAD_MSG); + } +@@ -297,9 +288,6 @@ static void virtio_snd_handle_pcm_set_params(VirtIOSound *s, + sizeof(virtio_snd_pcm_set_params)); + + if (msg_sz != sizeof(virtio_snd_pcm_set_params)) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virtio-snd command size incorrect %zu vs \ + %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params)); +@@ -609,9 +597,6 @@ static void virtio_snd_handle_pcm_release(VirtIOSound *s, + sizeof(stream_id)); + + if (msg_sz != sizeof(stream_id)) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virtio-snd command size incorrect %zu vs \ + %zu\n", __func__, msg_sz, sizeof(stream_id)); +@@ -623,9 +608,6 @@ static void virtio_snd_handle_pcm_release(VirtIOSound *s, + trace_virtio_snd_handle_pcm_release(stream_id); + stream = virtio_snd_pcm_get_stream(s, stream_id); + if (stream == NULL) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + error_report("already released stream %"PRIu32, stream_id); + virtio_error(VIRTIO_DEVICE(s), + "already released stream %"PRIu32, +@@ -668,9 +650,6 @@ process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd) + sizeof(virtio_snd_hdr)); + + if (msg_sz != sizeof(virtio_snd_hdr)) { +- /* +- * TODO: do we need to set DEVICE_NEEDS_RESET? +- */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: virtio-snd command size incorrect %zu vs \ + %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr)); diff --git a/debian/patches/extra/0021-virtio-snd-handle-5.14.6.2-for-PCM_INFO-properly.patch b/debian/patches/extra/0021-virtio-snd-handle-5.14.6.2-for-PCM_INFO-properly.patch new file mode 100644 index 0000000..ec9ad51 --- /dev/null +++ b/debian/patches/extra/0021-virtio-snd-handle-5.14.6.2-for-PCM_INFO-properly.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Manos Pitsidianakis +Date: Fri, 20 Feb 2026 11:40:14 +0200 +Subject: [PATCH] virtio-snd: handle 5.14.6.2 for PCM_INFO properly +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The section 5.14.6.2 of the VIRTIO spec says: + + 5.14.6.2 Driver Requirements: Item Information Request + + - The driver MUST NOT set start_id and count such that start_id + + count is greater than the total number of particular items that is + indicated in the device configuration space. + + - The driver MUST provide a buffer of sizeof(struct virtio_snd_hdr) + + count * size bytes for the response. + +While we performed some check for the second requirement, it failed to +check for integer overflow. + +Add also a check for the first requirement, which should limit exposure +to any overflow, since realistically the number of streams will be low +enough in value such that overflow is improbable. + +Cc: qemu-stable@nongnu.org +Reported-by: 罗铭源 +Signed-off-by: Manos Pitsidianakis +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260220-virtio-snd-series-v1-3-207c4f7200a2@linaro.org> +(cherry picked from commit 61679d7dcfa2dffc8fb115aa19b09e0e7cf5ea5c) +Signed-off-by: Fiona Ebner +--- + hw/audio/virtio-snd.c | 31 +++++++++++++++++++++++++++---- + 1 file changed, 27 insertions(+), 4 deletions(-) + +diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c +index fd03efc120..e9c24d6795 100644 +--- a/hw/audio/virtio-snd.c ++++ b/hw/audio/virtio-snd.c +@@ -156,7 +156,7 @@ static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s, + static void virtio_snd_handle_pcm_info(VirtIOSound *s, + virtio_snd_ctrl_command *cmd) + { +- uint32_t stream_id, start_id, count, size; ++ uint32_t stream_id, start_id, count, size, tmp; + virtio_snd_pcm_info val; + virtio_snd_query_info req; + VirtIOSoundPCMStream *stream = NULL; +@@ -179,11 +179,34 @@ static void virtio_snd_handle_pcm_info(VirtIOSound *s, + count = le32_to_cpu(req.count); + size = le32_to_cpu(req.size); + +- if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) < +- sizeof(virtio_snd_hdr) + size * count) { ++ /* ++ * 5.14.6.2 Driver Requirements: Item Information Request ++ * "The driver MUST NOT set start_id and count such that start_id + count ++ * is greater than the total number of particular items that is indicated ++ * in the device configuration space." ++ */ ++ if (start_id > s->snd_conf.streams ++ || !g_uint_checked_add(&tmp, start_id, count) ++ || start_id + count > s->snd_conf.streams) { ++ error_report("pcm info: start_id + count is greater than the total " ++ "number of streams, got: start_id = %u, count = %u", ++ start_id, count); ++ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); ++ return; ++ } ++ ++ /* ++ * 5.14.6.2 Driver Requirements: Item Information Request ++ * "The driver MUST provide a buffer of sizeof(struct virtio_snd_hdr) + ++ * count * size bytes for the response." ++ */ ++ if (!g_uint_checked_mul(&tmp, size, count) ++ || !g_uint_checked_add(&tmp, tmp, sizeof(virtio_snd_hdr)) ++ || iov_size(cmd->elem->in_sg, cmd->elem->in_num) < ++ sizeof(virtio_snd_hdr) + size * count) { + error_report("pcm info: buffer too small, got: %zu, needed: %zu", + iov_size(cmd->elem->in_sg, cmd->elem->in_num), +- sizeof(virtio_snd_pcm_info)); ++ sizeof(virtio_snd_pcm_info) * count); + cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG); + return; + } diff --git a/debian/patches/extra/0022-virtio-snd-fix-max_size-bounds-check-in-input-cb.patch b/debian/patches/extra/0022-virtio-snd-fix-max_size-bounds-check-in-input-cb.patch new file mode 100644 index 0000000..e4e255a --- /dev/null +++ b/debian/patches/extra/0022-virtio-snd-fix-max_size-bounds-check-in-input-cb.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Manos Pitsidianakis +Date: Fri, 20 Feb 2026 11:40:15 +0200 +Subject: [PATCH] virtio-snd: fix max_size bounds check in input cb + +In 98e77e3d we calculated the max size and checked that each buffer is smaller than it. + +We neglected to subtract the size of the virtio_snd_pcm_status header +from the max size, and max_size was thus larger than the correct value, +leading to potential OOB writes. + +If the buffer cannot fit the header or can fit only the header, return +the buffer immediately. + +Cc: qemu-stable@nongnu.org +Fixes: 98e77e3dd8dd6e7aa9a7dffa60f49c8c8a49d4e3 ("virtio-snd: add max size bounds check in input cb") +Reported-by: DARKNAVY +Signed-off-by: Manos Pitsidianakis +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260220-virtio-snd-series-v1-4-207c4f7200a2@linaro.org> +(cherry picked from commit bcb53328aa70023f1405fade4e253e7f77567261) +Signed-off-by: Fiona Ebner +--- + hw/audio/virtio-snd.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c +index e9c24d6795..3437211f79 100644 +--- a/hw/audio/virtio-snd.c ++++ b/hw/audio/virtio-snd.c +@@ -1255,6 +1255,12 @@ static void virtio_snd_pcm_in_cb(void *data, int available) + } + + max_size = iov_size(buffer->elem->in_sg, buffer->elem->in_num); ++ if (max_size <= sizeof(virtio_snd_pcm_status)) { ++ return_rx_buffer(stream, buffer); ++ continue; ++ } ++ max_size -= sizeof(virtio_snd_pcm_status); ++ + for (;;) { + if (buffer->size >= max_size) { + return_rx_buffer(stream, buffer); diff --git a/debian/patches/extra/0023-virtio-snd-tighten-read-amount-in-in_cb.patch b/debian/patches/extra/0023-virtio-snd-tighten-read-amount-in-in_cb.patch new file mode 100644 index 0000000..00e496c --- /dev/null +++ b/debian/patches/extra/0023-virtio-snd-tighten-read-amount-in-in_cb.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Manos Pitsidianakis +Date: Fri, 20 Feb 2026 11:40:16 +0200 +Subject: [PATCH] virtio-snd: tighten read amount in in_cb + +The amount of bytes to read passed to AUD_read() should never surpass +the maximum available buffer length. Tighten the current amount by +MIN(, max_size - ). + +Cc: qemu-stable@nongnu.org +Fixes: 98e77e3dd8dd6e7aa9a7dffa60f49c8c8a49d4e3 ("virtio-snd: add max size bounds check in input cb") +Reported-by: DARKNAVY +Signed-off-by: Manos Pitsidianakis +Reviewed-by: Michael S. Tsirkin +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260220-virtio-snd-series-v1-5-207c4f7200a2@linaro.org> +(cherry picked from commit 7994203bb1b83a6604f3ab00fe9598909bb66164) +Signed-off-by: Fiona Ebner +--- + hw/audio/virtio-snd.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c +index 3437211f79..fc0781ae9a 100644 +--- a/hw/audio/virtio-snd.c ++++ b/hw/audio/virtio-snd.c +@@ -1240,7 +1240,7 @@ static void virtio_snd_pcm_in_cb(void *data, int available) + { + VirtIOSoundPCMStream *stream = data; + VirtIOSoundPCMBuffer *buffer; +- size_t size, max_size; ++ size_t size, max_size, to_read; + + WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) { + while (!QSIMPLEQ_EMPTY(&stream->queue)) { +@@ -1266,10 +1266,12 @@ static void virtio_snd_pcm_in_cb(void *data, int available) + return_rx_buffer(stream, buffer); + break; + } ++ to_read = stream->params.period_bytes - buffer->size; ++ to_read = MIN(to_read, available); ++ to_read = MIN(to_read, max_size - buffer->size); + size = AUD_read(stream->voice.in, +- buffer->data + buffer->size, +- MIN(available, (stream->params.period_bytes - +- buffer->size))); ++ buffer->data + buffer->size, ++ to_read); + if (!size) { + available = 0; + break; diff --git a/debian/patches/extra/0024-hw-misc-virt_ctrl-Fix-incorrect-trace-event-in-read-.patch b/debian/patches/extra/0024-hw-misc-virt_ctrl-Fix-incorrect-trace-event-in-read-.patch new file mode 100644 index 0000000..fd41b73 --- /dev/null +++ b/debian/patches/extra/0024-hw-misc-virt_ctrl-Fix-incorrect-trace-event-in-read-.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kuan-Wei Chiu +Date: Sun, 11 Jan 2026 18:49:15 +0000 +Subject: [PATCH] hw/misc/virt_ctrl: Fix incorrect trace event in read + operation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The virt_ctrl_read() function currently invokes trace_virt_ctrl_write() +instead of trace_virt_ctrl_read(). This results in read operations +appearing as write operations in the trace output, which is misleading +during debugging and analysis. + +Replace the incorrect trace call with the proper read-specific trace +event to accurately reflect the hardware behavior. + +Fixes: 0791bc02b8fb ("m68k: add a system controller") +Signed-off-by: Kuan-Wei Chiu +Reviewed-by: Philippe Mathieu-Daudé +Message-ID: <20260111184915.1363318-1-visitorckw@gmail.com> +Signed-off-by: Philippe Mathieu-Daudé +(cherry picked from commit 8608ed356ef90815cc5bcf04fcdbde987fd24bca) +Signed-off-by: Fiona Ebner +--- + hw/misc/virt_ctrl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/misc/virt_ctrl.c b/hw/misc/virt_ctrl.c +index 9f16093ca2..7dc2fe4f94 100644 +--- a/hw/misc/virt_ctrl.c ++++ b/hw/misc/virt_ctrl.c +@@ -43,7 +43,7 @@ static uint64_t virt_ctrl_read(void *opaque, hwaddr addr, unsigned size) + break; + } + +- trace_virt_ctrl_write(s, addr, size, value); ++ trace_virt_ctrl_read(s, addr, size, value); + + return value; + } diff --git a/debian/patches/extra/0025-target-i386-emulate-x86_decode-Actually-use-stream-i.patch b/debian/patches/extra/0025-target-i386-emulate-x86_decode-Actually-use-stream-i.patch new file mode 100644 index 0000000..f66e6a2 --- /dev/null +++ b/debian/patches/extra/0025-target-i386-emulate-x86_decode-Actually-use-stream-i.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bernhard Beschow +Date: Tue, 24 Feb 2026 00:39:25 +0100 +Subject: [PATCH] target/i386/emulate/x86_decode: Actually use stream in + decode_instruction_stream() + +Compared to decode_instruction(), decode_instruction_stream() has an additional +stream parameter which avoids some guest memory accesses during instruction +decoding. Both functions defer the actual work to decode_opcode() which would +set the stream pointer to zero such that decode_instruction_stream() essentially +behaved like decode_instruction(). Given that all callers of +decode_instruction_stream() properly zero-initialize the decode parameter, the +memset() call can be moved into decode_instruction() which is the only other +user of decode_opcode(). This preserves the non-zero stream pointer which +avoids extra guest memory accesses. + +Fixes: 1e25327b244a ("target/i386/emulate: Allow instruction decoding from stream") +cc: qemu-stable +Signed-off-by: Bernhard Beschow +Reviewed-by: Mohamed Mediouni +Reviewed-by: Wei Liu (Microsoft) +Tested-by: Magnus Kulke +Link: https://lore.kernel.org/r/20260223233950.96076-4-mohamed@unpredictable.fr +Signed-off-by: Paolo Bonzini +(cherry picked from commit 1b93832f55927b1b76a6587ca75a5a35676188de) +Signed-off-by: Fiona Ebner +--- + target/i386/emulate/x86_decode.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/target/i386/emulate/x86_decode.c b/target/i386/emulate/x86_decode.c +index d037ed1142..a1122d2b27 100644 +--- a/target/i386/emulate/x86_decode.c ++++ b/target/i386/emulate/x86_decode.c +@@ -2088,8 +2088,6 @@ static void decode_opcodes(CPUX86State *env, struct x86_decode *decode) + + static uint32_t decode_opcode(CPUX86State *env, struct x86_decode *decode) + { +- memset(decode, 0, sizeof(*decode)); +- + decode_prefix(env, decode); + set_addressing_size(env, decode); + set_operand_size(env, decode); +@@ -2101,6 +2099,8 @@ static uint32_t decode_opcode(CPUX86State *env, struct x86_decode *decode) + + uint32_t decode_instruction(CPUX86State *env, struct x86_decode *decode) + { ++ memset(decode, 0, sizeof(*decode)); ++ + return decode_opcode(env, decode); + } + diff --git a/debian/patches/extra/0026-io-separate-freeing-of-tasks-from-marking-them-as-co.patch b/debian/patches/extra/0026-io-separate-freeing-of-tasks-from-marking-them-as-co.patch new file mode 100644 index 0000000..50ab81c --- /dev/null +++ b/debian/patches/extra/0026-io-separate-freeing-of-tasks-from-marking-them-as-co.patch @@ -0,0 +1,258 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +Date: Tue, 6 Jan 2026 16:08:49 +0000 +Subject: [PATCH] io: separate freeing of tasks from marking them as complete +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The original design of QIOTask was intended to simplify lifecycle +management by automatically freeing it when the task was marked as +complete. This overlooked the fact that when a QIOTask is used in +combination with a GSource, there may be times when the source +callback is never invoked. This is typically when a GSource is +released before any I/O event arrives. In such cases it is not +desirable to mark a QIOTask as complete, but it still needs to be +freed. To satisfy this, the task must be released manually. + +Reviewed-by: Marc-André Lureau +Signed-off-by: Daniel P. Berrangé +(cherry picked from commit 163cd0ae1182e67509b271f244a73dfd938337b9) +Signed-off-by: Fiona Ebner +--- + include/io/task.h | 29 +++++++++++++++++++++-------- + io/channel-tls.c | 4 ++++ + io/channel-websock.c | 3 +++ + io/task.c | 8 ++++++-- + tests/unit/test-io-task.c | 26 ++++++++++++++++++++++++++ + 5 files changed, 60 insertions(+), 10 deletions(-) + +diff --git a/include/io/task.h b/include/io/task.h +index 0b5342ee84..98847f5994 100644 +--- a/include/io/task.h ++++ b/include/io/task.h +@@ -96,7 +96,7 @@ typedef void (*QIOTaskWorker)(QIOTask *task, + * 1000, + * myobject_operation_timer, + * task, +- * NULL); ++ * qio_task_free); + * } + * + * +@@ -138,9 +138,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task, + * the callback func 'myobject_operation_notify' shown + * earlier to deal with the results. + * +- * Once this function returns false, object_unref will be called +- * automatically on the task causing it to be released and the +- * ref on QMyObject dropped too. ++ * Once this function returns FALSE, the task will be freed, ++ * causing it release the ref on QMyObject too. + * + * The QIOTask module can also be used to perform operations + * in a background thread context, while still reporting the +@@ -208,8 +207,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task, + * 'err' attribute in the task object to determine if + * the operation was successful or not. + * +- * The returned task will be released when qio_task_complete() +- * is invoked. ++ * The returned task must be released by calling ++ * qio_task_free() when no longer required. + * + * Returns: the task struct + */ +@@ -218,6 +217,19 @@ QIOTask *qio_task_new(Object *source, + gpointer opaque, + GDestroyNotify destroy); + ++/** ++ * qio_task_free: ++ * task: the task object to free ++ * ++ * Free the resources associated with the task. Typically ++ * the qio_task_complete() method will be called immediately ++ * before this to trigger the task callback, however, it is ++ * permissible to free the task in the case of cancellation. ++ * The destroy callback will be used to release the opaque ++ * data provided to qio_task_new(). ++ */ ++void qio_task_free(QIOTask *task); ++ + /** + * qio_task_run_in_thread: + * @task: the task struct +@@ -268,8 +280,9 @@ void qio_task_wait_thread(QIOTask *task); + * qio_task_complete: + * @task: the task struct + * +- * Invoke the completion callback for @task and +- * then free its memory. ++ * Invoke the completion callback for @task. This should typically ++ * only be invoked once on a task, and then qio_task_free() used ++ * to free it. + */ + void qio_task_complete(QIOTask *task); + +diff --git a/io/channel-tls.c b/io/channel-tls.c +index b0cec27cb9..07274c12df 100644 +--- a/io/channel-tls.c ++++ b/io/channel-tls.c +@@ -170,6 +170,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + trace_qio_channel_tls_handshake_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); ++ qio_task_free(task); + return; + } + +@@ -183,6 +184,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + trace_qio_channel_tls_credentials_allow(ioc); + } + qio_task_complete(task); ++ qio_task_free(task); + } else { + GIOCondition condition; + QIOChannelTLSData *data = g_new0(typeof(*data), 1); +@@ -270,11 +272,13 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + trace_qio_channel_tls_bye_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); ++ qio_task_free(task); + return; + } + + if (status == QCRYPTO_TLS_BYE_COMPLETE) { + qio_task_complete(task); ++ qio_task_free(task); + return; + } + +diff --git a/io/channel-websock.c b/io/channel-websock.c +index cb4dafdebb..b4f96a0af4 100644 +--- a/io/channel-websock.c ++++ b/io/channel-websock.c +@@ -545,6 +545,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, + trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); + qio_task_set_error(task, err); + qio_task_complete(task); ++ qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +@@ -561,6 +562,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, + trace_qio_channel_websock_handshake_complete(ioc); + qio_task_complete(task); + } ++ qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +@@ -588,6 +590,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, + trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); + qio_task_set_error(task, err); + qio_task_complete(task); ++ qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +diff --git a/io/task.c b/io/task.c +index 451f26f8b4..331febd4e1 100644 +--- a/io/task.c ++++ b/io/task.c +@@ -70,8 +70,12 @@ QIOTask *qio_task_new(Object *source, + return task; + } + +-static void qio_task_free(QIOTask *task) ++void qio_task_free(QIOTask *task) + { ++ if (!task) { ++ return; ++ } ++ + qemu_mutex_lock(&task->thread_lock); + if (task->thread) { + if (task->thread->destroy) { +@@ -110,6 +114,7 @@ static gboolean qio_task_thread_result(gpointer opaque) + + trace_qio_task_thread_result(task); + qio_task_complete(task); ++ qio_task_free(task); + + return FALSE; + } +@@ -196,7 +201,6 @@ void qio_task_complete(QIOTask *task) + { + task->func(task, task->opaque); + trace_qio_task_complete(task); +- qio_task_free(task); + } + + +diff --git a/tests/unit/test-io-task.c b/tests/unit/test-io-task.c +index 115dba8970..b1c8ecb7ab 100644 +--- a/tests/unit/test-io-task.c ++++ b/tests/unit/test-io-task.c +@@ -73,6 +73,7 @@ static void test_task_complete(void) + src = qio_task_get_source(task); + + qio_task_complete(task); ++ qio_task_free(task); + + g_assert(obj == src); + +@@ -84,6 +85,28 @@ static void test_task_complete(void) + } + + ++static void test_task_cancel(void) ++{ ++ QIOTask *task; ++ Object *obj = object_new(TYPE_DUMMY); ++ Object *src; ++ struct TestTaskData data = { NULL, NULL, false }; ++ ++ task = qio_task_new(obj, task_callback, &data, NULL); ++ src = qio_task_get_source(task); ++ ++ qio_task_free(task); ++ ++ g_assert(obj == src); ++ ++ object_unref(obj); ++ ++ g_assert(data.source == NULL); ++ g_assert(data.err == NULL); ++ g_assert(data.freed == false); ++} ++ ++ + static void task_data_free(gpointer opaque) + { + struct TestTaskData *data = opaque; +@@ -101,6 +124,7 @@ static void test_task_data_free(void) + task = qio_task_new(obj, task_callback, &data, task_data_free); + + qio_task_complete(task); ++ qio_task_free(task); + + object_unref(obj); + +@@ -123,6 +147,7 @@ static void test_task_failure(void) + + qio_task_set_error(task, err); + qio_task_complete(task); ++ qio_task_free(task); + + object_unref(obj); + +@@ -260,6 +285,7 @@ int main(int argc, char **argv) + module_call_init(MODULE_INIT_QOM); + type_register_static(&dummy_info); + g_test_add_func("/crypto/task/complete", test_task_complete); ++ g_test_add_func("/crypto/task/cancel", test_task_cancel); + g_test_add_func("/crypto/task/datafree", test_task_data_free); + g_test_add_func("/crypto/task/failure", test_task_failure); + g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete); diff --git a/debian/patches/extra/0027-io-fix-cleanup-for-TLS-I-O-source-data-on-cancellati.patch b/debian/patches/extra/0027-io-fix-cleanup-for-TLS-I-O-source-data-on-cancellati.patch new file mode 100644 index 0000000..9627d84 --- /dev/null +++ b/debian/patches/extra/0027-io-fix-cleanup-for-TLS-I-O-source-data-on-cancellati.patch @@ -0,0 +1,176 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +Date: Tue, 6 Jan 2026 13:45:10 +0000 +Subject: [PATCH] io: fix cleanup for TLS I/O source data on cancellation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The TLS code will create a GSource for tracking completion of the +handshake process, passing a QIOChannelTLSData struct that contains +various data items. The data struct is freed by the callback when +it completes, which means when a source is cancelled, nothing is +free'ing the data struct or its contents. + +Switch to provide a data free callback to the GSource, which ensures +the QIOChannelTLSData struct is always freed even when the main event +callback never fires. + +Fixes: https://gitlab.com/qemu-project/qemu/-/issues/3114 +Reviewed-by: Marc-André Lureau +Signed-off-by: Daniel P. Berrangé +(cherry picked from commit d39d0f3acdd7c1bb275db7e97b511f98254ecd9f) +Signed-off-by: Fiona Ebner +--- + io/channel-tls.c | 68 ++++++++++++++++++++++++++++++------------------ + 1 file changed, 43 insertions(+), 25 deletions(-) + +diff --git a/io/channel-tls.c b/io/channel-tls.c +index 07274c12df..940fc3c6d1 100644 +--- a/io/channel-tls.c ++++ b/io/channel-tls.c +@@ -153,13 +153,32 @@ struct QIOChannelTLSData { + }; + typedef struct QIOChannelTLSData QIOChannelTLSData; + ++static void qio_channel_tls_io_data_free(gpointer user_data) ++{ ++ QIOChannelTLSData *data = user_data; ++ /* ++ * Usually 'task' will be NULL since the GSource ++ * callback will either complete the task or pass ++ * it on to a new GSource. We'll see a non-NULL ++ * task here only if the GSource was released before ++ * its callback triggers ++ */ ++ if (data->task) { ++ qio_task_free(data->task); ++ } ++ if (data->context) { ++ g_main_context_unref(data->context); ++ } ++ g_free(data); ++} ++ + static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, + GIOCondition condition, + gpointer user_data); + +-static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, +- QIOTask *task, +- GMainContext *context) ++static gboolean qio_channel_tls_handshake_task(QIOChannelTLS *ioc, ++ QIOTask *task, ++ GMainContext *context) + { + Error *err = NULL; + int status; +@@ -170,8 +189,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + trace_qio_channel_tls_handshake_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); +- qio_task_free(task); +- return; ++ return TRUE; + } + + if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) { +@@ -184,7 +202,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + trace_qio_channel_tls_credentials_allow(ioc); + } + qio_task_complete(task); +- qio_task_free(task); ++ return TRUE; + } else { + GIOCondition condition; + QIOChannelTLSData *data = g_new0(typeof(*data), 1); +@@ -208,8 +226,9 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc, + condition, + qio_channel_tls_handshake_io, + data, +- NULL, ++ qio_channel_tls_io_data_free, + context); ++ return FALSE; + } + } + +@@ -225,11 +244,9 @@ static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc, + qio_task_get_source(task)); + + tioc->hs_ioc_tag = 0; +- g_free(data); +- qio_channel_tls_handshake_task(tioc, task, context); +- +- if (context) { +- g_main_context_unref(context); ++ if (!qio_channel_tls_handshake_task(tioc, task, context)) { ++ /* task is kept by new GSource so must not be released yet */ ++ data->task = NULL; + } + + return FALSE; +@@ -252,14 +269,16 @@ void qio_channel_tls_handshake(QIOChannelTLS *ioc, + func, opaque, destroy); + + trace_qio_channel_tls_handshake_start(ioc); +- qio_channel_tls_handshake_task(ioc, task, context); ++ if (qio_channel_tls_handshake_task(ioc, task, context)) { ++ qio_task_free(task); ++ } + } + + static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + gpointer user_data); + +-static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, +- GMainContext *context) ++static gboolean qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, ++ GMainContext *context) + { + GIOCondition condition; + QIOChannelTLSData *data; +@@ -272,14 +291,12 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + trace_qio_channel_tls_bye_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); +- qio_task_free(task); +- return; ++ return TRUE; + } + + if (status == QCRYPTO_TLS_BYE_COMPLETE) { + qio_task_complete(task); +- qio_task_free(task); +- return; ++ return TRUE; + } + + data = g_new0(typeof(*data), 1); +@@ -299,7 +316,10 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + trace_qio_channel_tls_bye_pending(ioc, status); + ioc->bye_ioc_tag = qio_channel_add_watch_full(ioc->master, condition, + qio_channel_tls_bye_io, +- data, NULL, context); ++ data, ++ qio_channel_tls_io_data_free, ++ context); ++ return FALSE; + } + + +@@ -312,11 +332,9 @@ static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(qio_task_get_source(task)); + + tioc->bye_ioc_tag = 0; +- g_free(data); +- qio_channel_tls_bye_task(tioc, task, context); +- +- if (context) { +- g_main_context_unref(context); ++ if (!qio_channel_tls_bye_task(tioc, task, context)) { ++ /* task is kept by new GSource so must not be released yet */ ++ data->task = NULL; + } + + return FALSE; diff --git a/debian/patches/extra/0028-io-fix-cleanup-for-websock-I-O-source-data-on-cancel.patch b/debian/patches/extra/0028-io-fix-cleanup-for-websock-I-O-source-data-on-cancel.patch new file mode 100644 index 0000000..2604d55 --- /dev/null +++ b/debian/patches/extra/0028-io-fix-cleanup-for-websock-I-O-source-data-on-cancel.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= +Date: Tue, 6 Jan 2026 13:45:10 +0000 +Subject: [PATCH] io: fix cleanup for websock I/O source data on cancellation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The websock code will create a GSource for tracking completion of the +handshake process, passing a QIOTask which is freed by the callback +when it completes, which means when a source is cancelled, nothing is +free'ing the task. + +Switch to provide a data free callback to the GSource, which ensures +the QIOTask is always freed even when the main event callback never +fires. + +Fixes: https://gitlab.com/qemu-project/qemu/-/issues/3114 +Signed-off-by: Daniel P. Berrangé +(cherry picked from commit 9545c059f77e3f814fcbaba83203572ea655c50e) +Signed-off-by: Fiona Ebner +--- + io/channel-websock.c | 49 +++++++++++++++++++++++++++++++------------- + 1 file changed, 35 insertions(+), 14 deletions(-) + +diff --git a/io/channel-websock.c b/io/channel-websock.c +index b4f96a0af4..bb10bc4f7f 100644 +--- a/io/channel-websock.c ++++ b/io/channel-websock.c +@@ -526,11 +526,32 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc, + return 1; + } + ++typedef struct QIOChannelWebsockData { ++ QIOTask *task; ++} QIOChannelWebsockData; ++ ++static void qio_channel_websock_data_free(gpointer user_data) ++{ ++ QIOChannelWebsockData *data = user_data; ++ /* ++ * Usually 'task' will be NULL since the GSource ++ * callback will either complete the task or pass ++ * it on to a new GSource. We'll see a non-NULL ++ * task here only if the GSource was released before ++ * its callback triggers ++ */ ++ if (data->task) { ++ qio_task_free(data->task); ++ } ++ g_free(data); ++} ++ + static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, + GIOCondition condition, + gpointer user_data) + { +- QIOTask *task = user_data; ++ QIOChannelWebsockData *data = user_data; ++ QIOTask *task = data->task; + QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( + qio_task_get_source(task)); + Error *err = NULL; +@@ -545,7 +566,6 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, + trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); + qio_task_set_error(task, err); + qio_task_complete(task); +- qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +@@ -562,7 +582,6 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, + trace_qio_channel_websock_handshake_complete(ioc); + qio_task_complete(task); + } +- qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +@@ -574,7 +593,8 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, + GIOCondition condition, + gpointer user_data) + { +- QIOTask *task = user_data; ++ QIOChannelWebsockData *data = user_data, *newdata = NULL; ++ QIOTask *task = data->task; + QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( + qio_task_get_source(task)); + Error *err = NULL; +@@ -590,7 +610,6 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, + trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); + qio_task_set_error(task, err); + qio_task_complete(task); +- qio_task_free(task); + wioc->hs_io_tag = 0; + return FALSE; + } +@@ -603,12 +622,14 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, + error_propagate(&wioc->io_err, err); + + trace_qio_channel_websock_handshake_reply(ioc); ++ newdata = g_new0(QIOChannelWebsockData, 1); ++ newdata->task = g_steal_pointer(&data->task); + wioc->hs_io_tag = qio_channel_add_watch( + wioc->master, + G_IO_OUT, + qio_channel_websock_handshake_send, +- task, +- NULL); ++ newdata, ++ qio_channel_websock_data_free); + return FALSE; + } + +@@ -904,12 +925,12 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, + gpointer opaque, + GDestroyNotify destroy) + { +- QIOTask *task; ++ QIOChannelWebsockData *data = g_new0(QIOChannelWebsockData, 1); + +- task = qio_task_new(OBJECT(ioc), +- func, +- opaque, +- destroy); ++ data->task = qio_task_new(OBJECT(ioc), ++ func, ++ opaque, ++ destroy); + + trace_qio_channel_websock_handshake_start(ioc); + trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); +@@ -917,8 +938,8 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, + ioc->master, + G_IO_IN, + qio_channel_websock_handshake_io, +- task, +- NULL); ++ data, ++ qio_channel_websock_data_free); + } + + diff --git a/debian/patches/extra/0029-hw-Make-qdev_get_printable_name-consistently-return-.patch b/debian/patches/extra/0029-hw-Make-qdev_get_printable_name-consistently-return-.patch new file mode 100644 index 0000000..50438b0 --- /dev/null +++ b/debian/patches/extra/0029-hw-Make-qdev_get_printable_name-consistently-return-.patch @@ -0,0 +1,142 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Peter Maydell +Date: Sat, 7 Mar 2026 15:50:46 +0000 +Subject: [PATCH] hw: Make qdev_get_printable_name() consistently return + freeable string +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The current implementation of qdev_get_printable_name() sometimes +returns a string that must not be freed (vdev->id or the fixed +fallback string "" and sometimes returns a string +that must be freed (the return value of qdev_get_dev_path()). This +forces callers to leak the string in the "must be freed" case. + +Make the function consistent that it always returns a string that +the caller must free, and make the three callsites free it. + +This fixes leaks like this that show up when running "make check" +with the address sanitizer enabled: + +Direct leak of 13 byte(s) in 1 object(s) allocated from: + #0 0x5561de21f293 in malloc (/home/pm215/qemu/build/san/qemu-system-i386+0x1a2d293) (BuildId: 6d6fad7130fd5c8dbbc03401df554f68b8034936) + #1 0x767ad7a82ac9 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x62ac9) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3) + #2 0x5561deaf34f2 in pcibus_get_dev_path /home/pm215/qemu/build/san/../../hw/pci/pci.c:2792:12 + #3 0x5561df9d8830 in qdev_get_printable_name /home/pm215/qemu/build/san/../../hw/core/qdev.c:431:24 + #4 0x5561deebdca2 in virtio_init_region_cache /home/pm215/qemu/build/san/../../hw/virtio/virtio.c:298:17 + #5 0x5561df05f842 in memory_region_write_accessor /home/pm215/qemu/build/san/../../system/memory.c:491:5 + #6 0x5561df05ed1b in access_with_adjusted_size /home/pm215/qemu/build/san/../../system/memory.c:567:18 + #7 0x5561df05e3fa in memory_region_dispatch_write /home/pm215/qemu/build/san/../../system/memory.c + #8 0x5561df0aa805 in address_space_stm_internal /home/pm215/qemu/build/san/../../system/memory_ldst.c.inc:85:13 + #9 0x5561df0bcad3 in qtest_process_command /home/pm215/qemu/build/san/../../system/qtest.c:480:13 + +Cc: qemu-stable@nongnu.org +Fixes: e209d4d7a31b9 ("virtio: improve virtqueue mapping error messages") +Signed-off-by: Peter Maydell +Reviewed-by: Philippe Mathieu-Daudé +Message-ID: <20260307155046.3940197-3-peter.maydell@linaro.org> +Signed-off-by: Philippe Mathieu-Daudé +(cherry picked from commit 1e3e1d51e20e8b38efa089bf54b5ee2cbbcca221) +Signed-off-by: Fiona Ebner +--- + hw/core/qdev.c | 4 ++-- + hw/virtio/virtio.c | 12 +++++++++--- + include/hw/qdev-core.h | 16 ++++++++++++++++ + 3 files changed, 27 insertions(+), 5 deletions(-) + +diff --git a/hw/core/qdev.c b/hw/core/qdev.c +index fab42a7270..ce0ee9fcef 100644 +--- a/hw/core/qdev.c ++++ b/hw/core/qdev.c +@@ -420,7 +420,7 @@ const char *qdev_get_printable_name(DeviceState *vdev) + * names. + */ + if (vdev->id) { +- return vdev->id; ++ return g_strdup(vdev->id); + } + /* + * Fall back to the canonical QOM device path (eg. ID for PCI +@@ -437,7 +437,7 @@ const char *qdev_get_printable_name(DeviceState *vdev) + * Final fallback: if all else fails, return a placeholder string. + * This ensures the error message always contains a valid string. + */ +- return ""; ++ return g_strdup(""); + } + + void qdev_add_unplug_blocker(DeviceState *dev, Error *reason) +diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c +index 683026adc4..deb7c6695e 100644 +--- a/hw/virtio/virtio.c ++++ b/hw/virtio/virtio.c +@@ -258,10 +258,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) + len = address_space_cache_init(&new->desc, vdev->dma_as, + addr, size, packed); + if (len < size) { ++ g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); ++ + virtio_error(vdev, + "Failed to map descriptor ring for device %s: " + "invalid guest physical address or corrupted queue setup", +- qdev_get_printable_name(DEVICE(vdev))); ++ devname); + goto err_desc; + } + +@@ -269,10 +271,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) + len = address_space_cache_init(&new->used, vdev->dma_as, + vq->vring.used, size, true); + if (len < size) { ++ g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); ++ + virtio_error(vdev, + "Failed to map used ring for device %s: " + "possible guest misconfiguration or insufficient memory", +- qdev_get_printable_name(DEVICE(vdev))); ++ devname); + goto err_used; + } + +@@ -280,10 +284,12 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n) + len = address_space_cache_init(&new->avail, vdev->dma_as, + vq->vring.avail, size, false); + if (len < size) { ++ g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev)); ++ + virtio_error(vdev, + "Failed to map avalaible ring for device %s: " + "possible queue misconfiguration or overlapping memory region", +- qdev_get_printable_name(DEVICE(vdev))); ++ devname); + goto err_avail; + } + +diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h +index 2caa0cbd26..774329bba9 100644 +--- a/include/hw/qdev-core.h ++++ b/include/hw/qdev-core.h +@@ -1065,6 +1065,22 @@ bool qdev_set_parent_bus(DeviceState *dev, BusState *bus, Error **errp); + extern bool qdev_hot_removed; + + char *qdev_get_dev_path(DeviceState *dev); ++ ++/** ++ * qdev_get_printable_name: Return human readable name for device ++ * @dev: Device to get name of ++ * ++ * Returns: A newly allocated string containing some human ++ * readable name for the device, suitable for printing in ++ * user-facing error messages. The function will never return NULL, ++ * so the name can be used without further checking or fallbacks. ++ * ++ * If the device has an explicitly set ID (e.g. by the user on the ++ * command line via "-device thisdev,id=myid") this is preferred. ++ * Otherwise we try the canonical QOM device path (which will be ++ * the PCI ID for PCI devices, for example). If all else fails ++ * we will return the placeholder ". ++ */ + const char *qdev_get_printable_name(DeviceState *dev); + + void qbus_set_hotplug_handler(BusState *bus, Object *handler); diff --git a/debian/patches/extra/0030-fuse-Copy-write-buffer-content-before-polling.patch b/debian/patches/extra/0030-fuse-Copy-write-buffer-content-before-polling.patch new file mode 100644 index 0000000..9b760f7 --- /dev/null +++ b/debian/patches/extra/0030-fuse-Copy-write-buffer-content-before-polling.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +Date: Mon, 9 Mar 2026 16:08:32 +0100 +Subject: [PATCH] fuse: Copy write buffer content before polling + +aio_poll() in I/O functions can lead to nested read_from_fuse_export() +calls, overwriting the request buffer's content. The only function +affected by this is fuse_write(), which therefore must use a bounce +buffer or corruption may occur. + +Note that in addition we do not know whether libfuse-internal structures +can cope with this nesting, and even if we did, we probably cannot rely +on it in the future. This is the main reason why we want to remove +libfuse from the I/O path. + +I do not have a good reproducer for this other than: + +$ dd if=/dev/urandom of=image bs=1M count=4096 +$ dd if=/dev/zero of=copy bs=1M count=4096 +$ touch fuse-export +$ qemu-storage-daemon \ + --blockdev file,node-name=file,filename=copy \ + --export \ + fuse,id=exp,node-name=file,mountpoint=fuse-export,writable=true \ + & + +Other shell: +$ qemu-img convert -p -n -f raw -O raw -t none image fuse-export +$ killall -SIGINT qemu-storage-daemon +$ qemu-img compare image copy +Content mismatch at offset 0! + +(The -t none in qemu-img convert is important.) + +I tried reproducing this with throttle and small aio_write requests from +another qemu-io instance, but for some reason all requests are perfectly +serialized then. + +I think in theory we should get parallel writes only if we set +fi->parallel_direct_writes in fuse_open(). In fact, I can confirm that +if we do that, that throttle-based reproducer works (i.e. does get +parallel (nested) write requests). I have no idea why we still get +parallel requests with qemu-img convert anyway. + +Also, a later patch in this series will set fi->parallel_direct_writes +and note that it makes basically no difference when running fio on the +current libfuse-based version of our code. It does make a difference +without libfuse. So something quite fishy is going on. + +I will try to investigate further what the root cause is, but I think +for now let's assume that calling blk_pwrite() can invalidate the buffer +contents through nested polling. + +Cc: qemu-stable@nongnu.org +Reviewed-by: Kevin Wolf +Signed-off-by: Hanna Czenczek +Message-ID: <20260309150856.26800-2-hreitz@redhat.com> +Reviewed-by: Kevin Wolf +Signed-off-by: Kevin Wolf +(cherry picked from commit a3fcbca0ef643a8aecf354bdeb08b1d81e5b33e7) +Signed-off-by: Fiona Ebner +--- + block/export/fuse.c | 17 ++++++++++++++++- + 1 file changed, 16 insertions(+), 1 deletion(-) + +diff --git a/block/export/fuse.c b/block/export/fuse.c +index 465cc9891d..aec4d8736d 100644 +--- a/block/export/fuse.c ++++ b/block/export/fuse.c +@@ -301,6 +301,12 @@ static void read_from_fuse_export(void *opaque) + goto out; + } + ++ /* ++ * Note that aio_poll() in any request-processing function can lead to a ++ * nested read_from_fuse_export() call, which will overwrite the contents of ++ * exp->fuse_buf. Anything that takes a buffer needs to take care that the ++ * content is copied before potentially polling via aio_poll(). ++ */ + fuse_session_process_buf(exp->fuse_session, &exp->fuse_buf); + + out: +@@ -624,6 +630,7 @@ static void fuse_write(fuse_req_t req, fuse_ino_t inode, const char *buf, + size_t size, off_t offset, struct fuse_file_info *fi) + { + FuseExport *exp = fuse_req_userdata(req); ++ QEMU_AUTO_VFREE void *copied = NULL; + int64_t length; + int ret; + +@@ -638,6 +645,14 @@ static void fuse_write(fuse_req_t req, fuse_ino_t inode, const char *buf, + return; + } + ++ /* ++ * Heed the note on read_from_fuse_export(): If we call aio_poll() (which ++ * any blk_*() I/O function may do), read_from_fuse_export() may be nested, ++ * overwriting the request buffer content. Therefore, we must copy it here. ++ */ ++ copied = blk_blockalign(exp->common.blk, size); ++ memcpy(copied, buf, size); ++ + /** + * Clients will expect short writes at EOF, so we have to limit + * offset+size to the image length. +@@ -660,7 +675,7 @@ static void fuse_write(fuse_req_t req, fuse_ino_t inode, const char *buf, + } + } + +- ret = blk_pwrite(exp->common.blk, offset, size, buf, 0); ++ ret = blk_pwrite(exp->common.blk, offset, size, copied, 0); + if (ret >= 0) { + fuse_reply_write(req, size); + } else { diff --git a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch index b4e1b2f..09e94e8 100644 --- a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch +++ b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch @@ -10,10 +10,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h -index cee1f692a1..6f49be3796 100644 +index ee3ed5ae6d..9d69d76f69 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h -@@ -2641,9 +2641,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); +@@ -2642,9 +2642,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); #define CPU_RESOLVING_TYPE TYPE_X86_CPU #ifdef TARGET_X86_64 diff --git a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch index b353177..e3f8ab8 100644 --- a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch +++ b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch @@ -247,7 +247,7 @@ index 0000000000..036edb17f5 + +block_init(bdrv_zeroinit_init); diff --git a/qapi/block-core.json b/qapi/block-core.json -index 64f2befdf5..19b60e3a96 100644 +index d4a5765dc4..476b9c011e 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3376,7 +3376,7 @@ diff --git a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch index 2f70373..2cae103 100644 --- a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch +++ b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch @@ -119,7 +119,7 @@ index 41ac3f222f..612942a222 100644 }; return raw_co_create(&options, errp); diff --git a/qapi/block-core.json b/qapi/block-core.json -index 19b60e3a96..029b8f2b51 100644 +index 476b9c011e..bddc97d494 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -5153,6 +5153,10 @@ diff --git a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch index a56fc83..0f97bdd 100644 --- a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch +++ b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch @@ -1688,7 +1688,7 @@ index 0000000000..177fb851b4 + return ret; +} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 029b8f2b51..75406f4215 100644 +index bddc97d494..693a123e94 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -947,6 +947,248 @@ diff --git a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch index 00fef7e..8995604 100644 --- a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch +++ b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch @@ -361,7 +361,7 @@ index 73f6e2e93b..b717cad2f9 100644 summary_info += {'libdaxctl support': libdaxctl} summary_info += {'libcbor support': libcbor} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 75406f4215..f998aafc49 100644 +index 693a123e94..e0f44067e0 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3614,6 +3614,7 @@ diff --git a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch index 2c3c25f..09565fe 100644 --- a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch +++ b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch @@ -192,7 +192,7 @@ index 177fb851b4..7575abab7c 100644 ret->pbs_masterkey = true; ret->backup_max_workers = true; diff --git a/qapi/block-core.json b/qapi/block-core.json -index f998aafc49..5b3bb3c19e 100644 +index e0f44067e0..f1679264bb 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1107,6 +1107,11 @@ diff --git a/debian/patches/pve/0038-block-add-alloc-track-driver.patch b/debian/patches/pve/0038-block-add-alloc-track-driver.patch index de886e2..cb3c9ae 100644 --- a/debian/patches/pve/0038-block-add-alloc-track-driver.patch +++ b/debian/patches/pve/0038-block-add-alloc-track-driver.patch @@ -449,7 +449,7 @@ index d023753091..a777c8079c 100644 out: diff --git a/qapi/block-core.json b/qapi/block-core.json -index 5b3bb3c19e..1d98f0a406 100644 +index f1679264bb..6d8c2df22b 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3606,7 +3606,8 @@ diff --git a/debian/patches/pve/0039-PVE-backup-add-fleecing-option.patch b/debian/patches/pve/0039-PVE-backup-add-fleecing-option.patch index 50021c5..c9d4d39 100644 --- a/debian/patches/pve/0039-PVE-backup-add-fleecing-option.patch +++ b/debian/patches/pve/0039-PVE-backup-add-fleecing-option.patch @@ -429,7 +429,7 @@ index 7575abab7c..8b83465ebd 100644 return ret; } diff --git a/qapi/block-core.json b/qapi/block-core.json -index 1d98f0a406..db0a5a1266 100644 +index 6d8c2df22b..ea73974495 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1049,6 +1049,10 @@ diff --git a/debian/patches/pve/0045-PVE-backup-implement-backup-access-setup-and-teardow.patch b/debian/patches/pve/0045-PVE-backup-implement-backup-access-setup-and-teardow.patch index 8bdbf11..f4ec604 100644 --- a/debian/patches/pve/0045-PVE-backup-implement-backup-access-setup-and-teardow.patch +++ b/debian/patches/pve/0045-PVE-backup-implement-backup-access-setup-and-teardow.patch @@ -740,7 +740,7 @@ index 0000000000..9ebeef7c8f + +#endif /* PVE_BACKUP_H */ diff --git a/qapi/block-core.json b/qapi/block-core.json -index db0a5a1266..94296c0bc9 100644 +index ea73974495..52b750dbb3 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1123,6 +1123,9 @@ diff --git a/debian/patches/pve/0047-savevm-async-reuse-migration-blocker-check-for-snaps.patch b/debian/patches/pve/0047-savevm-async-reuse-migration-blocker-check-for-snaps.patch index 18db6fa..d0c7868 100644 --- a/debian/patches/pve/0047-savevm-async-reuse-migration-blocker-check-for-snaps.patch +++ b/debian/patches/pve/0047-savevm-async-reuse-migration-blocker-check-for-snaps.patch @@ -61,7 +61,7 @@ Message-ID: <20250618102531.57444-1-f.ebner@proxmox.com> 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/block/vmdk.c b/block/vmdk.c -index 89e89cd10e..06df10a799 100644 +index cd8b4ec7c8..1f64ab1766 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -1404,9 +1404,7 @@ static int vmdk_open(BlockDriverState *bs, QDict *options, int flags, diff --git a/debian/patches/series b/debian/patches/series index 7c8e26c..8ed0c52 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -2,6 +2,32 @@ extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch extra/0002-ide-avoid-potential-deadlock-when-draining-during-tr.patch extra/0003-block-mirror-check-range-when-setting-zero-bitmap-fo.patch extra/0004-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch +extra/0005-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch +extra/0006-block-throttle-groups-fix-deadlock-with-iolimits-and.patch +extra/0007-block-Never-drop-BLOCK_IO_ERROR-with-action-stop-for.patch +extra/0008-mirror-Fix-missed-dirty-bitmap-writes-during-startup.patch +extra/0009-virtio-gpu-virgl-Add-virtio-gpu-virgl-hostmem-region.patch +extra/0010-virtio-gpu-Ensure-BHs-are-invoked-only-from-main-loo.patch +extra/0011-hw-i2c-aspeed_i2c-Fix-out-of-bounds-read-in-I2C-MMIO.patch +extra/0012-target-arm-Account-for-SME-in-aarch64_sve_narrow_vq-.patch +extra/0013-target-arm-Fix-feature-check-in-DO_SVE2_RRX-DO_SVE2_.patch +extra/0014-target-arm-tcg-Allow-SVE-RAX1-in-SME2p1-streaming-mo.patch +extra/0015-target-arm-Don-t-let-sme-on-downgrade-SME.patch +extra/0016-target-arm-set-the-correct-TI-bits-for-WFIT-traps.patch +extra/0017-aio-posix-notify-main-loop-when-SQEs-are-queued.patch +extra/0018-fdmon-io_uring-check-CQ-ring-directly-in-gsource_che.patch +extra/0019-target-i386-add-compat-for-migrating-error-code.patch +extra/0020-virtio-snd-remove-TODO-comments.patch +extra/0021-virtio-snd-handle-5.14.6.2-for-PCM_INFO-properly.patch +extra/0022-virtio-snd-fix-max_size-bounds-check-in-input-cb.patch +extra/0023-virtio-snd-tighten-read-amount-in-in_cb.patch +extra/0024-hw-misc-virt_ctrl-Fix-incorrect-trace-event-in-read-.patch +extra/0025-target-i386-emulate-x86_decode-Actually-use-stream-i.patch +extra/0026-io-separate-freeing-of-tasks-from-marking-them-as-co.patch +extra/0027-io-fix-cleanup-for-TLS-I-O-source-data-on-cancellati.patch +extra/0028-io-fix-cleanup-for-websock-I-O-source-data-on-cancel.patch +extra/0029-hw-Make-qdev_get_printable_name-consistently-return-.patch +extra/0030-fuse-Copy-write-buffer-content-before-polling.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.47.3