migration/mem pull for 11.2

v2: fixes macos build error
 
 - 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
 -----BEGIN PGP SIGNATURE-----
 
 iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCan3KARIccGV0ZXJ4QHJl
 ZGhhdC5jb20ACgkQO1/MzfOr1wa76QD/eBLnPtDvmpNHNH3+bm/3XC3zwyy7v69U
 bGK3ocwI3sQA/j9o5FCc7xDCA0QaW6RMeerlLXvXR0uwH46UESKKDloF
 =/Jbs
 -----END PGP SIGNATURE-----

Merge tag 'next-pull-request' of https://gitlab.com/peterx/qemu into staging

migration/mem pull for 11.2

v2: fixes macos build error

- 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

# -----BEGIN PGP SIGNATURE-----
#
# iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCan3KARIccGV0ZXJ4QHJl
# ZGhhdC5jb20ACgkQO1/MzfOr1wa76QD/eBLnPtDvmpNHNH3+bm/3XC3zwyy7v69U
# bGK3ocwI3sQA/j9o5FCc7xDCA0QaW6RMeerlLXvXR0uwH46UESKKDloF
# =/Jbs
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 13 Aug 2026 06:43:29 AM PDT
# gpg:                using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706
# gpg:                issuer "peterx@redhat.com"
# gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown]
# gpg:                 aka "Peter Xu <peterx@redhat.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D  D1A9 3B5F CCCD F3AB D706

* tag 'next-pull-request' of https://gitlab.com/peterx/qemu:
  migration: Fix rare hang of migration_channel_read_peek()
  migration/ram: Check for RAMBlock size mismatch when parsing
  migration/multifd: Replace assert() with error_setg() in recv paths
  migration/multifd: Validate next_packet_size in zlib/zstd recv
  tests/qtest/migration: Only build tls_no_hostname test with TASN1
  system/memory: Make ram device region directly accessible
  system/memory: Use qemu_ram_move() for directly accessible regions
  system/memory: Use memmove() for directly accessible regions
  migration/cpr: Add HMP support for cpr-transfer
  docs: Add security considerations for migration

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2026-08-13 07:14:33 -07:00
commit 42f8118926
16 changed files with 216 additions and 82 deletions

View file

@ -133,6 +133,16 @@ 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
@ -159,10 +169,11 @@ 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
''''''''''''''''''''''''''''
@ -223,6 +234,9 @@ 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
------------------------

View file

@ -928,16 +928,17 @@ 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``
@ -945,6 +946,9 @@ 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
{

View file

@ -375,9 +375,9 @@ 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);
qemu_ram_move((ram_ptr + offset), buf, size);
} else {
memcpy(buf, (ram_ptr + offset), size);
qemu_ram_move(buf, (ram_ptr + offset), size);
}
return 0;

View file

@ -2668,6 +2668,39 @@ 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,
@ -2685,15 +2718,8 @@ 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,
@ -2741,7 +2767,7 @@ 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);
qemu_ram_move(buf, ptr, len);
} else {
result = flatview_read_continue(fv, addr, attrs, buf, len,
addr1, l, mr);

View file

@ -296,9 +296,16 @@ 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;

View file

@ -837,9 +837,11 @@ 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);
@ -847,6 +849,22 @@ 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;

View file

@ -348,7 +348,10 @@ 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;
}

View file

@ -664,26 +664,42 @@ 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;

View file

@ -245,12 +245,19 @@ 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) {
@ -259,12 +266,21 @@ 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;

View file

@ -216,10 +216,19 @@ 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;
}

View file

@ -210,10 +210,19 @@ 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;
}

View file

@ -4263,15 +4263,15 @@ static int parse_ramblock(QEMUFile *f, RAMBlock *block, ram_addr_t length)
return ret;
}
static int parse_ramblocks(QEMUFile *f, ram_addr_t total_ram_bytes)
static int parse_ramblocks(QEMUFile *f, uint64_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;
uint64_t length;
int len = qemu_get_byte(f);
qemu_get_buffer(f, (uint8_t *)id, len);
@ -4285,8 +4285,15 @@ 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;

View file

@ -1364,43 +1364,6 @@ 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,
@ -1692,10 +1655,8 @@ 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,

View file

@ -3158,6 +3158,50 @@ 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;
@ -3270,7 +3314,7 @@ 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;
@ -3363,7 +3407,7 @@ 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);
qemu_ram_move(buf, ram_ptr, *l);
return MEMTX_OK;
}

View file

@ -20,8 +20,6 @@ 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)"

View file

@ -492,6 +492,7 @@ 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)
{
@ -519,7 +520,6 @@ 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)
{
@ -719,8 +719,10 @@ 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);