:p
atchew
Login
The following changes since commit 30e8a06b64aa58a3990ba39cb5d09531e7d265e0: Merge tag 'net-pull-request' of https://github.com/jasowang/qemu into staging (2026-06-29 17:41:42 +0200) are available in the Git repository at: https://gitlab.com/peterx/qemu.git tags/next-pull-request for you to fetch changes up to 60f2281450c4b4e9a16922fd69c66486a8207abb: migration: clean up postcopy blocktime presentation (2026-07-17 12:33:42 -0400) ---------------------------------------------------------------- Migration pull for rc1 - Bin's cleanup on migration blocktime (partly) - Yujun's fix on print format ---------------------------------------------------------------- Bin Guo (2): migration: fix ineffective overflow assert in postcopy blocktime migration: clean up postcopy blocktime presentation yujun (1): migration: Fix invalid %ud format and trace arg typo migration/migration-hmp-cmds.c | 2 +- migration/postcopy-ram.c | 14 ++++++-------- migration/trace-events | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) -- 2.54.0
From: yujun <yujun@kylinos.cn> Standard printf has no %ud conversion; glibc treats it as %u followed by a literal 'd', so postcopy userfaultfd error messages printed event values like "5d" instead of "5". The same typo existed in two postcopy discard trace format strings. Also rename the misspelled ram_save_iterate_big_wait() trace argument milliconds to milliseconds. Fixes: 00fa4fc85b ("postcopy: Allow registering of fd handler") Signed-off-by: yujun <yujun@kylinos.cn> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260630031324.43453-1-yujun@kylinos.cn Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/postcopy-ram.c | 4 ++-- migration/trace-events | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -XXX,XX +XXX,XX @@ static void *postcopy_ram_fault_thread(void *opaque) } } if (msg.event != UFFD_EVENT_PAGEFAULT) { - error_report("%s: Read unexpected event %ud from userfaultfd", + error_report("%s: Read unexpected event %u from userfaultfd", __func__, msg.event); continue; /* It's not a page fault, shouldn't happen */ } @@ -XXX,XX +XXX,XX @@ retry: } } if (msg.event != UFFD_EVENT_PAGEFAULT) { - error_report("%s: Read unexpected event %ud " + error_report("%s: Read unexpected event %u " "from userfaultfd (shared)", __func__, msg.event); continue; /* It's not a page fault, shouldn't happen */ diff --git a/migration/trace-events b/migration/trace-events index XXXXXXX..XXXXXXX 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -XXX,XX +XXX,XX @@ loadvm_postcopy_handle_run(void) "" loadvm_postcopy_handle_resume(void) "" loadvm_postcopy_ram_handle_discard(void) "" loadvm_postcopy_ram_handle_discard_end(void) "" -loadvm_postcopy_ram_handle_discard_header(const char *ramid, uint16_t len) "%s: %ud" +loadvm_postcopy_ram_handle_discard_header(const char *ramid, uint16_t len) "%s: %u" loadvm_process_command(const char *s, uint16_t len) "com=%s len=%d" loadvm_process_command_ping(uint32_t val) "0x%x" loadvm_approve_switchover(unsigned int switchover_ack_pending_num) "Switchover ack pending num=%u" postcopy_ram_listen_thread_exit(void) "" postcopy_ram_listen_thread_start(void) "" qemu_savevm_send_postcopy_advise(void) "" -qemu_savevm_send_postcopy_ram_discard(const char *id, uint16_t len) "%s: %ud" +qemu_savevm_send_postcopy_ram_discard(const char *id, uint16_t len) "%s: %u" savevm_command_send(uint16_t command, uint16_t len) "com=0x%x len=%d" savevm_section_start(const char *id, unsigned int section_id) "%s, section_id %u" savevm_section_end(const char *id, unsigned int section_id, int ret) "%s, section_id %u -> %d" @@ -XXX,XX +XXX,XX @@ colo_flush_ram_cache_begin(uint64_t dirty_pages) "dirty_pages %" PRIu64 colo_flush_ram_cache_end(void) "" save_xbzrle_page_skipping(void) "" save_xbzrle_page_overflow(void) "" -ram_save_iterate_big_wait(uint64_t milliconds, int iterations) "big wait: %" PRIu64 " milliseconds, %d iterations" +ram_save_iterate_big_wait(uint64_t milliseconds, int iterations) "big wait: %" PRIu64 " milliseconds, %d iterations" ram_load_start(void) "" ram_load_complete(int ret, uint64_t seq_iter) "exit_code %d seq iteration %" PRIu64 ram_write_tracking_ramblock_start(const char *block_id, size_t page_size, void *addr, size_t length) "%s: page_size: %zu addr: %p length: %zu" -- 2.54.0
From: Bin Guo <guobin@linux.alibaba.com> vcpu_faults_current[] is uint8_t. The overflow assert was checked after the post-increment, so 255 would wrap to 0 and the assert would pass silently. Move the check before the increment and use < 255. Signed-off-by: Bin Guo <guobin@linux.alibaba.com> Link: https://lore.kernel.org/r/20260716101952.65329-2-guobin@linux.alibaba.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/postcopy-ram.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -XXX,XX +XXX,XX @@ void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid, /* * Account how many concurrent faults on this vCPU we trapped. See * comments above vcpu_faults_current[] on why it can be more than one. + * + * vcpu_faults_current[] is uint8_t, so assert before incrementing to + * catch overflow before it wraps. */ + assert(dc->vcpu_faults_current[cpu] < 255); if (dc->vcpu_faults_current[cpu]++ == 0) { dc->smp_cpus_down++; /* @@ -XXX,XX +XXX,XX @@ void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid, */ dc->last_begin = current; } - - /* Making sure it won't overflow - it really should never! */ - assert(dc->vcpu_faults_current[cpu] <= 255); } else { /* * For non-vCPU thread faults, we don't care about tid or cpu index -- 2.54.0
From: Bin Guo <guobin@linux.alibaba.com> Small cleanups for the postcopy blocktime fine-grained tracking feature: * Remove a redundant memset() on latency_buckets after g_new0(). * Use singular "Latency" in the HMP label for postcopy non-vCPU latency, consistent with other single-value labels. Signed-off-by: Bin Guo <guobin@linux.alibaba.com> Link: https://lore.kernel.org/r/20260716101952.65329-4-guobin@linux.alibaba.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration-hmp-cmds.c | 2 +- migration/postcopy-ram.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -XXX,XX +XXX,XX @@ static void migration_dump_blocktime(Monitor *mon, MigrationInfo *info) } if (info->has_postcopy_non_vcpu_latency) { - monitor_printf(mon, "Postcopy non-vCPU Latencies (ns): %" PRIu64 "\n", + monitor_printf(mon, "Postcopy non-vCPU Latency (ns): %" PRIu64 "\n", info->postcopy_non_vcpu_latency); } diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -XXX,XX +XXX,XX @@ static struct PostcopyBlocktimeContext *blocktime_context_new(void) unsigned int smp_cpus = ms->smp.cpus; PostcopyBlocktimeContext *ctx = g_new0(PostcopyBlocktimeContext, 1); - /* Initialize all counters to be zeros */ - memset(ctx->latency_buckets, 0, sizeof(ctx->latency_buckets)); - ctx->vcpu_blocktime_total = g_new0(uint64_t, smp_cpus); ctx->vcpu_faults_count = g_new0(uint64_t, smp_cpus); ctx->vcpu_faults_current = g_new0(uint8_t, smp_cpus); -- 2.54.0
The following changes since commit 84f07211cc5b4fc6a371559bf8a5de4fb068e648: Update version for v11.1.0 release (2026-08-11 10:04:46 -0400) are available in the Git repository at: https://gitlab.com/peterx/qemu.git tags/next-pull-request for you to fetch changes up to 2375e9b1239eb6ad794eaae29245b94f429c87c5: migration: Fix rare hang of migration_channel_read_peek() (2026-08-12 10:42:09 -0400) ---------------------------------------------------------------- migration/mem pull for 11.2 next 11.2: - Dongli's patch to add cpr-transfer support for HMP - Fabiano's doc update for migration on security issues - Gavin's fix for MMIO access support for memory APIs, reverting ram_device ops - Sam's migration test build fix for !ASN1 - Peter's a few migration hardening fixes ---------------------------------------------------------------- Dongli Zhang (1): migration/cpr: Add HMP support for cpr-transfer Fabiano Rosas (1): docs: Add security considerations for migration Gavin Shan (3): system/memory: Use memmove() for directly accessible regions system/memory: Use qemu_ram_move() for directly accessible regions system/memory: Make ram device region directly accessible Peter Xu (4): migration/multifd: Validate next_packet_size in zlib/zstd recv migration/multifd: Replace assert() with error_setg() in recv paths migration/ram: Check for RAMBlock size mismatch when parsing migration: Fix rare hang of migration_channel_read_peek() Sam Heney (1): tests/qtest/migration: Only build tls_no_hostname test with TASN1 docs/system/security.rst | 22 +++++++++++--- include/system/memory.h | 46 ++++++++++++++++++++++------- hw/remote/vfio-user-obj.c | 4 +-- migration/channel.c | 11 +++++-- migration/migration-hmp-cmds.c | 18 ++++++++++++ migration/multifd-qatzip.c | 5 +++- migration/multifd-qpl.c | 24 +++++++++++++--- migration/multifd-uadk.c | 24 +++++++++++++--- migration/multifd-zlib.c | 11 ++++++- migration/multifd-zstd.c | 11 ++++++- migration/ram.c | 11 +++++-- system/memory.c | 41 +------------------------- system/physmem.c | 48 +++++++++++++++++++++++++++++-- tests/qtest/migration/tls-tests.c | 4 ++- hmp-commands.hx | 12 +++++--- system/trace-events | 2 -- 16 files changed, 214 insertions(+), 80 deletions(-) -- 2.54.0
From: Fabiano Rosas <farosas@suse.de> Add the security considerations that are unique to migration and that do not already fall into one of the other categories. Some aspects are better framed as security architecture considerations, so extend that section to mention TLS and clarify that disk images and guest network also need to be isolated from other processes, not just other guests. Reviewed-by: Peter Xu <peterx@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260721131457.3062767-1-farosas@suse.de Signed-off-by: Peter Xu <peterx@redhat.com> --- docs/system/security.rst | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/system/security.rst b/docs/system/security.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/security.rst +++ b/docs/system/security.rst @@ -XXX,XX +XXX,XX @@ an issue as a normal bug. that affect the level 0 QEMU process. While these bugs should be fixed, they will not be triaged as security flaws at this time. +* **migration/snapshots**. Migration failures and snapshot load + failures are considered part of normal operation as long as the + source virtual machine and savevm file, respectively, are still + functional. Aborting the QEMU process at the migration/snapshot + destination is similarly not considered a security issue. The + migration stream is assumed to be secure as long as the design + principles described in the Architecture section are held, in + which case plain manipulation of the stream is not considered as + an attack vector. + * **low severity impact**. As a catch all rule, issues which are judged to have a "low" severity impact on the system will usually not justify handling as security bugs, nor assignment @@ -XXX,XX +XXX,XX @@ could allow malicious guests to gain code execution in QEMU. At this point the guest has escaped the virtual machine and is able to act in the context of the QEMU process on the host. -Guests often interact with other guests and share resources with them. A -malicious guest must not gain control of other guests or access their data. -Disk image files and network traffic must be protected from other guests unless -explicitly shared between them by the user. +Guests often interact with other guests and share resources with them. +A malicious guest must not gain control of other guests or access +their data. Disk image files and network traffic must be protected +from other guests, users and processes unless explicitly shared with +them by the user. Principle of Least Privilege '''''''''''''''''''''''''''' @@ -XXX,XX +XXX,XX @@ Some Linux distros already ship with UNIX groups for these devices by default. system calls that are not needed by QEMU, thereby reducing the host kernel attack surface. +- Transport Layer Security (TLS) protocol can be used to ensure authenticity and + encryption of the live migration connection where the network is untrusted. + Sensitive configurations ------------------------ -- 2.54.0
From: Dongli Zhang <dongli.zhang@oracle.com> Currently the cpr-transfer source QEMU instance cannot be driven entirely via HMP. The source must use QMP in order to specify both the main migration channel and the CPR channel. Extend the HMP migrate command with an optional CPR channel URI. When the migration mode is cpr-transfer, HMP uses this URI to build a CPR MigrationChannel in addition to the main migration channel. The new option is rejected unless the migration mode is cpr-transfer, so existing HMP migrate usage is unchanged. For example, source QEMU HMP commands can be something like below. The "-c unix:/tmp/cpr.sock" is for CPR URI. (qemu) migrate_set_parameter mode cpr-transfer (qemu) migrate -c unix:/tmp/cpr.sock tcp:0:50002 Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com> Reviewed-by: Dr. David Alan Gilbert <dave@treblig.org> Acked-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com> Link: https://lore.kernel.org/r/20260728085903.173265-1-dongli.zhang@oracle.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration-hmp-cmds.c | 18 ++++++++++++++++++ hmp-commands.hx | 12 ++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -XXX,XX +XXX,XX @@ void hmp_migrate(Monitor *mon, const QDict *qdict) bool detach = qdict_get_try_bool(qdict, "detach", false); bool resume = qdict_get_try_bool(qdict, "resume", false); const char *uri = qdict_get_str(qdict, "uri"); + const char *uri_cpr = qdict_get_try_str(qdict, "uri-cpr"); Error *err = NULL; g_autoptr(MigrationChannelList) caps = NULL; g_autoptr(MigrationChannel) channel = NULL; + g_autoptr(MigrationChannel) channel_cpr = NULL; if (!migrate_uri_parse(uri, &channel, &err)) { hmp_handle_error(mon, err); @@ -XXX,XX +XXX,XX @@ void hmp_migrate(Monitor *mon, const QDict *qdict) } QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel)); + if (uri_cpr) { + if (migrate_mode() != MIG_MODE_CPR_TRANSFER) { + error_setg(&err, "-c can only be used in cpr-transfer mode"); + hmp_handle_error(mon, err); + return; + } + + if (!migrate_uri_parse(uri_cpr, &channel_cpr, &err)) { + hmp_handle_error(mon, err); + return; + } + + channel_cpr->channel_type = MIGRATION_CHANNEL_TYPE_CPR; + QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel_cpr)); + } + qmp_migrate(NULL, true, caps, true, resume, &err); if (hmp_handle_error(mon, err)) { return; diff --git a/hmp-commands.hx b/hmp-commands.hx index XXXXXXX..XXXXXXX 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -XXX,XX +XXX,XX @@ ERST { .name = "migrate", - .args_type = "detach:-d,resume:-r,uri:s", - .params = "[-d] [-r] uri", + .args_type = "detach:-d,resume:-r,uri-cpr:-cs,uri:s", + .params = "[-d] [-r] [-c uri-cpr] uri", .help = "migrate to URI (using -d to not wait for completion)" - "\n\t\t\t -r to resume a paused postcopy migration", + "\n\t\t\t -r to resume a paused postcopy migration" + "\n\t\t\t -c to specify a CPR URI for cpr-transfer mode", .cmd = hmp_migrate, }, SRST -``migrate [-d] [-r]`` *uri* +``migrate [-d] [-r] [-c uri-cpr]`` *uri* Migrate the VM to *uri*. ``-d`` @@ -XXX,XX +XXX,XX @@ SRST query an ongoing migration process, use "info migrate". ``-r`` Resume a paused postcopy migration. + ``-c`` *uri-cpr* + Specify the CPR URI for cpr-transfer mode. It must be a UNIX domain + socket. ERST { -- 2.54.0
From: Gavin Shan <gshan@redhat.com> Similar to what's done in commit 4a73aee88140 ("softmmu: Use memmove in flatview_write_continue"), there are more sites where the overlapping source and destination buffer are allowed for the directly accessible regions. Use memmove() in those sites, listed as below. hw/remote/vfio-user-obj.c::vfu_object_mr_rw include/system/memory.h::address_space_read system/physmem.c::flatview_read_continue_step Signed-off-by: Gavin Shan <gshan@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Xu <peterx@redhat.com> Link: https://lore.kernel.org/r/20260728031731.286666-2-gshan@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/memory.h | 2 +- hw/remote/vfio-user-obj.c | 4 ++-- system/physmem.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ MemTxResult address_space_read(const AddressSpace *as, hwaddr addr, mr = flatview_translate(fv, addr, &addr1, &l, false, attrs); if (len == l && memory_access_is_direct(mr, false, attrs)) { ptr = qemu_map_ram_ptr(mr->ram_block, addr1); - memcpy(buf, ptr, len); + memmove(buf, ptr, len); } else { result = flatview_read_continue(fv, addr, attrs, buf, len, addr1, l, mr); diff --git a/hw/remote/vfio-user-obj.c b/hw/remote/vfio-user-obj.c index XXXXXXX..XXXXXXX 100644 --- a/hw/remote/vfio-user-obj.c +++ b/hw/remote/vfio-user-obj.c @@ -XXX,XX +XXX,XX @@ static int vfu_object_mr_rw(MemoryRegion *mr, uint8_t *buf, hwaddr offset, ram_ptr = memory_region_get_ram_ptr(mr); if (is_write) { - memcpy((ram_ptr + offset), buf, size); + memmove((ram_ptr + offset), buf, size); } else { - memcpy(buf, (ram_ptr + offset), size); + memmove(buf, (ram_ptr + offset), size); } return 0; diff --git a/system/physmem.c b/system/physmem.c index XXXXXXX..XXXXXXX 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -XXX,XX +XXX,XX @@ static MemTxResult flatview_read_continue_step(MemTxAttrs attrs, uint8_t *buf, uint8_t *ram_ptr = qemu_ram_ptr_length(mr->ram_block, mr_addr, l, false, false); - memcpy(buf, ram_ptr, *l); + memmove(buf, ram_ptr, *l); return MEMTX_OK; } -- 2.54.0
From: Gavin Shan <gshan@redhat.com> All ram device regions were turned to be indirectly accessible by commit 4a2e242bbb ("memory: Don't use memcpy for ram_device regions"). This leads to guest hang on attempt to build 'cuda-samples' as reported by Julia. The guest is started by the following command lines, with GH100 GPU card passed from the host. host$ lspci | grep GH100 0009:01:00.0 3D controller: NVIDIA Corporation GH100 [GH200 120GB / 480GB] (rev a1) host$ /home/sandbox/gavin/qemu.main/build/qemu-system-aarch64 \ -machine virt,gic-version=host,ras=on,highmem-mmio-size=4T \ -accel kvm -cpu host -smp cpus=48 -m size=8G \ -drive file=/home/gavin/sandbox/images/disk.qcow2,if=none,id=d0 \ -device virtio-blk-pci,id=vb0,bus=pcie.0,drive=d0,num-queues=4 \ -device vfio-pci-nohotplug,host=0009:01:00.0,bus=pcie.1.0 : guest$ cd cuda-samples/build guest$ make -j 20 clean guest$ make -j 20 : [ 54%] Linking CUDA executable graphMemoryNodes [ 54%] Built target graphMemoryNodes <no more output afterwards, guest becomes frozen here> guest$ qemu-system-aarch64: virtio: bogus descriptor or out of resources [ 555.814025] virtio_blk virtio0: [vda] new size: 268435456 512-byte logical blocks (137 GB/128 GiB) When the GPU's driver (NVidia open driver) is loaded on guest bootup, the memory blocks residing in the PCI BAR#4 of the GH100 GPU card can be presented to the guest through memory hot-add. The page cache can then be allocated from the hot added memory blocks when cuda-samples is being built. Afterwards, the page cache is sent to QEMU's virtio-blk device as part of the DMA request, the bounce buffer has to be used to accomodate the request as the corresponding memory region (MemoryRegion) is an indirectly accessible ram device region in qemu. However, the max bounce bufer size is only 4096 bytes by default and that is exhausted quickly, leading to a reset on the virtio-blk device and frozen guest eventually. QEMU ==== virtio_blk_handle_output virtio_blk_handle_vq virtio_blk_get_request virtqueue_pop virtqueue_split_pop virtqueue_map_desc address_space_map memory_access_is_direct # Return false memory_region_supports_direct_access (qemu) info mtree memory-region: pci_bridge_pci 0000000000000000-ffffffffffffffff (prio 0, container): pci_bridge_pci 0000042000000000-0000043fffffffff (prio 1, i/o): 0009:01:00.0 base BAR 4 0000042000000000-0000043fffffffff (prio 0, i/o): 0009:01:00.0 BAR 4 0000042000000000-000004379fffffff (prio 0, ramd): 0009:01:00.0 BAR 4 mmaps[0] This adds qemu_ram_move() where the aligned and small-sized accesses are handled by qatomics, and fall back to memmove() otherwise. The memove() for the directly accessible regions is replaced by qemu_ram_move() so that the issue covered by commit 4a2e242bbb (MMIO access instructions were optimized to SSE instructions) is fixed. This makes 'ram_device_mem_ops' redundant, paving the way to revert that commit to make the ram device region directly accessible again in the next patch. Besides, this also fixes the issue of the unexpected frozen reception on e1000 NIC in the scenario of DPDK due to the wrong Rx queue full indication caused by the following memcpy(), which is turned to 3 consective 'strb' instructions to the same location by glibc-2.24+ for aarch64. With this applied, the syntax of one-byte store is strictly ensured by a one-byte qatomic set. QEMU ==== e1000_receive_iov pci_dma_write pci_dma_rw dma_memory_rw dma_memory_rw_relaxed address_space_rw address_space_write flatview_write flatview_write_continue flatview_write_continue_step memcpy # 3 consective 'strb' instructions Reported-by: Julia Graham <jugraham@redhat.com> Reported-by: Liu Gang <liugang24219@sangfor.com.cn> Reported-by: Ding Hui <dinghui@sangfor.com.cn> Suggested-by: Michael S. Tsirkin <mst@redhat.com> Suggested-by: Peter Xu <peterx@redhat.com> Suggested-by: Richard Henderson <richard.henderson@linaro.org> Suggested-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Gavin Shan <gshan@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Link: https://lore.kernel.org/r/20260728031731.286666-3-gshan@redhat.com [peterx: remove src==dst check, fix doc, enhance comments, per PeterM, add R-b] Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/memory.h | 35 +++++++++++++++++++++++++++- hw/remote/vfio-user-obj.c | 4 ++-- system/physmem.c | 48 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ void address_space_register_map_client(AddressSpace *as, QEMUBH *bh); void address_space_unregister_map_client(AddressSpace *as, QEMUBH *bh); /* Internal functions, part of the implementation of address_space_read. */ + +/** + * qemu_ram_move: move data from or to ramblock + * + * @dst: destination where the data is moved to + * @src: source where the data is moved from + * @n: length of data to be moved + * + * Move @n bytes from @src to @dst, the memory areas may overlap. This + * provides the same semantics as memmove(), plus an additional stronger + * guarantee: if @n is 1, 2 or 4 or 8 bytes, and @src and @dst are both + * naturally aligned for that access size, then both the load and the store + * will be done as a single atomic access (with the semantics of + * qatomic_read() and qatomic_set()). + * + * This is the underlying function that we use to implement accesses by + * a guest vCPU or a device DMA operation to a ram block. The atomic + * guarantee is needed for two major cases: (A) When the ram block is + * backed by a PCI BAR passed through from a host device (and so it might + * be hardware registers that must be accessed exactly once at the right + * width); (B) When an emulated device updates a data structure shared in + * guest memory with guest software (e.g. a network device's set of tx and + * rx descriptor blocks), if a write to memory is accidentally performed + * multiple times then it can break the guest code when it busy polls the + * guest memory. + * + * We don't attempt to perform the exact access when it would be unaligned + * because this can't be done on all host architectures. Although this is + * strictly speaking not doing what would happen on real hardware, we don't + * think there are going to be situations where that matters in practice. + */ +void qemu_ram_move(void *dst, const void *src, size_t n); + MemTxResult address_space_read_full(const AddressSpace *as, hwaddr addr, MemTxAttrs attrs, void *buf, hwaddr len); MemTxResult flatview_read_continue(FlatView *fv, hwaddr addr, @@ -XXX,XX +XXX,XX @@ MemTxResult address_space_read(const AddressSpace *as, hwaddr addr, mr = flatview_translate(fv, addr, &addr1, &l, false, attrs); if (len == l && memory_access_is_direct(mr, false, attrs)) { ptr = qemu_map_ram_ptr(mr->ram_block, addr1); - memmove(buf, ptr, len); + qemu_ram_move(buf, ptr, len); } else { result = flatview_read_continue(fv, addr, attrs, buf, len, addr1, l, mr); diff --git a/hw/remote/vfio-user-obj.c b/hw/remote/vfio-user-obj.c index XXXXXXX..XXXXXXX 100644 --- a/hw/remote/vfio-user-obj.c +++ b/hw/remote/vfio-user-obj.c @@ -XXX,XX +XXX,XX @@ static int vfu_object_mr_rw(MemoryRegion *mr, uint8_t *buf, hwaddr offset, ram_ptr = memory_region_get_ram_ptr(mr); if (is_write) { - memmove((ram_ptr + offset), buf, size); + qemu_ram_move((ram_ptr + offset), buf, size); } else { - memmove(buf, (ram_ptr + offset), size); + qemu_ram_move(buf, (ram_ptr + offset), size); } return 0; diff --git a/system/physmem.c b/system/physmem.c index XXXXXXX..XXXXXXX 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -XXX,XX +XXX,XX @@ void memory_region_flush_rom_device(MemoryRegion *mr, hwaddr addr, hwaddr size) invalidate_and_set_dirty(mr, addr, size); } +void qemu_ram_move(void *dst, const void *src, size_t n) +{ + uintptr_t test, len; + + if (n == 0) { + return; + } + + /* + * Calculate "the lowest set bit" over @src, @dst and @n, result put + * into @len (which guarantees a power-of-two). With that and the + * later check (len!=n), it makes sure that we will only do the atomic + * ops when: + * + * (1) @n is a power-of-two + * (2) @src and @dst addresses are both aligned to @n + */ + test = (uintptr_t)src | (uintptr_t)dst | n; + len = test & -test; + + /* Overlapping buffers, unaligned or oversized access */ + if (n > 8 || len != n) { + memmove(dst, src, n); + return; + } + + switch (len) { + case 1: + qatomic_set((uint8_t *)dst, qatomic_read((uint8_t *)src)); + break; + case 2: + qatomic_set((uint16_t *)dst, qatomic_read((uint16_t *)src)); + break; + case 4: + qatomic_set((uint32_t *)dst, qatomic_read((uint32_t *)src)); + break; + case 8: + qatomic_set((uint64_t *)dst, qatomic_read((uint64_t *)src)); + break; + default: + g_assert_not_reached(); + } +} + int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr) { unsigned access_size_max = mr->ops->valid.max_access_size; @@ -XXX,XX +XXX,XX @@ static MemTxResult flatview_write_continue_step(MemTxAttrs attrs, uint8_t *ram_ptr = qemu_ram_ptr_length(mr->ram_block, mr_addr, l, false, true); - memmove(ram_ptr, buf, *l); + qemu_ram_move(ram_ptr, buf, *l); invalidate_and_set_dirty(mr, mr_addr, *l); return MEMTX_OK; @@ -XXX,XX +XXX,XX @@ static MemTxResult flatview_read_continue_step(MemTxAttrs attrs, uint8_t *buf, uint8_t *ram_ptr = qemu_ram_ptr_length(mr->ram_block, mr_addr, l, false, false); - memmove(buf, ram_ptr, *l); + qemu_ram_move(buf, ram_ptr, *l); return MEMTX_OK; } -- 2.54.0
From: Gavin Shan <gshan@redhat.com> This basically reverts 4a2e242bbb30 ("memory: Don't use memcpy for ram_device regions") to make ram device region directly accessible again. With this, the bounce buffer is bypassed in address_space_map() when a ram device region is involved, potentially avoid to overrun the (small) bounce buffer. Reported-by: Julia Graham <jugraham@redhat.com> Suggested-by: Michael S. Tsirkin <mst@redhat.com> Suggested-by: Peter Xu <peterx@redhat.com> Suggested-by: Richard Henderson <richard.henderson@linaro.org> Suggested-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Gavin Shan <gshan@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Link: https://lore.kernel.org/r/20260728031731.286666-4-gshan@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/memory.h | 11 ++--------- system/memory.c | 41 +---------------------------------------- system/trace-events | 2 -- 3 files changed, 3 insertions(+), 51 deletions(-) diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ static inline bool memory_region_supports_direct_access(const MemoryRegion *mr) if (memory_region_is_romd(mr)) { return true; } - if (!memory_region_is_ram(mr)) { - return false; - } - /* - * RAM DEVICE regions can be accessed directly using memcpy, but it might - * be MMIO and access using mempy can be wrong (e.g., using instructions not - * intended for MMIO access). So we treat this as IO. - */ - return !memory_region_is_ram_device(mr); + + return memory_region_is_ram(mr); } static inline bool memory_access_is_direct(const MemoryRegion *mr, diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ const MemoryRegionOps unassigned_mem_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static uint64_t memory_region_ram_device_read(void *opaque, - hwaddr addr, unsigned size) -{ - MemoryRegion *mr = opaque; - uint64_t data = ldn_he_p(mr->ram_block->host + addr, size); - - trace_memory_region_ram_device_read(get_cpu_index(), mr, addr, data, size); - - return data; -} - -static void memory_region_ram_device_write(void *opaque, hwaddr addr, - uint64_t data, unsigned size) -{ - MemoryRegion *mr = opaque; - - trace_memory_region_ram_device_write(get_cpu_index(), mr, addr, data, size); - - stn_he_p(mr->ram_block->host + addr, size, data); -} - -static const MemoryRegionOps ram_device_mem_ops = { - .read = memory_region_ram_device_read, - .write = memory_region_ram_device_write, - .endianness = HOST_BIG_ENDIAN ? DEVICE_BIG_ENDIAN : DEVICE_LITTLE_ENDIAN, - .valid = { - .min_access_size = 1, - .max_access_size = 8, - .unaligned = true, - }, - .impl = { - .min_access_size = 1, - .max_access_size = 8, - .unaligned = true, - }, -}; - bool memory_region_access_valid(MemoryRegion *mr, hwaddr addr, unsigned size, @@ -XXX,XX +XXX,XX @@ void memory_region_init_ram_device_ptr(MemoryRegion *mr, Object *owner, const char *name, uint64_t size, void *ptr) { - memory_region_init_io(mr, owner, &ram_device_mem_ops, mr, name, size); - mr->ram = true; + memory_region_init_ram_ptr(mr, owner, name, size, ptr); mr->ram_device = true; - memory_region_set_ram_ptr(mr, size, ptr); } void memory_region_init_alias(MemoryRegion *mr, Object *owner, diff --git a/system/trace-events b/system/trace-events index XXXXXXX..XXXXXXX 100644 --- a/system/trace-events +++ b/system/trace-events @@ -XXX,XX +XXX,XX @@ memory_region_ops_read(int cpu_index, void *mr, uint64_t addr, uint64_t value, u memory_region_ops_write(int cpu_index, void *mr, uint64_t addr, uint64_t value, unsigned size, const char *name) "cpu %d mr %p addr 0x%"PRIx64" value 0x%"PRIx64" size %u name '%s'" memory_region_subpage_read(int cpu_index, void *mr, uint64_t offset, uint64_t value, unsigned size) "cpu %d mr %p offset 0x%"PRIx64" value 0x%"PRIx64" size %u" memory_region_subpage_write(int cpu_index, void *mr, uint64_t offset, uint64_t value, unsigned size) "cpu %d mr %p offset 0x%"PRIx64" value 0x%"PRIx64" size %u" -memory_region_ram_device_read(int cpu_index, void *mr, uint64_t addr, uint64_t value, unsigned size) "cpu %d mr %p addr 0x%"PRIx64" value 0x%"PRIx64" size %u" -memory_region_ram_device_write(int cpu_index, void *mr, uint64_t addr, uint64_t value, unsigned size) "cpu %d mr %p addr 0x%"PRIx64" value 0x%"PRIx64" size %u" memory_region_sync_dirty(const char *mr, const char *listener, int global) "mr '%s' listener '%s' synced (global=%d)" flatview_new(void *view, void *root) "%p (root %p)" flatview_destroy(void *view, void *root) "%p (root %p)" -- 2.54.0
From: Sam Heney <github@me.samiser.xyz> The test_precopy_tcp_tls_no_hostname test and its start hook use TestMigrateTLSX509 and migrate_hook_start_tls_x509_common(), which are only defined when CONFIG_TASN1 is set. This means building with gnutls enabled but libtasn1 unavailable fails: ../tests/qtest/migration/tls-tests.c: In function 'migrate_hook_start_tls_x509_no_host': ../tests/qtest/migration/tls-tests.c:510:5: error: unknown type name 'TestMigrateTLSX509' Guard the test with CONFIG_TASN1 like the other x509 tests. Fixes: df9c38b19af8 ("tests/qtest/migration: Add a NULL parameters test for TLS") Signed-off-by: Sam Heney <github@me.samiser.xyz> Link: https://lore.kernel.org/r/5f24de0e-49af-45a7-927f-f79b203bb335@app.fastmail.com Signed-off-by: Peter Xu <peterx@redhat.com> --- tests/qtest/migration/tls-tests.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/qtest/migration/tls-tests.c b/tests/qtest/migration/tls-tests.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/tls-tests.c +++ b/tests/qtest/migration/tls-tests.c @@ -XXX,XX +XXX,XX @@ static void test_precopy_tcp_no_tls(char *name, MigrateCommon *args) test_precopy_common(args); } +#ifdef CONFIG_TASN1 static void * migrate_hook_start_tls_x509_no_host(QTestState *from, QTestState *to) { @@ -XXX,XX +XXX,XX @@ static void test_precopy_tcp_tls_no_hostname(char *name, MigrateCommon *args) test_precopy_common(args); } -#ifdef CONFIG_TASN1 static void test_precopy_tcp_tls_x509_default_host(char *name, MigrateCommon *args) { @@ -XXX,XX +XXX,XX @@ void migration_test_add_tls(MigrationTestEnv *env) migration_test_add("/migration/precopy/tcp/no-tls", test_precopy_tcp_no_tls); +#ifdef CONFIG_TASN1 migration_test_add("/migration/precopy/tcp/tls/no-hostname", test_precopy_tcp_tls_no_hostname); +#endif /* CONFIG_TASN1 */ migration_test_add("/migration/precopy/unix/tls/psk", test_precopy_unix_tls_psk); -- 2.54.0
The zlib and zstd multifd compression backends read next_packet_size from the incoming migration stream and use it directly as the read length into a fixed-size buffer (MULTIFD_PACKET_SIZE * 2 = 1MB). A malicious migration source can set next_packet_size bigger than allocated, causing a heap buffer overflow write on the destination. Add a check against zbuff_len before reading, matching what the qatzip backend already does. Also replace the assert(in_size == 0) for empty packets with proper error reporting, since the value is wire-controlled, meanwhile assert() stops working with -DNDEBUG builds. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3737 Reported-by: xlabai <xlabai@tencent.com> Reported-by: Jules Denardou <jules.denardou@datadoghq.com> Reported-by: Tristan Madani <tristan@talencesecurity.com> Reported-by: david korczynski (@david1766) Reported-by: huntr bubble (@bubblehuntr) Cc: qemu-stable <qemu-stable@nongnu.org> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260728210417.1925078-3-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/multifd-zlib.c | 11 ++++++++++- migration/multifd-zstd.c | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/migration/multifd-zlib.c b/migration/multifd-zlib.c index XXXXXXX..XXXXXXX 100644 --- a/migration/multifd-zlib.c +++ b/migration/multifd-zlib.c @@ -XXX,XX +XXX,XX @@ static int multifd_zlib_recv(MultiFDRecvParams *p, Error **errp) return -1; } + if (in_size > z->zbuff_len) { + error_setg(errp, "multifd %u: next_packet_size %"PRIu32 + " exceeds allocated %"PRIu32, p->id, in_size, z->zbuff_len); + return -1; + } + multifd_recv_zero_page_process(p); if (!p->normal_num) { - assert(in_size == 0); + if (in_size != 0) { + error_setg(errp, "multifd %u: expected empty packet", p->id); + return -1; + } return 0; } diff --git a/migration/multifd-zstd.c b/migration/multifd-zstd.c index XXXXXXX..XXXXXXX 100644 --- a/migration/multifd-zstd.c +++ b/migration/multifd-zstd.c @@ -XXX,XX +XXX,XX @@ static int multifd_zstd_recv(MultiFDRecvParams *p, Error **errp) return -1; } + if (in_size > z->zbuff_len) { + error_setg(errp, "multifd %u: next_packet_size %"PRIu32 + " exceeds allocated %"PRIu32, p->id, in_size, z->zbuff_len); + return -1; + } + multifd_recv_zero_page_process(p); if (!p->normal_num) { - assert(in_size == 0); + if (in_size != 0) { + error_setg(errp, "multifd %u: expected empty packet", p->id); + return -1; + } return 0; } -- 2.54.0
QPL and UADK multifd backends use assert() to validate wire-controlled fields like per-page compressed lengths and packet size consistency. These asserts will stop working with -DNDEBUG builds, so may stop working. Replace all assert() calls in the receive path with proper error_setg() so validation failures are reported gracefully rather than crashing or silently ignored. While at it, touch up an assert() in qatzip recv path too. Cc: qemu-stable <qemu-stable@nongnu.org> Cc: Yuan Liu <yuan1.liu@intel.com> Cc: Yichen Wang <yichen.wang@bytedance.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260728210417.1925078-4-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/multifd-qatzip.c | 5 ++++- migration/multifd-qpl.c | 24 ++++++++++++++++++++---- migration/multifd-uadk.c | 24 ++++++++++++++++++++---- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/migration/multifd-qatzip.c b/migration/multifd-qatzip.c index XXXXXXX..XXXXXXX 100644 --- a/migration/multifd-qatzip.c +++ b/migration/multifd-qatzip.c @@ -XXX,XX +XXX,XX @@ static int qatzip_recv(MultiFDRecvParams *p, Error **errp) multifd_recv_zero_page_process(p); if (!p->normal_num) { - assert(in_size == 0); + if (in_size != 0) { + error_setg(errp, "multifd %u: expected empty packet", p->id); + return -1; + } return 0; } diff --git a/migration/multifd-qpl.c b/migration/multifd-qpl.c index XXXXXXX..XXXXXXX 100644 --- a/migration/multifd-qpl.c +++ b/migration/multifd-qpl.c @@ -XXX,XX +XXX,XX @@ static int multifd_qpl_recv(MultiFDRecvParams *p, Error **errp) } multifd_recv_zero_page_process(p); if (!p->normal_num) { - assert(in_size == 0); + if (in_size != 0) { + error_setg(errp, "multifd %u: expected empty packet", p->id); + return -1; + } return 0; } /* read compressed page lengths */ len = p->normal_num * sizeof(uint32_t); - assert(len < in_size); + if (len >= in_size) { + error_setg(errp, "multifd %u: header len %"PRIu32 + " >= packet size %"PRIu32, p->id, len, in_size); + return -1; + } ret = qio_channel_read_all(p->c, (void *) qpl->zlen, len, errp); if (ret != 0) { return ret; } for (int i = 0; i < p->normal_num; i++) { qpl->zlen[i] = be32_to_cpu(qpl->zlen[i]); - assert(qpl->zlen[i] <= multifd_ram_page_size()); + if (qpl->zlen[i] > multifd_ram_page_size()) { + error_setg(errp, "multifd %u: page %d compressed len %" + PRIu32" too large", p->id, i, qpl->zlen[i]); + return -1; + } zbuf_len += qpl->zlen[i]; ramblock_recv_bitmap_set_offset(p->block, p->normal[i]); } /* read compressed pages */ - assert(in_size == len + zbuf_len); + if (in_size != len + zbuf_len) { + error_setg(errp, "multifd %u: packet size %"PRIu32 + " != header %"PRIu32" + data %"PRIu32, + p->id, in_size, len, zbuf_len); + return -1; + } ret = qio_channel_read_all(p->c, (void *) qpl->zbuf, zbuf_len, errp); if (ret != 0) { return ret; diff --git a/migration/multifd-uadk.c b/migration/multifd-uadk.c index XXXXXXX..XXXXXXX 100644 --- a/migration/multifd-uadk.c +++ b/migration/multifd-uadk.c @@ -XXX,XX +XXX,XX @@ static int multifd_uadk_recv(MultiFDRecvParams *p, Error **errp) multifd_recv_zero_page_process(p); if (!p->normal_num) { - assert(in_size == 0); + if (in_size != 0) { + error_setg(errp, "multifd %u: expected empty packet", p->id); + return -1; + } return 0; } /* read compressed data lengths */ - assert(hdr_len < in_size); + if (hdr_len >= in_size) { + error_setg(errp, "multifd %u: header len %"PRIu32 + " >= packet size %"PRIu32, p->id, hdr_len, in_size); + return -1; + } ret = qio_channel_read_all(p->c, (void *) uadk_data->buf_hdr, hdr_len, errp); if (ret != 0) { @@ -XXX,XX +XXX,XX @@ static int multifd_uadk_recv(MultiFDRecvParams *p, Error **errp) for (int i = 0; i < p->normal_num; i++) { uadk_data->buf_hdr[i] = be32_to_cpu(uadk_data->buf_hdr[i]); + if (uadk_data->buf_hdr[i] > page_size) { + error_setg(errp, "multifd %u: page %d compressed len %"PRIu32 + " too large", p->id, i, uadk_data->buf_hdr[i]); + return -1; + } data_len += uadk_data->buf_hdr[i]; - assert(uadk_data->buf_hdr[i] <= page_size); } /* read compressed data */ - assert(in_size == hdr_len + data_len); + if (in_size != hdr_len + data_len) { + error_setg(errp, "multifd %u: packet size %"PRIu32 + " != header %"PRIu32" + data %"PRIu32, + p->id, in_size, hdr_len, data_len); + return -1; + } ret = qio_channel_read_all(p->c, (void *)buf, data_len, errp); if (ret != 0) { return ret; -- 2.54.0
Add an underflow check for the subtract of total RAMBlock size to make sure it won't underflow. It should not happen in production systems but only if the migration stream was hijacked, which is not a real concern since migration channel is trusted. Still protect against it. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4013 Reported-by: Tristan Madani <tristan@talencesecurity.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260728210417.1925078-6-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/ram.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static int parse_ramblocks(QEMUFile *f, ram_addr_t total_ram_bytes) int ret = 0; /* Synchronize RAM block list */ - while (!ret && total_ram_bytes) { + while (total_ram_bytes) { RAMBlock *block; char id[256]; ram_addr_t length; @@ -XXX,XX +XXX,XX @@ static int parse_ramblocks(QEMUFile *f, ram_addr_t total_ram_bytes) error_report("Unknown ramblock \"%s\", cannot accept " "migration", id); ret = -EINVAL; + break; + } + + if (usub64_overflow(total_ram_bytes, length, &total_ram_bytes)) { + error_report("%s: RAMBlock '%s' size underflow total RAM size", + __func__, block->idstr); + ret = -EFAULT; + break; } - total_ram_bytes -= length; } return ret; -- 2.54.0
In an unlikely case, when a migration stream is attached to the destination QEMU and only send <4 bytes to the channel as magic, it's possible that migration_channel_read_peek() may spin forever. Fix it by adding a manual sleep for partial read. Since the path isn't attached to a coroutine, it means when partial read happens, there's yet not much we can do but hang the main thread, it will happen even for len==0 case. It means monitors can hang due to this, either partial read or no data arrived (but connection established). Leave this for later, the hope is this is extremely rare in production. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3889 Reported-by: Feifan Qian <bea1e@proton.me> Cc: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Link: https://lore.kernel.org/r/20260812124327.2572363-1-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/channel.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/migration/channel.c b/migration/channel.c index XXXXXXX..XXXXXXX 100644 --- a/migration/channel.c +++ b/migration/channel.c @@ -XXX,XX +XXX,XX @@ int migration_channel_read_peek(QIOChannel *ioc, if (len == buflen) { break; + } else if (len == QIO_CHANNEL_ERR_BLOCK) { + qio_channel_wait_cond(ioc, G_IO_IN); + } else { + /* + * When partially ready, we can't use qio_channel_wait_cond() + * because it will return immediately. Apply a manual wait. + */ + assert(!qemu_in_coroutine()); + g_usleep(1000); } - - qio_channel_wait_cond(ioc, G_IO_IN); } return 0; -- 2.54.0