On incoming migration qxl_post_load() replays the tracked cursor and surface commands by handing their guest addresses straight to spice, without revalidating them. Those addresses were checked when the guest submitted them, but the guest may have freed or reused that memory before migration, so qxl's tracked pointer can be stale. spice-server then re-parses the command from that memory and, for a stale cursor, reads a garbage shape pointer -- aborting the target in memslot_get_virt() (again, spice-server function) and failing the migration.
Validate each replayed command with qxl_guest_phys2virt(report_bug=false) before adding it to the replay list, and for a cursor also validate the nested shape pointer. Commands that no longer resolve are skipped rather than replayed. report_bug is false so a stale pointer is not mistaken for a live guest error, which would needlessly disable a healthy guest's display. Signed-off-by: Andrey Drobyshev <[email protected]> --- hw/display/qxl.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/hw/display/qxl.c b/hw/display/qxl.c index be620b9c94d..b9b19849738 100644 --- a/hw/display/qxl.c +++ b/hw/display/qxl.c @@ -2329,6 +2329,37 @@ static void qxl_create_memslots(PCIQXLDevice *d) } } +/* + * Validate a command tracked for loadvm replay before handing its guest + * address to spice-server. + */ +static bool qxl_loadvm_cmd_valid(PCIQXLDevice *d, QXLPHYSICAL data, + uint32_t type) +{ + switch (type) { + case QXL_CMD_SURFACE: + return qxl_guest_phys2virt(d, data, + sizeof(QXLSurfaceCmd), false) != NULL; + + case QXL_CMD_CURSOR: { + QXLCursorCmd *cmd = qxl_guest_phys2virt(d, data, sizeof(QXLCursorCmd), + false); + + if (!cmd) { + return false; + } + if (le32_to_cpu(cmd->type) == QXL_CURSOR_SET) { + return qxl_guest_phys2virt(d, le64_to_cpu(cmd->u.set.shape), + sizeof(QXLCursor), false) != NULL; + } + return true; + } + + default: + g_assert_not_reached(); + } +} + static int qxl_post_load(void *opaque, int version) { PCIQXLDevice* d = opaque; @@ -2367,12 +2398,17 @@ static int qxl_post_load(void *opaque, int version) if (d->guest_surfaces.cmds[in] == 0) { continue; } + if (!qxl_loadvm_cmd_valid(d, d->guest_surfaces.cmds[in], + QXL_CMD_SURFACE)) { + continue; + } cmds[out].cmd.data = d->guest_surfaces.cmds[in]; cmds[out].cmd.type = QXL_CMD_SURFACE; cmds[out].group_id = MEMSLOT_GROUP_GUEST; out++; } - if (d->guest_cursor) { + if (d->guest_cursor && + qxl_loadvm_cmd_valid(d, d->guest_cursor, QXL_CMD_CURSOR)) { cmds[out].cmd.data = d->guest_cursor; cmds[out].cmd.type = QXL_CMD_CURSOR; cmds[out].group_id = MEMSLOT_GROUP_GUEST; -- 2.47.1
