From 1da3e2fe5c10a4b8c05948362a4e6bab6351ca76 Mon Sep 17 00:00:00 2001 From: cunshunxia Date: Fri, 19 Dec 2025 14:59:11 +0800 Subject: [PATCH] fix CVE-2025-12748 --- ...-before-parsing-the-whole-domain-XML.patch | 72 ++ ...-before-parsing-the-whole-domain-XML.patch | 178 ++++ ...Enable-ETHERNET-Network-mode-support.patch | 872 ++++++++++++++++++ ...river-Add-domainManagedSave-callback.patch | 160 ++++ ...domainSave-domainSaveFlags-callbacks.patch | 217 +++++ ...r-Implement-domain-restore-callbacks.patch | 225 +++++ ...driver-Implement-more-save-callbacks.patch | 226 +++++ 0001-qemu-Decompose-qemuSaveImageOpen.patch | 435 +++++++++ ...ing-corrupt-save-image-file-to-calle.patch | 224 +++++ ...-before-parsing-the-whole-domain-XML.patch | 71 ++ ...-before-parsing-the-whole-domain-XML.patch | 73 ++ ...-before-parsing-the-whole-domain-XML.patch | 64 ++ ...-before-parsing-the-whole-domain-XML.patch | 178 ++++ ...-before-parsing-the-whole-domain-XML.patch | 395 ++++++++ libvirt.spec | 20 +- 15 files changed, 3409 insertions(+), 1 deletion(-) create mode 100644 0001-bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0001-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0001-ch-Enable-ETHERNET-Network-mode-support.patch create mode 100644 0001-ch_driver-Add-domainManagedSave-callback.patch create mode 100644 0001-ch_driver-Add-domainSave-domainSaveFlags-callbacks.patch create mode 100644 0001-ch_driver-Implement-domain-restore-callbacks.patch create mode 100644 0001-ch_driver-Implement-more-save-callbacks.patch create mode 100644 0001-qemu-Decompose-qemuSaveImageOpen.patch create mode 100644 0001-qemu-Move-unlinking-corrupt-save-image-file-to-calle.patch create mode 100644 0002-libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0003-lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0004-vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0005-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch create mode 100644 0006-qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch diff --git a/0001-bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0001-bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..a14fdce --- /dev/null +++ b/0001-bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,72 @@ +From b45f10bc0a2f30ccdbf2cb55da2e4f85b3ebfb23 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:31:12 +0100 +Subject: [PATCH 1/6] bhyve: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/bhyve/bhyve_driver.c | 24 ++++++++++++++++++------ + 1 file changed, 18 insertions(+), 6 deletions(-) + +diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c +index 00a484ae21..3a4e83d3d2 100644 +--- a/src/bhyve/bhyve_driver.c ++++ b/src/bhyve/bhyve_driver.c +@@ -486,6 +486,15 @@ bhyveDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flag + if (!caps) + return NULL; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, provconn->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) ++ return NULL; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ + if ((def = virDomainDefParseString(xml, privconn->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; +@@ -493,9 +502,6 @@ bhyveDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flag + if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) +- goto cleanup; +- + if (bhyveDomainAssignAddresses(def, NULL) < 0) + goto cleanup; + +@@ -889,11 +895,17 @@ bhyveDomainCreateXML(virConnectPtr conn, + if (flags & VIR_DOMAIN_START_AUTODESTROY) + start_flags |= VIR_BHYVE_PROCESS_START_AUTODESTROY; + +- if ((def = virDomainDefParseString(xml, privconn->xmlopt, +- NULL, parse_flags)) == NULL) +- goto cleanup; ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, provconn->xmlopt, parse_flags))) ++ return NULL; + + if (virDomainCreateXMLEnsureACL(conn, def) < 0) ++ return NULL; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ ++ if ((def = virDomainDefParseString(xml, privconn->xmlopt, ++ NULL, parse_flags)) == NULL) + goto cleanup; + + if (bhyveDomainAssignAddresses(def, NULL) < 0) +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0001-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..6e6fc70 --- /dev/null +++ b/0001-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,178 @@ +From eb4322dfe8fff544d6dac01b2748c20f78f00d69 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 16:23:30 +0100 +Subject: [PATCH] ch: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +This is one of the more complex ones since there is also a function that +reads relevant metadata from a save image XML. In order not to extract +the parsing out of the function (and make the function basically trivial +and all callers more complex) add a callback to the function which will +be used to check the ACLs. And since this function is called in APIs +that perform ACL checks both with and without flags, add two of them for +good measure. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/ch/ch_driver.c | 76 ++++++++++++++++++++++++++++++++-------------- + 1 file changed, 53 insertions(+), 23 deletions(-) + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index 8ec90e1192..662857f88e 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -216,14 +216,19 @@ chDomainCreateXML(virConnectPtr conn, + if (flags & VIR_DOMAIN_START_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(vmdef = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainCreateXMLEnsureACL(conn, vmdef) < 0) ++ return NULL; ++ ++ g_clear_pointer(&vmdef, virDomainDefFree); + + if ((vmdef = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; + +- if (virDomainCreateXMLEnsureACL(conn, vmdef) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, + &vmdef, + driver->xmlopt, +@@ -347,6 +352,15 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (flags & VIR_DOMAIN_START_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(vmdef = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, vmdef) < 0) ++ return NULL; ++ ++ g_clear_pointer(&vmdef, virDomainDefFree); ++ + if ((vmdef = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; +@@ -354,9 +368,6 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (virXMLCheckIllegalChars("name", vmdef->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, vmdef) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, &vmdef, + driver->xmlopt, + 0, &oldDef))) +@@ -920,16 +931,24 @@ chDomainSaveXMLRead(int fd) + return g_steal_pointer(&xml); + } + +-static int chDomainSaveImageRead(virCHDriver *driver, ++static int chDomainSaveImageRead(virConnectPtr conn, + const char *path, +- virDomainDef **ret_def) ++ virDomainDef **ret_def, ++ unsigned int flags, ++ int (*ensureACL)(virConnectPtr, virDomainDef *), ++ int (*ensureACLWithFlags)(virConnectPtr, ++ virDomainDef *, ++ unsigned int)) + { ++ virCHDriver *driver = conn->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver); + g_autoptr(virDomainDef) def = NULL; + g_autofree char *from = NULL; + g_autofree char *xml = NULL; + VIR_AUTOCLOSE fd = -1; + int ret = -1; ++ unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + + from = g_strdup_printf("%s/%s", path, CH_SAVE_XML); + if ((fd = virFileOpenAs(from, O_RDONLY, 0, cfg->user, cfg->group, 0)) < 0) { +@@ -942,9 +961,23 @@ static int chDomainSaveImageRead(virCHDriver *driver, + if (!(xml = chDomainSaveXMLRead(fd))) + goto end; + +- if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL, +- VIR_DOMAIN_DEF_PARSE_INACTIVE | +- VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ if (ensureACL || ensureACLWithFlags) { ++ /* Parse only the IDs for ACL checks */ ++ g_autoptr(virDomainDef) aclDef = virDomainDefIDsParseString(xml, ++ driver->xmlopt, ++ parse_flags); ++ ++ if (!aclDef) ++ goto end; ++ ++ if (ensureACL && ensureACL(conn, aclDef) < 0) ++ goto end; ++ ++ if (ensureACLWithFlags && ensureACLWithFlags(conn, aclDef, flags) < 0) ++ goto end; ++ } ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL, parse_flags))) + goto end; + + *ret_def = g_steal_pointer(&def); +@@ -965,10 +998,9 @@ chDomainSaveImageGetXMLDesc(virConnectPtr conn, + + virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); + +- if (chDomainSaveImageRead(driver, path, &def) < 0) +- goto cleanup; +- +- if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) ++ if (chDomainSaveImageRead(conn, path, &def, flags, ++ virDomainSaveImageGetXMLDescEnsureACL, ++ NULL) < 0) + goto cleanup; + + ret = virDomainDefFormat(def, driver->xmlopt, +@@ -1068,10 +1100,9 @@ chDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + goto cleanup; + + path = chDomainManagedSavePath(driver, vm); +- if (chDomainSaveImageRead(driver, path, &def) < 0) +- goto cleanup; +- +- if (virDomainManagedSaveGetXMLDescEnsureACL(dom->conn, def, flags) < 0) ++ if (chDomainSaveImageRead(dom->conn, path, &def, flags, ++ NULL, ++ virDomainManagedSaveGetXMLDescEnsureACL) < 0) + goto cleanup; + + ret = virDomainDefFormat(def, driver->xmlopt, +@@ -1123,10 +1154,9 @@ chDomainRestoreFlags(virConnectPtr conn, + return -1; + } + +- if (chDomainSaveImageRead(driver, from, &def) < 0) +- goto cleanup; +- +- if (virDomainRestoreFlagsEnsureACL(conn, def) < 0) ++ if (chDomainSaveImageRead(conn, from, &def, flags, ++ virDomainRestoreFlagsEnsureACL, ++ NULL) < 0) + goto cleanup; + + if (chDomainSaveRestoreAdditionalValidation(driver, def) < 0) +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch-Enable-ETHERNET-Network-mode-support.patch b/0001-ch-Enable-ETHERNET-Network-mode-support.patch new file mode 100644 index 0000000..e1e4dd6 --- /dev/null +++ b/0001-ch-Enable-ETHERNET-Network-mode-support.patch @@ -0,0 +1,872 @@ +From 6316f26cd2dbb506569ac5b757dd36a5dd38088a Mon Sep 17 00:00:00 2001 +From: Praveen K Paladugu +Date: Tue, 16 Jan 2024 15:25:43 -0600 +Subject: [PATCH] ch: Enable ETHERNET Network mode support + +enable VIR_DOMAIN_NET_TYPE_ETHERNET network support for ch guests. + +Tested with following interface config: + + + + + + + +Signed-off-by: Praveen K Paladugu +Signed-off-by: Michal Privoznik +Reviewed-by: Michal Privoznik +--- + po/POTFILES | 1 + + src/ch/ch_conf.h | 4 + + src/ch/ch_domain.c | 41 ++++++++ + src/ch/ch_domain.h | 3 + + src/ch/ch_interface.c | 99 ++++++++++++++++++++ + src/ch/ch_interface.h | 35 +++++++ + src/ch/ch_monitor.c | 207 ++++++++++++++--------------------------- + src/ch/ch_monitor.h | 7 +- + src/ch/ch_process.c | 211 +++++++++++++++++++++++++++++++++++++++++- + src/ch/meson.build | 2 + + 10 files changed, 468 insertions(+), 142 deletions(-) + create mode 100644 src/ch/ch_interface.c + create mode 100644 src/ch/ch_interface.h + +diff --git a/po/POTFILES b/po/POTFILES +index b594a8dd39..e48b9023e2 100644 +--- a/po/POTFILES ++++ b/po/POTFILES +@@ -21,6 +21,7 @@ src/bhyve/bhyve_process.c + src/ch/ch_conf.c + src/ch/ch_domain.c + src/ch/ch_driver.c ++src/ch/ch_interface.c + src/ch/ch_monitor.c + src/ch/ch_process.c + src/conf/backup_conf.c +diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h +index 5b9b42540d..579eca894e 100644 +--- a/src/ch/ch_conf.h ++++ b/src/ch/ch_conf.h +@@ -23,6 +23,7 @@ + #include "virdomainobjlist.h" + #include "virthread.h" + #include "ch_capabilities.h" ++#include "virebtables.h" + + #define CH_DRIVER_NAME "CH" + #define CH_CMD "cloud-hypervisor" +@@ -75,6 +76,9 @@ struct _virCHDriver + + /* pid file FD, ensures two copies of the driver can't use the same root */ + int lockFD; ++ ++ /* Immutable pointer, lockless APIs. Pointless abstraction */ ++ ebtablesContext *ebtables; + }; + + virCaps *virCHDriverCapsInit(void); +diff --git a/src/ch/ch_domain.c b/src/ch/ch_domain.c +index 2f3fae6758..a6bf749d89 100644 +--- a/src/ch/ch_domain.c ++++ b/src/ch/ch_domain.c +@@ -22,6 +22,7 @@ + + #include "ch_domain.h" + #include "domain_driver.h" ++#include "domain_validate.h" + #include "virchrdev.h" + #include "virlog.h" + #include "virtime.h" +@@ -355,3 +356,43 @@ virCHDomainObjFromDomain(virDomainPtr domain) + + return vm; + } ++ ++int ++virCHDomainValidateActualNetDef(virDomainNetDef *net) ++{ ++ virDomainNetType actualType = virDomainNetGetActualType(net); ++ ++ /* hypervisor-agnostic validation */ ++ if (virDomainActualNetDefValidate(net) < 0) ++ return -1; ++ ++ /* CH specific validation */ ++ switch (actualType) { ++ case VIR_DOMAIN_NET_TYPE_ETHERNET: ++ if (net->guestIP.nips > 1) { ++ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", ++ _("ethernet type supports a single guest ip")); ++ return -1; ++ } ++ break; ++ case VIR_DOMAIN_NET_TYPE_VHOSTUSER: ++ case VIR_DOMAIN_NET_TYPE_BRIDGE: ++ case VIR_DOMAIN_NET_TYPE_NETWORK: ++ case VIR_DOMAIN_NET_TYPE_DIRECT: ++ case VIR_DOMAIN_NET_TYPE_USER: ++ case VIR_DOMAIN_NET_TYPE_SERVER: ++ case VIR_DOMAIN_NET_TYPE_CLIENT: ++ case VIR_DOMAIN_NET_TYPE_MCAST: ++ case VIR_DOMAIN_NET_TYPE_INTERNAL: ++ case VIR_DOMAIN_NET_TYPE_HOSTDEV: ++ case VIR_DOMAIN_NET_TYPE_UDP: ++ case VIR_DOMAIN_NET_TYPE_VDPA: ++ case VIR_DOMAIN_NET_TYPE_NULL: ++ case VIR_DOMAIN_NET_TYPE_VDS: ++ case VIR_DOMAIN_NET_TYPE_LAST: ++ default: ++ break; ++ } ++ ++ return 0; ++} +diff --git a/src/ch/ch_domain.h b/src/ch/ch_domain.h +index 4990914e9f..8dea2b2123 100644 +--- a/src/ch/ch_domain.h ++++ b/src/ch/ch_domain.h +@@ -75,3 +75,6 @@ virCHDomainGetMachineName(virDomainObj *vm); + + virDomainObj * + virCHDomainObjFromDomain(virDomainPtr domain); ++ ++int ++virCHDomainValidateActualNetDef(virDomainNetDef *net); +diff --git a/src/ch/ch_interface.c b/src/ch/ch_interface.c +new file mode 100644 +index 0000000000..c7af6a35fa +--- /dev/null ++++ b/src/ch/ch_interface.c +@@ -0,0 +1,99 @@ ++/* ++ * Copyright Microsoft Corp. 2023 ++ * ++ * ch_interface.c: methods to connect guest interfaces to appropriate host ++ * backends ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see ++ * . ++ */ ++ ++#include ++ ++#include "domain_conf.h" ++#include "domain_interface.h" ++#include "virebtables.h" ++#include "viralloc.h" ++#include "ch_interface.h" ++#include "virjson.h" ++#include "virlog.h" ++ ++ ++#define VIR_FROM_THIS VIR_FROM_CH ++ ++VIR_LOG_INIT("ch.ch_interface"); ++ ++/** ++ * virCHConnetNetworkInterfaces: ++ * @driver: pointer to ch driver object ++ * @vm: pointer to domain definition ++ * @net: pointer to a guest net ++ * @nicindexes: returned array of FDs of guest interfaces ++ * @nnicindexes: returned number of guest interfaces ++ * ++ * ++ * Returns 0 on success, -1 on error. ++ */ ++int ++virCHConnetNetworkInterfaces(virCHDriver *driver, ++ virDomainDef *vm, ++ virDomainNetDef *net, ++ int *tapfds, int **nicindexes, size_t *nnicindexes) ++{ ++ virDomainNetType actualType = virDomainNetGetActualType(net); ++ ++ ++ switch (actualType) { ++ case VIR_DOMAIN_NET_TYPE_ETHERNET: ++ ++ if (virDomainInterfaceEthernetConnect(vm, net, ++ driver->ebtables, false, ++ driver->privileged, tapfds, ++ net->driver.virtio.queues) < 0) ++ return -1; ++ ++ G_GNUC_FALLTHROUGH; ++ case VIR_DOMAIN_NET_TYPE_NETWORK: ++ case VIR_DOMAIN_NET_TYPE_BRIDGE: ++ case VIR_DOMAIN_NET_TYPE_DIRECT: ++ if (nicindexes && nnicindexes && net->ifname) { ++ int nicindex = 0; ++ ++ if (virNetDevGetIndex(net->ifname, &nicindex) < 0) ++ return -1; ++ ++ VIR_APPEND_ELEMENT(*nicindexes, *nnicindexes, nicindex); ++ } ++ ++ break; ++ case VIR_DOMAIN_NET_TYPE_USER: ++ case VIR_DOMAIN_NET_TYPE_SERVER: ++ case VIR_DOMAIN_NET_TYPE_CLIENT: ++ case VIR_DOMAIN_NET_TYPE_MCAST: ++ case VIR_DOMAIN_NET_TYPE_VHOSTUSER: ++ case VIR_DOMAIN_NET_TYPE_INTERNAL: ++ case VIR_DOMAIN_NET_TYPE_HOSTDEV: ++ case VIR_DOMAIN_NET_TYPE_UDP: ++ case VIR_DOMAIN_NET_TYPE_VDPA: ++ case VIR_DOMAIN_NET_TYPE_NULL: ++ case VIR_DOMAIN_NET_TYPE_VDS: ++ case VIR_DOMAIN_NET_TYPE_LAST: ++ default: ++ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, ++ _("Unsupported Network type %1$d"), actualType); ++ return -1; ++ } ++ ++ return 0; ++} +diff --git a/src/ch/ch_interface.h b/src/ch/ch_interface.h +new file mode 100644 +index 0000000000..df87d4511f +--- /dev/null ++++ b/src/ch/ch_interface.h +@@ -0,0 +1,35 @@ ++/* ++ * Copyright Microsoft Corp. 2023 ++ * ++ * ch_interface.c: methods to connect guest interfaces to appropriate host ++ * backends ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see ++ * . ++ */ ++ ++#pragma once ++ ++ ++#include "ch_conf.h" ++#include "virconftypes.h" ++ ++ ++int ++virCHConnetNetworkInterfaces(virCHDriver *driver, ++ virDomainDef *vmdef, ++ virDomainNetDef *netdef, ++ int *tapfds, ++ int **nicindexes, ++ size_t *nnicindexes); +diff --git a/src/ch/ch_monitor.c b/src/ch/ch_monitor.c +index 6f960c3a51..62ba72bb82 100644 +--- a/src/ch/ch_monitor.c ++++ b/src/ch/ch_monitor.c +@@ -24,7 +24,11 @@ + #include + #include + ++#include "datatypes.h" ++#include "ch_conf.h" ++#include "ch_interface.h" + #include "ch_monitor.h" ++#include "domain_interface.h" + #include "viralloc.h" + #include "vircommand.h" + #include "virerror.h" +@@ -258,148 +262,92 @@ virCHMonitorBuildDisksJson(virJSONValue *content, virDomainDef *vmdef) + return 0; + } + +-static int +-virCHMonitorBuildNetJson(virJSONValue *nets, +- virDomainNetDef *netdef, +- size_t *nnicindexes, +- int **nicindexes) ++/** ++ * virCHMonitorBuildNetJson: ++ * @net: pointer to a guest network definition ++ * @jsonstr: returned network json ++ * ++ * Build net json to send to CH ++ * Returns 0 on success or -1 in case of error ++ */ ++int ++virCHMonitorBuildNetJson(virDomainNetDef *net, char **jsonstr) + { +- virDomainNetType netType = virDomainNetGetActualType(netdef); + char macaddr[VIR_MAC_STRING_BUFLEN]; +- g_autoptr(virJSONValue) net = NULL; +- +- // check net type at first +- net = virJSONValueNewObject(); +- +- switch (netType) { +- case VIR_DOMAIN_NET_TYPE_ETHERNET: +- if (netdef->guestIP.nips == 1) { +- const virNetDevIPAddr *ip = netdef->guestIP.ips[0]; +- g_autofree char *addr = NULL; +- virSocketAddr netmask; +- g_autofree char *netmaskStr = NULL; +- +- if (!(addr = virSocketAddrFormat(&ip->address))) +- return -1; +- if (virJSONValueObjectAppendString(net, "ip", addr) < 0) +- return -1; +- +- if (virSocketAddrPrefixToNetmask(ip->prefix, &netmask, AF_INET) < 0) { +- virReportError(VIR_ERR_INTERNAL_ERROR, +- _("Failed to translate net prefix %1$d to netmask"), +- ip->prefix); +- return -1; +- } +- if (!(netmaskStr = virSocketAddrFormat(&netmask))) +- return -1; +- if (virJSONValueObjectAppendString(net, "mask", netmaskStr) < 0) +- return -1; +- } else if (netdef->guestIP.nips > 1) { +- virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", +- _("ethernet type supports a single guest ip")); +- } ++ g_autoptr(virJSONValue) net_json = virJSONValueNewObject(); ++ virDomainNetType actualType = virDomainNetGetActualType(net); + +- /* network and bridge use a tap device, and direct uses a +- * macvtap device +- */ +- if (nicindexes && nnicindexes && netdef->ifname) { +- int nicindex = 0; ++ if (actualType == VIR_DOMAIN_NET_TYPE_ETHERNET && ++ net->guestIP.nips == 1) { ++ const virNetDevIPAddr *ip; ++ g_autofree char *addr = NULL; ++ virSocketAddr netmask; ++ g_autofree char *netmaskStr = NULL; + +- if (virNetDevGetIndex(netdef->ifname, &nicindex) < 0) +- return -1; ++ ip = net->guestIP.ips[0]; + +- VIR_APPEND_ELEMENT(*nicindexes, *nnicindexes, nicindex); +- } +- break; +- case VIR_DOMAIN_NET_TYPE_VHOSTUSER: +- if ((virDomainChrType)netdef->data.vhostuser->type != VIR_DOMAIN_CHR_TYPE_UNIX) { +- virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", +- _("vhost_user type support UNIX socket in this CH")); +- return -1; +- } else { +- if (virJSONValueObjectAppendString(net, "vhost_socket", netdef->data.vhostuser->data.nix.path) < 0) +- return -1; +- if (virJSONValueObjectAppendBoolean(net, "vhost_user", true) < 0) +- return -1; +- } +- break; +- case VIR_DOMAIN_NET_TYPE_BRIDGE: +- case VIR_DOMAIN_NET_TYPE_NETWORK: +- case VIR_DOMAIN_NET_TYPE_DIRECT: +- case VIR_DOMAIN_NET_TYPE_USER: +- case VIR_DOMAIN_NET_TYPE_SERVER: +- case VIR_DOMAIN_NET_TYPE_CLIENT: +- case VIR_DOMAIN_NET_TYPE_MCAST: +- case VIR_DOMAIN_NET_TYPE_INTERNAL: +- case VIR_DOMAIN_NET_TYPE_HOSTDEV: +- case VIR_DOMAIN_NET_TYPE_UDP: +- case VIR_DOMAIN_NET_TYPE_VDPA: +- case VIR_DOMAIN_NET_TYPE_NULL: +- case VIR_DOMAIN_NET_TYPE_VDS: +- case VIR_DOMAIN_NET_TYPE_LAST: +- default: +- virReportEnumRangeError(virDomainNetType, netType); +- return -1; +- } +- +- if (netdef->ifname != NULL) { +- if (virJSONValueObjectAppendString(net, "tap", netdef->ifname) < 0) +- return -1; +- } +- if (virJSONValueObjectAppendString(net, "mac", virMacAddrFormat(&netdef->mac, macaddr)) < 0) +- return -1; ++ if (!(addr = virSocketAddrFormat(&ip->address))) ++ return -1; ++ ++ if (virJSONValueObjectAppendString(net_json, "ip", addr) < 0) ++ return -1; ++ ++ if (virSocketAddrPrefixToNetmask(ip->prefix, &netmask, AF_INET) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, ++ _("Failed to translate net prefix %1$d to netmask"), ++ ip->prefix); ++ return -1; ++ } ++ ++ if (!(netmaskStr = virSocketAddrFormat(&netmask))) ++ return -1; ++ ++ if (virJSONValueObjectAppendString(net_json, "mask", netmaskStr) < 0) ++ return -1; ++ } + ++ if (virJSONValueObjectAppendString(net_json, "mac", ++ virMacAddrFormat(&net->mac, macaddr)) < 0) ++ return -1; + +- if (netdef->virtio != NULL) { +- if (netdef->virtio->iommu == VIR_TRISTATE_SWITCH_ON) { +- if (virJSONValueObjectAppendBoolean(net, "iommu", true) < 0) ++ if (net->virtio != NULL) { ++ if (net->virtio->iommu == VIR_TRISTATE_SWITCH_ON) { ++ if (virJSONValueObjectAppendBoolean(net_json, "iommu", true) < 0) + return -1; + } + } +- if (netdef->driver.virtio.queues) { +- if (virJSONValueObjectAppendNumberInt(net, "num_queues", netdef->driver.virtio.queues) < 0) ++ ++ /* Cloud-Hypervisor expects number of queues. 1 for rx and 1 for tx. ++ * Multiply queue pairs by 2 to provide total number of queues to CH ++ */ ++ if (net->driver.virtio.queues) { ++ if (virJSONValueObjectAppendNumberInt(net_json, "num_queues", ++ 2 * net->driver.virtio.queues) < 0) + return -1; + } + +- if (netdef->driver.virtio.rx_queue_size || netdef->driver.virtio.tx_queue_size) { +- if (netdef->driver.virtio.rx_queue_size != netdef->driver.virtio.tx_queue_size) { ++ if (net->driver.virtio.rx_queue_size || net->driver.virtio.tx_queue_size) { ++ if (net->driver.virtio.rx_queue_size != ++ net->driver.virtio.tx_queue_size) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, +- _("virtio rx_queue_size option %1$d is not same with tx_queue_size %2$d"), +- netdef->driver.virtio.rx_queue_size, +- netdef->driver.virtio.tx_queue_size); ++ _("virtio rx_queue_size option %1$d is not same with tx_queue_size %2$d"), ++ net->driver.virtio.rx_queue_size, ++ net->driver.virtio.tx_queue_size); + return -1; + } +- if (virJSONValueObjectAppendNumberInt(net, "queue_size", netdef->driver.virtio.rx_queue_size) < 0) ++ if (virJSONValueObjectAppendNumberInt(net_json, "queue_size", ++ net->driver.virtio.rx_queue_size) < 0) + return -1; + } + +- if (virJSONValueArrayAppend(nets, &net) < 0) +- return -1; +- +- return 0; +-} +- +-static int +-virCHMonitorBuildNetsJson(virJSONValue *content, +- virDomainDef *vmdef, +- size_t *nnicindexes, +- int **nicindexes) +-{ +- g_autoptr(virJSONValue) nets = NULL; +- size_t i; +- +- if (vmdef->nnets > 0) { +- nets = virJSONValueNewArray(); +- +- for (i = 0; i < vmdef->nnets; i++) { +- if (virCHMonitorBuildNetJson(nets, vmdef->nets[i], +- nnicindexes, nicindexes) < 0) +- return -1; +- } +- if (virJSONValueObjectAppend(content, "net", &nets) < 0) ++ if (net->mtu) { ++ if (virJSONValueObjectAppendNumberInt(net_json, "mtu", net->mtu) < 0) + return -1; + } + ++ if (!(*jsonstr = virJSONValueToString(net_json, false))) ++ return -1; ++ + return 0; + } + +@@ -456,11 +404,8 @@ virCHMonitorBuildDevicesJson(virJSONValue *content, + } + + static int +-virCHMonitorBuildVMJson(virCHDriver *driver, +- virDomainDef *vmdef, +- char **jsonstr, +- size_t *nnicindexes, +- int **nicindexes) ++virCHMonitorBuildVMJson(virCHDriver *driver, virDomainDef *vmdef, ++ char **jsonstr) + { + g_autoptr(virJSONValue) content = virJSONValueNewObject(); + +@@ -490,10 +435,6 @@ virCHMonitorBuildVMJson(virCHDriver *driver, + if (virCHMonitorBuildDisksJson(content, vmdef) < 0) + return -1; + +- +- if (virCHMonitorBuildNetsJson(content, vmdef, nnicindexes, nicindexes) < 0) +- return -1; +- + if (virCHMonitorBuildDevicesJson(content, vmdef) < 0) + return -1; + +@@ -877,10 +818,7 @@ virCHMonitorShutdownVMM(virCHMonitor *mon) + } + + int +-virCHMonitorCreateVM(virCHDriver *driver, +- virCHMonitor *mon, +- size_t *nnicindexes, +- int **nicindexes) ++virCHMonitorCreateVM(virCHDriver *driver, virCHMonitor *mon) + { + g_autofree char *url = NULL; + int responseCode = 0; +@@ -892,8 +830,7 @@ virCHMonitorCreateVM(virCHDriver *driver, + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Content-Type: application/json"); + +- if (virCHMonitorBuildVMJson(driver, mon->vm->def, &payload, +- nnicindexes, nicindexes) != 0) ++ if (virCHMonitorBuildVMJson(driver, mon->vm->def, &payload) != 0) + return -1; + + VIR_WITH_OBJECT_LOCK_GUARD(mon) { +diff --git a/src/ch/ch_monitor.h b/src/ch/ch_monitor.h +index bbfa77cdff..47b4e7abbd 100644 +--- a/src/ch/ch_monitor.h ++++ b/src/ch/ch_monitor.h +@@ -104,10 +104,7 @@ void virCHMonitorClose(virCHMonitor *mon); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(virCHMonitor, virCHMonitorClose); + + +-int virCHMonitorCreateVM(virCHDriver *driver, +- virCHMonitor *mon, +- size_t *nnicindexes, +- int **nicindexes); ++int virCHMonitorCreateVM(virCHDriver *driver, virCHMonitor *mon); + int virCHMonitorBootVM(virCHMonitor *mon); + int virCHMonitorShutdownVM(virCHMonitor *mon); + int virCHMonitorRebootVM(virCHMonitor *mon); +@@ -123,3 +120,5 @@ size_t virCHMonitorGetThreadInfo(virCHMonitor *mon, bool refresh, + virCHMonitorThreadInfo **threads); + int virCHMonitorGetIOThreads(virCHMonitor *mon, + virDomainIOThreadInfo ***iothreads); ++int ++virCHMonitorBuildNetJson(virDomainNetDef *netdef, char **jsonstr); +diff --git a/src/ch/ch_process.c b/src/ch/ch_process.c +index f3bb4a7280..86d3190324 100644 +--- a/src/ch/ch_process.c ++++ b/src/ch/ch_process.c +@@ -22,14 +22,19 @@ + + #include + #include ++#include + + #include "ch_domain.h" + #include "ch_monitor.h" + #include "ch_process.h" + #include "domain_cgroup.h" ++#include "domain_interface.h" + #include "virerror.h" ++#include "virfile.h" + #include "virjson.h" + #include "virlog.h" ++#include "virstring.h" ++#include "ch_interface.h" + + #define VIR_FROM_THIS VIR_FROM_CH + +@@ -448,13 +453,192 @@ virCHProcessSetupVcpus(virDomainObj *vm) + return 0; + } + ++ ++#define PKT_TIMEOUT_MS 500 /* ms */ ++ ++static char * ++chSocketRecv(int sock) ++{ ++ struct pollfd pfds[1]; ++ char *buf = NULL; ++ size_t buf_len = 1024; ++ int ret; ++ ++ buf = g_new0(char, buf_len); ++ ++ pfds[0].fd = sock; ++ pfds[0].events = POLLIN; ++ ++ do { ++ ret = poll(pfds, G_N_ELEMENTS(pfds), PKT_TIMEOUT_MS); ++ } while (ret < 0 && errno == EINTR); ++ ++ if (ret <= 0) { ++ if (ret < 0) { ++ virReportSystemError(errno, _("Poll on sock %1$d failed"), sock); ++ } else if (ret == 0) { ++ virReportSystemError(errno, _("Poll on sock %1$d timed out"), sock); ++ } ++ return NULL; ++ } ++ ++ do { ++ ret = recv(sock, buf, buf_len - 1, 0); ++ } while (ret < 0 && errno == EINTR); ++ ++ if (ret < 0) { ++ virReportSystemError(errno, _("recv on sock %1$d failed"), sock); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&buf); ++} ++ ++#undef PKT_TIMEOUT_MS ++ ++/** ++ * chProcessAddNetworkDevices: ++ * @driver: pointer to ch driver object ++ * @mon: pointer to the monitor object ++ * @vmdef: pointer to domain definition ++ * @nicindexes: returned array of FDs of guest interfaces ++ * @nnicindexes: returned number of network indexes ++ * ++ * Send tap fds to CH process via AddNet api. Capture the network indexes of ++ * guest interfaces in nicindexes. ++ * ++ * Returns 0 on success, -1 on error. ++ */ ++static int ++chProcessAddNetworkDevices(virCHDriver *driver, ++ virCHMonitor *mon, ++ virDomainDef *vmdef, ++ int **nicindexes, ++ size_t *nnicindexes) ++{ ++ size_t i; ++ VIR_AUTOCLOSE mon_sockfd = -1; ++ struct sockaddr_un server_addr; ++ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; ++ g_auto(virBuffer) http_headers = VIR_BUFFER_INITIALIZER; ++ ++ if (!virBitmapIsBitSet(driver->chCaps, CH_MULTIFD_IN_ADDNET)) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Guest networking is not supported by this version of ch")); ++ return -1; ++ } ++ ++ mon_sockfd = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (mon_sockfd < 0) { ++ virReportSystemError(errno, "%s", _("Failed to open a UNIX socket")); ++ return -1; ++ } ++ ++ memset(&server_addr, 0, sizeof(server_addr)); ++ server_addr.sun_family = AF_UNIX; ++ if (virStrcpyStatic(server_addr.sun_path, mon->socketpath) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, ++ _("UNIX socket path '%1$s' too long"), mon->socketpath); ++ return -1; ++ } ++ ++ if (connect(mon_sockfd, (struct sockaddr *)&server_addr, ++ sizeof(server_addr)) == -1) { ++ virReportSystemError(errno, "%s", _("Failed to connect to mon socket")); ++ return -1; ++ } ++ ++ virBufferAddLit(&http_headers, "PUT /api/v1/vm.add-net HTTP/1.1\r\n"); ++ virBufferAddLit(&http_headers, "Host: localhost\r\n"); ++ virBufferAddLit(&http_headers, "Content-Type: application/json\r\n"); ++ ++ for (i = 0; i < vmdef->nnets; i++) { ++ g_autofree int *tapfds = NULL; ++ g_autofree char *payload = NULL; ++ g_autofree char *response = NULL; ++ size_t j; ++ size_t tapfd_len; ++ int http_res; ++ int rc; ++ ++ if (vmdef->nets[i]->driver.virtio.queues == 0) { ++ /* "queues" here refers to queue pairs. When 0, initialize ++ * queue pairs to 1. ++ */ ++ vmdef->nets[i]->driver.virtio.queues = 1; ++ } ++ tapfd_len = vmdef->nets[i]->driver.virtio.queues; ++ ++ if (virCHDomainValidateActualNetDef(vmdef->nets[i]) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("net definition failed validation")); ++ return -1; ++ } ++ ++ tapfds = g_new0(int, tapfd_len); ++ memset(tapfds, -1, (tapfd_len) * sizeof(int)); ++ ++ /* Connect Guest interfaces */ ++ if (virCHConnetNetworkInterfaces(driver, vmdef, vmdef->nets[i], tapfds, ++ nicindexes, nnicindexes) < 0) ++ return -1; ++ ++ if (virCHMonitorBuildNetJson(vmdef->nets[i], &payload) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Failed to build net json")); ++ return -1; ++ } ++ ++ VIR_DEBUG("payload sent with net-add request to CH = %s", payload); ++ ++ virBufferAsprintf(&buf, "%s", virBufferCurrentContent(&http_headers)); ++ virBufferAsprintf(&buf, "Content-Length: %ld\r\n\r\n", strlen(payload)); ++ virBufferAsprintf(&buf, "%s", payload); ++ payload = virBufferContentAndReset(&buf); ++ ++ rc = virSocketSendMsgWithFDs(mon_sockfd, payload, tapfds, tapfd_len); ++ ++ /* Close sent tap fds in Libvirt, as they have been dup()ed in CH */ ++ for (j = 0; j < tapfd_len; j++) { ++ VIR_FORCE_CLOSE(tapfds[j]); ++ } ++ ++ if (rc < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Failed to send net-add request to CH")); ++ return -1; ++ } ++ ++ /* Process the response from CH */ ++ response = chSocketRecv(mon_sockfd); ++ if (response == NULL) { ++ return -1; ++ } ++ ++ /* Parse the HTTP response code */ ++ rc = sscanf(response, "HTTP/1.%*d %d", &http_res); ++ if (rc != 1) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Failed to parse HTTP response code")); ++ return -1; ++ } ++ if (http_res != 204 && http_res != 200) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, ++ _("Unexpected response from CH: %1$d"), http_res); ++ return -1; ++ } ++ } ++ ++ return 0; ++} ++ + /** + * virCHProcessStart: + * @driver: pointer to driver structure + * @vm: pointer to virtual machine structure + * @reason: reason for switching vm to running state + * +- * Starts Cloud-Hypervisor listen on a local socket ++ * Starts Cloud-Hypervisor listening on a local socket + * + * Returns 0 on success or -1 in case of error + */ +@@ -483,8 +667,7 @@ virCHProcessStart(virCHDriver *driver, + goto cleanup; + } + +- if (virCHMonitorCreateVM(driver, priv->monitor, +- &nnicindexes, &nicindexes) < 0) { ++ if (virCHMonitorCreateVM(driver, priv->monitor) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create guest VM")); + goto cleanup; +@@ -495,6 +678,13 @@ virCHProcessStart(virCHDriver *driver, + vm->def->id = vm->pid; + priv->machineName = virCHDomainGetMachineName(vm); + ++ if (chProcessAddNetworkDevices(driver, priv->monitor, vm->def, ++ &nicindexes, &nnicindexes) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Failed while adding guest interfaces")); ++ goto cleanup; ++ } ++ + if (virDomainCgroupSetupCgroup("ch", vm, + nnicindexes, nicindexes, + &priv->cgroup, +@@ -507,6 +697,10 @@ virCHProcessStart(virCHDriver *driver, + if (virCHProcessInitCpuAffinity(vm) < 0) + goto cleanup; + ++ /* Bring up netdevs before starting CPUs */ ++ if (virDomainInterfaceStartDevices(vm->def) < 0) ++ return -1; ++ + if (virCHMonitorBootVM(priv->monitor) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to boot guest VM")); +@@ -552,6 +746,9 @@ virCHProcessStop(virCHDriver *driver G_GNUC_UNUSED, + int ret; + int retries = 0; + virCHDomainObjPrivate *priv = vm->privateData; ++ virCHDriverConfig *cfg = virCHDriverGetConfig(driver); ++ virDomainDef *def = vm->def; ++ size_t i; + + VIR_DEBUG("Stopping VM name=%s pid=%d reason=%d", + vm->def->name, (int)vm->pid, (int)reason); +@@ -560,6 +757,14 @@ virCHProcessStop(virCHDriver *driver G_GNUC_UNUSED, + g_clear_pointer(&priv->monitor, virCHMonitorClose); + } + ++ /* de-activate netdevs after stopping vm */ ++ ignore_value(virDomainInterfaceStopDevices(vm->def)); ++ ++ for (i = 0; i < def->nnets; i++) { ++ virDomainNetDef *net = def->nets[i]; ++ virDomainInterfaceDeleteDevice(def, net, false, cfg->stateDir); ++ } ++ + retry: + if ((ret = virDomainCgroupRemoveCgroup(vm, + priv->cgroup, +diff --git a/src/ch/meson.build b/src/ch/meson.build +index 6c8bf9c43f..633966aac7 100644 +--- a/src/ch/meson.build ++++ b/src/ch/meson.build +@@ -7,6 +7,8 @@ ch_driver_sources = [ + 'ch_domain.h', + 'ch_driver.c', + 'ch_driver.h', ++ 'ch_interface.c', ++ 'ch_interface.h', + 'ch_monitor.c', + 'ch_monitor.h', + 'ch_process.c', +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch_driver-Add-domainManagedSave-callback.patch b/0001-ch_driver-Add-domainManagedSave-callback.patch new file mode 100644 index 0000000..09eb466 --- /dev/null +++ b/0001-ch_driver-Add-domainManagedSave-callback.patch @@ -0,0 +1,160 @@ +From d07cdc0f48c579de2b229e011d00ee0d517da965 Mon Sep 17 00:00:00 2001 +From: Purna Pavan Chandra Aekkaladevi +Date: Mon, 11 Mar 2024 09:44:00 +0000 +Subject: [PATCH] ch_driver: Add domainManagedSave callback + +Create libvirt managed saveDir and pass it to CH to save the VM + +Signed-off-by: Purna Pavan Chandra Aekkaladevi +Reviewed-by: Michal Privoznik +--- + src/ch/ch_conf.c | 6 ++++++ + src/ch/ch_conf.h | 1 + + src/ch/ch_driver.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ + src/ch/ch_monitor.c | 7 +++++++ + 4 files changed, 65 insertions(+) + +diff --git a/src/ch/ch_conf.c b/src/ch/ch_conf.c +index a7b2285886..cab97639c4 100644 +--- a/src/ch/ch_conf.c ++++ b/src/ch/ch_conf.c +@@ -148,10 +148,12 @@ virCHDriverConfigNew(bool privileged) + if (privileged) { + cfg->logDir = g_strdup_printf("%s/log/libvirt/ch", LOCALSTATEDIR); + cfg->stateDir = g_strdup_printf("%s/libvirt/ch", RUNSTATEDIR); ++ cfg->saveDir = g_strdup_printf("%s/lib/libvirt/ch/save", LOCALSTATEDIR); + + } else { + g_autofree char *rundir = NULL; + g_autofree char *cachedir = NULL; ++ g_autofree char *configbasedir = NULL; + + cachedir = virGetUserCacheDirectory(); + +@@ -159,6 +161,9 @@ virCHDriverConfigNew(bool privileged) + + rundir = virGetUserRuntimeDirectory(); + cfg->stateDir = g_strdup_printf("%s/ch/run", rundir); ++ ++ configbasedir = virGetUserConfigDirectory(); ++ cfg->saveDir = g_strdup_printf("%s/ch/save", configbasedir); + } + + return cfg; +@@ -175,6 +180,7 @@ virCHDriverConfigDispose(void *obj) + { + virCHDriverConfig *cfg = obj; + ++ g_free(cfg->saveDir); + g_free(cfg->stateDir); + g_free(cfg->logDir); + } +diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h +index 4b4c3345b6..a77cad7a2a 100644 +--- a/src/ch/ch_conf.h ++++ b/src/ch/ch_conf.h +@@ -37,6 +37,7 @@ struct _virCHDriverConfig { + + char *stateDir; + char *logDir; ++ char *saveDir; + + int cgroupControllers; + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index 8a00d035c9..f8a987a87f 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -178,6 +178,14 @@ static char *chConnectGetCapabilities(virConnectPtr conn) + return xml; + } + ++static char * ++chDomainManagedSavePath(virCHDriver *driver, virDomainObj *vm) ++{ ++ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver); ++ return g_strdup_printf("%s/%s.save", cfg->saveDir, vm->def->name); ++} ++ ++ + /** + * chDomainCreateXML: + * @conn: pointer to connection +@@ -763,6 +771,48 @@ chDomainSave(virDomainPtr dom, const char *to) + return chDomainSaveFlags(dom, to, NULL, 0); + } + ++static int ++chDomainManagedSave(virDomainPtr dom, unsigned int flags) ++{ ++ virCHDriver *driver = dom->conn->privateData; ++ virDomainObj *vm = NULL; ++ g_autofree char *to = NULL; ++ int ret = -1; ++ ++ virCheckFlags(0, -1); ++ ++ if (!(vm = virCHDomainObjFromDomain(dom))) ++ goto cleanup; ++ ++ if (virDomainManagedSaveEnsureACL(dom->conn, vm->def) < 0) ++ goto cleanup; ++ ++ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) ++ goto cleanup; ++ ++ if (virDomainObjCheckActive(vm) < 0) ++ goto endjob; ++ ++ if (!vm->persistent) { ++ virReportError(VIR_ERR_OPERATION_INVALID, "%s", ++ _("cannot do managed save for transient domain")); ++ goto endjob; ++ } ++ ++ to = chDomainManagedSavePath(driver, vm); ++ if (chDoDomainSave(driver, vm, to, true) < 0) ++ goto endjob; ++ ++ ret = 0; ++ ++ endjob: ++ virDomainObjEndJob(vm); ++ ++ cleanup: ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ + static virDomainPtr chDomainLookupByID(virConnectPtr conn, + int id) + { +@@ -1886,6 +1936,7 @@ static virHypervisorDriver chHypervisorDriver = { + .domainGetNumaParameters = chDomainGetNumaParameters, /* 8.1.0 */ + .domainSave = chDomainSave, /* 10.2.0 */ + .domainSaveFlags = chDomainSaveFlags, /* 10.2.0 */ ++ .domainManagedSave = chDomainManagedSave, /* 10.2.0 */ + }; + + static virConnectDriver chConnectDriver = { +diff --git a/src/ch/ch_monitor.c b/src/ch/ch_monitor.c +index 939fa13667..7b6b77de1c 100644 +--- a/src/ch/ch_monitor.c ++++ b/src/ch/ch_monitor.c +@@ -557,6 +557,13 @@ virCHMonitorNew(virDomainObj *vm, virCHDriverConfig *cfg) + return NULL; + } + ++ if (g_mkdir_with_parents(cfg->saveDir, 0777) < 0) { ++ virReportSystemError(errno, ++ _("Cannot create save directory '%1$s'"), ++ cfg->saveDir); ++ return NULL; ++ } ++ + cmd = virCommandNew(vm->def->emulator); + virCommandSetUmask(cmd, 0x002); + socket_fd = chMonitorCreateSocket(mon->socketpath); +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch_driver-Add-domainSave-domainSaveFlags-callbacks.patch b/0001-ch_driver-Add-domainSave-domainSaveFlags-callbacks.patch new file mode 100644 index 0000000..9f5586b --- /dev/null +++ b/0001-ch_driver-Add-domainSave-domainSaveFlags-callbacks.patch @@ -0,0 +1,217 @@ +From c22fb87b05a1ae0acfe358d52baf8d66eb27b8e9 Mon Sep 17 00:00:00 2001 +From: Purna Pavan Chandra Aekkaladevi +Date: Mon, 11 Mar 2024 09:43:59 +0000 +Subject: [PATCH] ch_driver: Add domainSave, domainSaveFlags callbacks + +Implemented save callbacks. CH's vmm.snapshot API is called to save the +domain state. The path passed to these callbacks has to be of directory +as CH takes dir as input to snapshot and saves multiple files under it. + +Signed-off-by: Purna Pavan Chandra Aekkaladevi +Reviewed-by: Michal Privoznik +--- + src/ch/ch_conf.h | 11 ++++ + src/ch/ch_driver.c | 144 +++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 155 insertions(+) + +diff --git a/src/ch/ch_conf.h b/src/ch/ch_conf.h +index 579eca894e..4b4c3345b6 100644 +--- a/src/ch/ch_conf.h ++++ b/src/ch/ch_conf.h +@@ -81,6 +81,17 @@ struct _virCHDriver + ebtablesContext *ebtables; + }; + ++#define CH_SAVE_MAGIC "libvirt-xml\n \0 \r" ++#define CH_SAVE_XML "libvirt-save.xml" ++ ++typedef struct _CHSaveXMLHeader CHSaveXMLHeader; ++struct _CHSaveXMLHeader { ++ char magic[sizeof(CH_SAVE_MAGIC)-1]; ++ uint32_t xmlLen; ++ /* 20 bytes used, pad up to 64 bytes */ ++ uint32_t unused[11]; ++}; ++ + virCaps *virCHDriverCapsInit(void); + virCaps *virCHDriverGetCapabilities(virCHDriver *driver, + bool refresh); +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index ae550802f5..8a00d035c9 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -19,6 +19,7 @@ + */ + + #include ++#include + + #include "ch_capabilities.h" + #include "ch_conf.h" +@@ -34,6 +35,7 @@ + #include "virerror.h" + #include "virlog.h" + #include "virobject.h" ++#include "virfile.h" + #include "virtypedparam.h" + #include "virutil.h" + #include "viruuid.h" +@@ -621,6 +623,146 @@ chDomainDestroy(virDomainPtr dom) + return chDomainDestroyFlags(dom, 0); + } + ++/** ++ * chDoDomainSave: ++ * @driver: pointer to driver structure ++ * @vm: pointer to virtual machine structure. Must be locked before invocation. ++ * @to_dir: directory path (CH needs directory input) to save the domain ++ * @managed: whether the VM is managed or not ++ * ++ * Checks if the domain is running or paused, then suspends it and saves it ++ * using CH's vmm.snapshot API. CH creates multiple files for config, memory, ++ * device state into @to_dir. ++ * ++ * Returns 0 on success or -1 in case of error ++ */ ++static int ++chDoDomainSave(virCHDriver *driver, ++ virDomainObj *vm, ++ const char *to_dir, ++ bool managed) ++{ ++ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver); ++ virCHDomainObjPrivate *priv = vm->privateData; ++ CHSaveXMLHeader hdr; ++ g_autofree char *to = NULL; ++ g_autofree char *xml = NULL; ++ uint32_t xml_len; ++ VIR_AUTOCLOSE fd = -1; ++ int ret = -1; ++ ++ virDomainState domainState = virDomainObjGetState(vm, NULL); ++ if (domainState == VIR_DOMAIN_RUNNING) { ++ if (virCHMonitorSuspendVM(priv->monitor) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to suspend domain before saving")); ++ goto end; ++ } ++ virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_SAVE); ++ } else if (domainState != VIR_DOMAIN_PAUSED) { ++ virReportError(VIR_ERR_OPERATION_INVALID, "%s", ++ _("only can save running/paused domain")); ++ goto end; ++ } ++ ++ if (virDirCreate(to_dir, 0770, cfg->user, cfg->group, ++ VIR_DIR_CREATE_ALLOW_EXIST) < 0) { ++ virReportSystemError(errno, _("Failed to create SAVE dir %1$s"), to_dir); ++ goto end; ++ } ++ ++ to = g_strdup_printf("%s/%s", to_dir, CH_SAVE_XML); ++ if ((fd = virFileOpenAs(to, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR, ++ cfg->user, cfg->group, 0)) < 0) { ++ virReportSystemError(-fd, ++ _("Failed to create/open domain save xml file '%1$s'"), ++ to); ++ goto end; ++ } ++ ++ if ((xml = virDomainDefFormat(vm->def, driver->xmlopt, 0)) == NULL) ++ goto end; ++ xml_len = strlen(xml) + 1; ++ ++ memset(&hdr, 0, sizeof(hdr)); ++ memcpy(hdr.magic, CH_SAVE_MAGIC, sizeof(hdr.magic)); ++ hdr.xmlLen = xml_len; ++ ++ if (safewrite(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { ++ virReportSystemError(errno, "%s", _("Failed to write file header")); ++ goto end; ++ } ++ ++ if (safewrite(fd, xml, xml_len) != xml_len) { ++ virReportSystemError(errno, "%s", _("Failed to write xml definition")); ++ goto end; ++ } ++ ++ if (virCHMonitorSaveVM(priv->monitor, to_dir) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to save domain")); ++ goto end; ++ } ++ ++ if (virCHProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SAVED) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Failed to shutoff after domain save")); ++ goto end; ++ } ++ ++ vm->hasManagedSave = managed; ++ ret = 0; ++ ++ end: ++ return ret; ++} ++ ++static int ++chDomainSaveFlags(virDomainPtr dom, const char *to, const char *dxml, unsigned int flags) ++{ ++ virCHDriver *driver = dom->conn->privateData; ++ virDomainObj *vm = NULL; ++ int ret = -1; ++ ++ virCheckFlags(0, -1); ++ if (dxml) { ++ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", ++ _("xml modification unsupported")); ++ return -1; ++ } ++ ++ if (!(vm = virCHDomainObjFromDomain(dom))) ++ goto cleanup; ++ ++ if (virDomainSaveFlagsEnsureACL(dom->conn, vm->def) < 0) ++ goto cleanup; ++ ++ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) ++ goto cleanup; ++ ++ if (virDomainObjCheckActive(vm) < 0) ++ goto endjob; ++ ++ if (chDoDomainSave(driver, vm, to, false) < 0) ++ goto endjob; ++ ++ /* Remove if VM is not persistent */ ++ virCHDomainRemoveInactive(driver, vm); ++ ret = 0; ++ ++ endjob: ++ virDomainObjEndJob(vm); ++ ++ cleanup: ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ ++static int ++chDomainSave(virDomainPtr dom, const char *to) ++{ ++ return chDomainSaveFlags(dom, to, NULL, 0); ++} ++ + static virDomainPtr chDomainLookupByID(virConnectPtr conn, + int id) + { +@@ -1742,6 +1884,8 @@ static virHypervisorDriver chHypervisorDriver = { + .nodeGetCPUMap = chNodeGetCPUMap, /* 8.0.0 */ + .domainSetNumaParameters = chDomainSetNumaParameters, /* 8.1.0 */ + .domainGetNumaParameters = chDomainGetNumaParameters, /* 8.1.0 */ ++ .domainSave = chDomainSave, /* 10.2.0 */ ++ .domainSaveFlags = chDomainSaveFlags, /* 10.2.0 */ + }; + + static virConnectDriver chConnectDriver = { +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch_driver-Implement-domain-restore-callbacks.patch b/0001-ch_driver-Implement-domain-restore-callbacks.patch new file mode 100644 index 0000000..09a9a17 --- /dev/null +++ b/0001-ch_driver-Implement-domain-restore-callbacks.patch @@ -0,0 +1,225 @@ +From 53ec0fd09db4f64707acc0f8b2095f60d4dbaad1 Mon Sep 17 00:00:00 2001 +From: Purna Pavan Chandra Aekkaladevi +Date: Mon, 11 Mar 2024 09:44:03 +0000 +Subject: [PATCH] ch_driver: Implement domain restore callbacks + +Following callbacks have been implemented +* domainRestore +* domainRestoreFlags +The path parameter to these callbacks has to be of the directory where +libvirt has performed save. Additionally, call restore in `domainCreate` +if the domain has managedsave. + +Signed-off-by: Purna Pavan Chandra Aekkaladevi +Reviewed-by: Michal Privoznik +--- + src/ch/ch_driver.c | 96 ++++++++++++++++++++++++++++++++++++++++++++- + src/ch/ch_process.c | 53 +++++++++++++++++++++++++ + src/ch/ch_process.h | 4 ++ + 3 files changed, 152 insertions(+), 1 deletion(-) + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index dace267d99..7b704702cf 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -252,6 +252,8 @@ chDomainCreateWithFlags(virDomainPtr dom, unsigned int flags) + { + virCHDriver *driver = dom->conn->privateData; + virDomainObj *vm; ++ virCHDomainObjPrivate *priv; ++ g_autofree char *managed_save_path = NULL; + int ret = -1; + + virCheckFlags(0, -1); +@@ -265,8 +267,34 @@ chDomainCreateWithFlags(virDomainPtr dom, unsigned int flags) + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + +- ret = virCHProcessStart(driver, vm, VIR_DOMAIN_RUNNING_BOOTED); ++ if (vm->hasManagedSave) { ++ priv = vm->privateData; ++ managed_save_path = chDomainManagedSavePath(driver, vm); ++ if (virCHProcessStartRestore(driver, vm, managed_save_path) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to restore domain from managed save")); ++ goto endjob; ++ } ++ if (virCHMonitorResumeVM(priv->monitor) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to resume domain after restore from managed save")); ++ goto endjob; ++ } ++ virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_RESTORED); ++ /* cleanup the save dir after restore */ ++ if (virFileDeleteTree(managed_save_path) < 0) { ++ virReportSystemError(errno, ++ _("Failed to remove managed save path '%1$s'"), ++ managed_save_path); ++ goto endjob; ++ } ++ vm->hasManagedSave = false; ++ ret = 0; ++ } else { ++ ret = virCHProcessStart(driver, vm, VIR_DOMAIN_RUNNING_BOOTED); ++ } + ++ endjob: + virDomainObjEndJob(vm); + + cleanup: +@@ -989,6 +1017,70 @@ chDomainHasManagedSaveImage(virDomainPtr dom, unsigned int flags) + return ret; + } + ++static int ++chDomainRestoreFlags(virConnectPtr conn, ++ const char *from, ++ const char *dxml, ++ unsigned int flags) ++{ ++ virCHDriver *driver = conn->privateData; ++ virDomainObj *vm = NULL; ++ virCHDomainObjPrivate *priv; ++ g_autoptr(virDomainDef) def = NULL; ++ int ret = -1; ++ ++ virCheckFlags(0, -1); ++ ++ if (dxml) { ++ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", ++ _("xml modification unsupported")); ++ return -1; ++ } ++ ++ if (chDomainSaveImageRead(driver, from, &def) < 0) ++ goto cleanup; ++ ++ if (virDomainRestoreFlagsEnsureACL(conn, def) < 0) ++ goto cleanup; ++ ++ if (!(vm = virDomainObjListAdd(driver->domains, &def, ++ driver->xmlopt, ++ VIR_DOMAIN_OBJ_LIST_ADD_LIVE | ++ VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, ++ NULL))) ++ goto cleanup; ++ ++ if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) ++ goto cleanup; ++ ++ if (virCHProcessStartRestore(driver, vm, from) < 0) ++ goto endjob; ++ ++ priv = vm->privateData; ++ if (virCHMonitorResumeVM(priv->monitor) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to resume domain after restore")); ++ goto endjob; ++ } ++ virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_RESTORED); ++ ret = 0; ++ ++ endjob: ++ virDomainObjEndJob(vm); ++ ++ cleanup: ++ if (vm && ret < 0) ++ virCHDomainRemoveInactive(driver, vm); ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ ++static int ++chDomainRestore(virConnectPtr conn, const char *from) ++{ ++ return chDomainRestoreFlags(conn, from, NULL, 0); ++} ++ + static virDomainPtr chDomainLookupByID(virConnectPtr conn, + int id) + { +@@ -2117,6 +2209,8 @@ static virHypervisorDriver chHypervisorDriver = { + .domainManagedSaveRemove = chDomainManagedSaveRemove, /* 10.2.0 */ + .domainManagedSaveGetXMLDesc = chDomainManagedSaveGetXMLDesc, /* 10.2.0 */ + .domainHasManagedSaveImage = chDomainHasManagedSaveImage, /* 10.2.0 */ ++ .domainRestore = chDomainRestore, /* 10.2.0 */ ++ .domainRestoreFlags = chDomainRestoreFlags, /* 10.2.0 */ + }; + + static virConnectDriver chConnectDriver = { +diff --git a/src/ch/ch_process.c b/src/ch/ch_process.c +index 250d66bc18..48190a1273 100644 +--- a/src/ch/ch_process.c ++++ b/src/ch/ch_process.c +@@ -853,3 +853,56 @@ virCHProcessStop(virCHDriver *driver G_GNUC_UNUSED, + + return 0; + } ++ ++/** ++ * virCHProcessStartRestore: ++ * @driver: pointer to driver structure ++ * @vm: pointer to virtual machine structure ++ * @from: directory path to restore the VM from ++ * ++ * Starts Cloud-Hypervisor process with the restored VM ++ * ++ * Returns 0 on success or -1 in case of error ++ */ ++int ++virCHProcessStartRestore(virCHDriver *driver, virDomainObj *vm, const char *from) ++{ ++ virCHDomainObjPrivate *priv = vm->privateData; ++ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(priv->driver); ++ ++ if (!priv->monitor) { ++ /* Get the first monitor connection if not already */ ++ if (!(priv->monitor = virCHProcessConnectMonitor(driver, vm))) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to create connection to CH socket")); ++ return -1; ++ } ++ } ++ ++ vm->pid = priv->monitor->pid; ++ vm->def->id = vm->pid; ++ priv->machineName = virCHDomainGetMachineName(vm); ++ ++ if (virCHMonitorRestoreVM(priv->monitor, from) < 0) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("failed to restore domain")); ++ return -1; ++ } ++ ++ /* Pass 0, NULL as restore only works without networking support */ ++ if (virDomainCgroupSetupCgroup("ch", vm, ++ 0, NULL, /* nnicindexes, nicindexes */ ++ &priv->cgroup, ++ cfg->cgroupControllers, ++ 0, /*maxThreadsPerProc*/ ++ priv->driver->privileged, ++ priv->machineName) < 0) ++ return -1; ++ ++ if (virCHProcessSetup(vm) < 0) ++ return -1; ++ ++ virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); ++ ++ return 0; ++} +diff --git a/src/ch/ch_process.h b/src/ch/ch_process.h +index 800e3f4e23..38bfce3b7f 100644 +--- a/src/ch/ch_process.h ++++ b/src/ch/ch_process.h +@@ -32,3 +32,7 @@ int virCHProcessStop(virCHDriver *driver, + + int virCHProcessSetupVcpu(virDomainObj *vm, + unsigned int vcpuid); ++ ++int virCHProcessStartRestore(virCHDriver *driver, ++ virDomainObj *vm, ++ const char *from); +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-ch_driver-Implement-more-save-callbacks.patch b/0001-ch_driver-Implement-more-save-callbacks.patch new file mode 100644 index 0000000..e0a054a --- /dev/null +++ b/0001-ch_driver-Implement-more-save-callbacks.patch @@ -0,0 +1,226 @@ +From 829340127db5be9c5c95c0d19d98ca400761becc Mon Sep 17 00:00:00 2001 +From: Purna Pavan Chandra Aekkaladevi +Date: Mon, 11 Mar 2024 09:44:01 +0000 +Subject: [PATCH] ch_driver: Implement more save callbacks + +Following callbacks have been implemented +* domainSaveImageGetXMLDesc +* domainManagedSaveRemove +* domainManagedSaveGetXMLDesc +* domainHasManagedSaveImage + +Signed-off-by: Purna Pavan Chandra Aekkaladevi +Reviewed-by: Michal Privoznik +--- + src/ch/ch_driver.c | 180 +++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 180 insertions(+) + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index f8a987a87f..dace267d99 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -771,6 +771,99 @@ chDomainSave(virDomainPtr dom, const char *to) + return chDomainSaveFlags(dom, to, NULL, 0); + } + ++static char * ++chDomainSaveXMLRead(int fd) ++{ ++ g_autofree char *xml = NULL; ++ CHSaveXMLHeader hdr; ++ ++ if (saferead(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { ++ virReportError(VIR_ERR_OPERATION_FAILED, "%s", ++ _("failed to read CHSaveXMLHeader header")); ++ return NULL; ++ } ++ ++ if (memcmp(hdr.magic, CH_SAVE_MAGIC, sizeof(hdr.magic))) { ++ virReportError(VIR_ERR_INVALID_ARG, "%s", ++ _("save image magic is incorrect")); ++ return NULL; ++ } ++ ++ if (hdr.xmlLen <= 0) { ++ virReportError(VIR_ERR_OPERATION_FAILED, ++ _("invalid XML length: %1$d"), hdr.xmlLen); ++ return NULL; ++ } ++ ++ xml = g_new0(char, hdr.xmlLen); ++ ++ if (saferead(fd, xml, hdr.xmlLen) != hdr.xmlLen) { ++ virReportError(VIR_ERR_OPERATION_FAILED, "%s", ++ _("failed to read XML")); ++ return NULL; ++ } ++ ++ return g_steal_pointer(&xml); ++} ++ ++static int chDomainSaveImageRead(virCHDriver *driver, ++ const char *path, ++ virDomainDef **ret_def) ++{ ++ g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver); ++ g_autoptr(virDomainDef) def = NULL; ++ g_autofree char *from = NULL; ++ g_autofree char *xml = NULL; ++ VIR_AUTOCLOSE fd = -1; ++ int ret = -1; ++ ++ from = g_strdup_printf("%s/%s", path, CH_SAVE_XML); ++ if ((fd = virFileOpenAs(from, O_RDONLY, 0, cfg->user, cfg->group, 0)) < 0) { ++ virReportSystemError(errno, ++ _("Failed to open domain save file '%1$s'"), ++ from); ++ goto end; ++ } ++ ++ if (!(xml = chDomainSaveXMLRead(fd))) ++ goto end; ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL, ++ VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ goto end; ++ ++ *ret_def = g_steal_pointer(&def); ++ ret = 0; ++ ++ end: ++ return ret; ++} ++ ++static char * ++chDomainSaveImageGetXMLDesc(virConnectPtr conn, ++ const char *path, ++ unsigned int flags) ++{ ++ virCHDriver *driver = conn->privateData; ++ g_autoptr(virDomainDef) def = NULL; ++ char *ret = NULL; ++ ++ virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); ++ ++ if (chDomainSaveImageRead(driver, path, &def) < 0) ++ goto cleanup; ++ ++ if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) ++ goto cleanup; ++ ++ ret = virDomainDefFormat(def, driver->xmlopt, ++ virDomainDefFormatConvertXMLFlags(flags)); ++ ++ cleanup: ++ return ret; ++} ++ + static int + chDomainManagedSave(virDomainPtr dom, unsigned int flags) + { +@@ -813,6 +906,89 @@ chDomainManagedSave(virDomainPtr dom, unsigned int flags) + return ret; + } + ++static int ++chDomainManagedSaveRemove(virDomainPtr dom, unsigned int flags) ++{ ++ virCHDriver *driver = dom->conn->privateData; ++ virDomainObj *vm; ++ int ret = -1; ++ g_autofree char *path = NULL; ++ ++ virCheckFlags(0, -1); ++ ++ if (!(vm = virCHDomainObjFromDomain(dom))) ++ return -1; ++ ++ if (virDomainManagedSaveRemoveEnsureACL(dom->conn, vm->def) < 0) ++ goto cleanup; ++ ++ path = chDomainManagedSavePath(driver, vm); ++ ++ if (virFileDeleteTree(path) < 0) { ++ virReportSystemError(errno, ++ _("Failed to remove managed save path '%1$s'"), ++ path); ++ goto cleanup; ++ } ++ ++ vm->hasManagedSave = false; ++ ret = 0; ++ ++ cleanup: ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ ++static char * ++chDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) ++{ ++ virCHDriver *driver = dom->conn->privateData; ++ virDomainObj *vm = NULL; ++ g_autoptr(virDomainDef) def = NULL; ++ char *ret = NULL; ++ g_autofree char *path = NULL; ++ ++ virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); ++ ++ if (!(vm = virCHDomainObjFromDomain(dom))) ++ goto cleanup; ++ ++ path = chDomainManagedSavePath(driver, vm); ++ if (chDomainSaveImageRead(driver, path, &def) < 0) ++ goto cleanup; ++ ++ if (virDomainManagedSaveGetXMLDescEnsureACL(dom->conn, def, flags) < 0) ++ goto cleanup; ++ ++ ret = virDomainDefFormat(def, driver->xmlopt, ++ virDomainDefFormatConvertXMLFlags(flags)); ++ ++ cleanup: ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ ++static int ++chDomainHasManagedSaveImage(virDomainPtr dom, unsigned int flags) ++{ ++ virDomainObj *vm = NULL; ++ int ret = -1; ++ ++ virCheckFlags(0, -1); ++ ++ if (!(vm = virCHDomainObjFromDomain(dom))) ++ return -1; ++ ++ if (virDomainHasManagedSaveImageEnsureACL(dom->conn, vm->def) < 0) ++ goto cleanup; ++ ++ ret = vm->hasManagedSave; ++ ++ cleanup: ++ virDomainObjEndAPI(&vm); ++ return ret; ++} ++ + static virDomainPtr chDomainLookupByID(virConnectPtr conn, + int id) + { +@@ -1936,7 +2112,11 @@ static virHypervisorDriver chHypervisorDriver = { + .domainGetNumaParameters = chDomainGetNumaParameters, /* 8.1.0 */ + .domainSave = chDomainSave, /* 10.2.0 */ + .domainSaveFlags = chDomainSaveFlags, /* 10.2.0 */ ++ .domainSaveImageGetXMLDesc = chDomainSaveImageGetXMLDesc, /* 10.2.0 */ + .domainManagedSave = chDomainManagedSave, /* 10.2.0 */ ++ .domainManagedSaveRemove = chDomainManagedSaveRemove, /* 10.2.0 */ ++ .domainManagedSaveGetXMLDesc = chDomainManagedSaveGetXMLDesc, /* 10.2.0 */ ++ .domainHasManagedSaveImage = chDomainHasManagedSaveImage, /* 10.2.0 */ + }; + + static virConnectDriver chConnectDriver = { +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-qemu-Decompose-qemuSaveImageOpen.patch b/0001-qemu-Decompose-qemuSaveImageOpen.patch new file mode 100644 index 0000000..36e7943 --- /dev/null +++ b/0001-qemu-Decompose-qemuSaveImageOpen.patch @@ -0,0 +1,435 @@ +From 517248e2394476a3105ff5866b0b718fc6583073 Mon Sep 17 00:00:00 2001 +From: Jim Fehlig via Devel +Date: Thu, 30 Jan 2025 19:29:03 -0700 +Subject: [PATCH] qemu: Decompose qemuSaveImageOpen + +Split the reading of libvirt's save image metadata from the opening +of the fd that will be passed to QEMU. This allows improved error +handling and provides more flexibility users of qemu_saveimage. + +Signed-off-by: Jim Fehlig +Reviewed-by: Michal Privoznik +--- + src/qemu/qemu_driver.c | 31 +++--- + src/qemu/qemu_saveimage.c | 207 +++++++++++++++++++++++--------------- + src/qemu/qemu_saveimage.h | 13 ++- + src/qemu/qemu_snapshot.c | 8 +- + 4 files changed, 153 insertions(+), 106 deletions(-) + +diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c +index 2e80ce7921..f326937585 100644 +--- a/src/qemu/qemu_driver.c ++++ b/src/qemu/qemu_driver.c +@@ -5775,7 +5775,10 @@ qemuDomainRestoreInternal(virConnectPtr conn, + if (flags & VIR_DOMAIN_SAVE_RESET_NVRAM) + reset_nvram = true; + +- fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, ++ if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) ++ goto cleanup; ++ ++ fd = qemuSaveImageOpen(driver, path, + (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, + &wrapperFd, false); + if (fd < 0) +@@ -5906,15 +5909,11 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path, + virQEMUDriver *driver = conn->privateData; + char *ret = NULL; + g_autoptr(virDomainDef) def = NULL; +- int fd = -1; + virQEMUSaveData *data = NULL; + + virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); + +- fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- false, NULL, false); +- +- if (fd < 0) ++ if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) + goto cleanup; + + if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) +@@ -5924,7 +5923,6 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path, + + cleanup: + virQEMUSaveDataFree(data); +- VIR_FORCE_CLOSE(fd); + return ret; + } + +@@ -5948,9 +5946,10 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, + else if (flags & VIR_DOMAIN_SAVE_PAUSED) + state = 0; + +- fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- false, NULL, true); ++ if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) ++ goto cleanup; + ++ fd = qemuSaveImageOpen(driver, path, 0, NULL, false); + if (fd < 0) + goto cleanup; + +@@ -6007,7 +6006,6 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + g_autofree char *path = NULL; + char *ret = NULL; + g_autoptr(virDomainDef) def = NULL; +- int fd = -1; + virQEMUSaveData *data = NULL; + qemuDomainObjPrivate *priv; + +@@ -6029,15 +6027,13 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + goto cleanup; + } + +- if ((fd = qemuSaveImageOpen(driver, priv->qemuCaps, path, &def, &data, +- false, NULL, false)) < 0) ++ if (qemuSaveImageGetMetadata(driver, priv->qemuCaps, path, &def, &data) < 0) + goto cleanup; + + ret = qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags); + + cleanup: + virQEMUSaveDataFree(data); +- VIR_FORCE_CLOSE(fd); + virDomainObjEndAPI(&vm); + return ret; + } +@@ -6093,9 +6089,8 @@ qemuDomainObjRestore(virConnectPtr conn, + virQEMUSaveData *data = NULL; + virFileWrapperFd *wrapperFd = NULL; + +- fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- bypass_cache, &wrapperFd, false); +- if (fd < 0) { ++ ret = qemuSaveImageGetMetadata(driver, NULL, path, &def, &data); ++ if (ret < 0) { + if (qemuSaveImageIsCorrupt(driver, path)) { + if (unlink(path) < 0) { + virReportSystemError(errno, +@@ -6110,6 +6105,10 @@ qemuDomainObjRestore(virConnectPtr conn, + goto cleanup; + } + ++ fd = qemuSaveImageOpen(driver, path, bypass_cache, &wrapperFd, false); ++ if (fd < 0) ++ goto cleanup; ++ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + int hookret; + +diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c +index 385ac8a649..8315171b78 100644 +--- a/src/qemu/qemu_saveimage.c ++++ b/src/qemu/qemu_saveimage.c +@@ -249,6 +249,84 @@ qemuSaveImageGetCompressionCommand(virQEMUSaveFormat format) + } + + ++static int ++qemuSaveImageReadHeader(int fd, virQEMUSaveData **ret_data) ++{ ++ g_autoptr(virQEMUSaveData) data = NULL; ++ virQEMUSaveHeader *header; ++ size_t xml_len; ++ size_t cookie_len; ++ ++ data = g_new0(virQEMUSaveData, 1); ++ header = &data->header; ++ if (saferead(fd, header, sizeof(*header)) != sizeof(*header)) { ++ virReportError(VIR_ERR_OPERATION_FAILED, ++ "%s", _("failed to read qemu header")); ++ return -1; ++ } ++ ++ if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) != 0) { ++ if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)) == 0) { ++ virReportError(VIR_ERR_OPERATION_FAILED, "%s", ++ _("save image is incomplete")); ++ return -1; ++ } ++ ++ virReportError(VIR_ERR_OPERATION_FAILED, "%s", ++ _("image magic is incorrect")); ++ return -1; ++ } ++ ++ if (header->version > QEMU_SAVE_VERSION) { ++ /* convert endianness and try again */ ++ qemuSaveImageBswapHeader(header); ++ } ++ ++ if (header->version > QEMU_SAVE_VERSION) { ++ virReportError(VIR_ERR_OPERATION_FAILED, ++ _("image version is not supported (%1$d > %2$d)"), ++ header->version, QEMU_SAVE_VERSION); ++ return -1; ++ } ++ ++ if (header->data_len <= 0) { ++ virReportError(VIR_ERR_OPERATION_FAILED, ++ _("invalid header data length: %1$d"), header->data_len); ++ return -1; ++ } ++ ++ if (header->cookieOffset) ++ xml_len = header->cookieOffset; ++ else ++ xml_len = header->data_len; ++ ++ cookie_len = header->data_len - xml_len; ++ ++ data->xml = g_new0(char, xml_len); ++ ++ if (saferead(fd, data->xml, xml_len) != xml_len) { ++ virReportError(VIR_ERR_OPERATION_FAILED, ++ "%s", _("failed to read domain XML")); ++ return -1; ++ } ++ ++ if (cookie_len > 0) { ++ data->cookie = g_new0(char, cookie_len); ++ ++ if (saferead(fd, data->cookie, cookie_len) != cookie_len) { ++ virReportError(VIR_ERR_OPERATION_FAILED, "%s", ++ _("failed to read cookie")); ++ return -1; ++ } ++ } ++ ++ if (ret_data) ++ *ret_data = g_steal_pointer(&data); ++ ++ return 0; ++} ++ ++ + /** + * qemuSaveImageDecompressionStart: + * @data: data from memory state file +@@ -520,6 +598,7 @@ qemuSaveImageGetCompressionProgram(const char *imageFormat, + return -1; + } + ++ + /** + * qemuSaveImageIsCorrupt: + * @driver: qemu driver data +@@ -551,26 +630,61 @@ qemuSaveImageIsCorrupt(virQEMUDriver *driver, const char *path) + + + /** +- * qemuSaveImageOpen: ++ * qemuSaveImageGetMetadata: + * @driver: qemu driver data + * @qemuCaps: pointer to qemuCaps if the domain is running or NULL + * @path: path of the save image + * @ret_def: returns domain definition created from the XML stored in the image + * @ret_data: returns structure filled with data from the image header ++ * ++ * Open the save image file, read libvirt's save image metadata, and populate ++ * the @ret_def and @ret_data structures. Returns 0 on success and -1 on failure. ++ */ ++int ++qemuSaveImageGetMetadata(virQEMUDriver *driver, ++ virQEMUCaps *qemuCaps, ++ const char *path, ++ virDomainDef **ret_def, ++ virQEMUSaveData **ret_data) ++{ ++ g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); ++ VIR_AUTOCLOSE fd = -1; ++ virQEMUSaveData *data; ++ g_autoptr(virDomainDef) def = NULL; ++ int rc; ++ ++ if ((fd = qemuDomainOpenFile(cfg, NULL, path, O_RDONLY, NULL)) < 0) ++ return -1; ++ ++ if ((rc = qemuSaveImageReadHeader(fd, ret_data)) < 0) ++ return rc; ++ ++ data = *ret_data; ++ /* Create a domain from this XML */ ++ if (!(def = virDomainDefParseString(data->xml, driver->xmlopt, qemuCaps, ++ VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ return -1; ++ ++ *ret_def = g_steal_pointer(&def); ++ ++ return 0; ++} ++ ++ ++/** ++ * qemuSaveImageOpen: ++ * @driver: qemu driver data ++ * @path: path of the save image + * @bypass_cache: bypass cache when opening the file + * @wrapperFd: returns the file wrapper structure + * @open_write: open the file for writing (for updates) + * +- * Returns the opened fd of the save image file and fills the appropriate fields +- * on success. On error returns -1 on most failures, -3 if a corrupt image was +- * detected. ++ * Returns the opened fd of the save image file on success, -1 on failure. + */ + int + qemuSaveImageOpen(virQEMUDriver *driver, +- virQEMUCaps *qemuCaps, + const char *path, +- virDomainDef **ret_def, +- virQEMUSaveData **ret_data, + bool bypass_cache, + virFileWrapperFd **wrapperFd, + bool open_write) +@@ -578,12 +692,7 @@ qemuSaveImageOpen(virQEMUDriver *driver, + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + VIR_AUTOCLOSE fd = -1; + int ret = -1; +- g_autoptr(virQEMUSaveData) data = NULL; +- virQEMUSaveHeader *header; +- g_autoptr(virDomainDef) def = NULL; + int oflags = open_write ? O_RDWR : O_RDONLY; +- size_t xml_len; +- size_t cookie_len; + + if (bypass_cache) { + int directFlag = virFileDirectFdFlag(); +@@ -603,79 +712,11 @@ qemuSaveImageOpen(virQEMUDriver *driver, + VIR_FILE_WRAPPER_BYPASS_CACHE))) + return -1; + +- data = g_new0(virQEMUSaveData, 1); +- +- header = &data->header; +- if (saferead(fd, header, sizeof(*header)) != sizeof(*header)) { +- virReportError(VIR_ERR_OPERATION_FAILED, +- "%s", _("failed to read qemu header")); +- return -1; +- } +- +- if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) != 0) { +- if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)) == 0) { +- virReportError(VIR_ERR_OPERATION_FAILED, "%s", +- _("save image is incomplete")); +- return -1; +- } +- +- virReportError(VIR_ERR_OPERATION_FAILED, "%s", +- _("image magic is incorrect")); +- return -1; +- } +- +- if (header->version > QEMU_SAVE_VERSION) { +- /* convert endianness and try again */ +- qemuSaveImageBswapHeader(header); +- } +- +- if (header->version > QEMU_SAVE_VERSION) { +- virReportError(VIR_ERR_OPERATION_FAILED, +- _("image version is not supported (%1$d > %2$d)"), +- header->version, QEMU_SAVE_VERSION); +- return -1; +- } +- +- if (header->data_len <= 0) { +- virReportError(VIR_ERR_OPERATION_FAILED, +- _("invalid header data length: %1$d"), header->data_len); +- return -1; +- } +- +- if (header->cookieOffset) +- xml_len = header->cookieOffset; +- else +- xml_len = header->data_len; +- +- cookie_len = header->data_len - xml_len; +- +- data->xml = g_new0(char, xml_len); +- +- if (saferead(fd, data->xml, xml_len) != xml_len) { +- virReportError(VIR_ERR_OPERATION_FAILED, +- "%s", _("failed to read domain XML")); +- return -1; +- } +- +- if (cookie_len > 0) { +- data->cookie = g_new0(char, cookie_len); +- +- if (saferead(fd, data->cookie, cookie_len) != cookie_len) { +- virReportError(VIR_ERR_OPERATION_FAILED, "%s", +- _("failed to read cookie")); +- return -1; +- } +- } +- +- /* Create a domain from this XML */ +- if (!(def = virDomainDefParseString(data->xml, driver->xmlopt, qemuCaps, +- VIR_DOMAIN_DEF_PARSE_INACTIVE | +- VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ /* Read the header to position the file pointer for QEMU. Unfortunately we ++ * can't use lseek with virFileWrapperFD. */ ++ if (qemuSaveImageReadHeader(fd, NULL) < 0) + return -1; + +- *ret_def = g_steal_pointer(&def); +- *ret_data = g_steal_pointer(&data); +- + ret = fd; + fd = -1; + +diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h +index dc49f8463f..53ae222467 100644 +--- a/src/qemu/qemu_saveimage.h ++++ b/src/qemu/qemu_saveimage.h +@@ -74,16 +74,21 @@ qemuSaveImageIsCorrupt(virQEMUDriver *driver, + const char *path) + ATTRIBUTE_NONNULL(2); + ++int ++qemuSaveImageGetMetadata(virQEMUDriver *driver, ++ virQEMUCaps *qemuCaps, ++ const char *path, ++ virDomainDef **ret_def, ++ virQEMUSaveData **ret_data) ++ ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5); ++ + int + qemuSaveImageOpen(virQEMUDriver *driver, +- virQEMUCaps *qemuCaps, + const char *path, +- virDomainDef **ret_def, +- virQEMUSaveData **ret_data, + bool bypass_cache, + virFileWrapperFd **wrapperFd, + bool open_write) +- ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); ++ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4); + + int + qemuSaveImageGetCompressionProgram(const char *imageFormat, +diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c +index b9c3983472..7ce018b026 100644 +--- a/src/qemu/qemu_snapshot.c ++++ b/src/qemu/qemu_snapshot.c +@@ -2377,10 +2377,12 @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, + g_autoptr(virDomainDef) savedef = NULL; + + memdata->path = snapdef->memorysnapshotfile; +- memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path, +- &savedef, &memdata->data, +- false, NULL, false); ++ if (qemuSaveImageGetMetadata(driver, NULL, memdata->path, &savedef, ++ &memdata->data) < 0) ++ return -1; + ++ memdata->fd = qemuSaveImageOpen(driver, memdata->path, ++ false, NULL, false); + if (memdata->fd < 0) + return -1; + +-- +2.50.1.319.g90c0775e97 + diff --git a/0001-qemu-Move-unlinking-corrupt-save-image-file-to-calle.patch b/0001-qemu-Move-unlinking-corrupt-save-image-file-to-calle.patch new file mode 100644 index 0000000..e7eccdf --- /dev/null +++ b/0001-qemu-Move-unlinking-corrupt-save-image-file-to-calle.patch @@ -0,0 +1,224 @@ +From 50d65b94ef3f3588973557a1f1ad8ab3b66bc611 Mon Sep 17 00:00:00 2001 +From: Jim Fehlig via Devel +Date: Thu, 30 Jan 2025 19:29:02 -0700 +Subject: [PATCH] qemu: Move unlinking corrupt save image file to caller + +qemuDomainObjRestore is the only caller of qemuSaveImageOpen that +requests an unlink of a corrupted save image. Provide a function to +check for a corrupt image and move unlinking it to qemuDomainObjRestore. + +Signed-off-by: Jim Fehlig +Reviewed-by: Michal Privoznik +--- + src/qemu/qemu_driver.c | 23 ++++++++++----- + src/qemu/qemu_saveimage.c | 59 +++++++++++++++++++++------------------ + src/qemu/qemu_saveimage.h | 8 ++++-- + src/qemu/qemu_snapshot.c | 3 +- + 4 files changed, 55 insertions(+), 38 deletions(-) + +diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c +index da714f1975..2e80ce7921 100644 +--- a/src/qemu/qemu_driver.c ++++ b/src/qemu/qemu_driver.c +@@ -5777,7 +5777,7 @@ qemuDomainRestoreInternal(virConnectPtr conn, + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, + (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, +- &wrapperFd, false, false); ++ &wrapperFd, false); + if (fd < 0) + goto cleanup; + +@@ -5912,7 +5912,7 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path, + virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- false, NULL, false, false); ++ false, NULL, false); + + if (fd < 0) + goto cleanup; +@@ -5949,7 +5949,7 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, + state = 0; + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- false, NULL, true, false); ++ false, NULL, true); + + if (fd < 0) + goto cleanup; +@@ -6030,7 +6030,7 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + } + + if ((fd = qemuSaveImageOpen(driver, priv->qemuCaps, path, &def, &data, +- false, NULL, false, false)) < 0) ++ false, NULL, false)) < 0) + goto cleanup; + + ret = qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags); +@@ -6094,10 +6094,19 @@ qemuDomainObjRestore(virConnectPtr conn, + virFileWrapperFd *wrapperFd = NULL; + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- bypass_cache, &wrapperFd, false, true); ++ bypass_cache, &wrapperFd, false); + if (fd < 0) { +- if (fd == -3) +- ret = 1; ++ if (qemuSaveImageIsCorrupt(driver, path)) { ++ if (unlink(path) < 0) { ++ virReportSystemError(errno, ++ _("cannot remove corrupt file: %1$s"), ++ path); ++ ret = -1; ++ } else { ++ virResetLastError(); ++ ret = 1; ++ } ++ } + goto cleanup; + } + +diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c +index 69617e07eb..385ac8a649 100644 +--- a/src/qemu/qemu_saveimage.c ++++ b/src/qemu/qemu_saveimage.c +@@ -520,6 +520,35 @@ qemuSaveImageGetCompressionProgram(const char *imageFormat, + return -1; + } + ++/** ++ * qemuSaveImageIsCorrupt: ++ * @driver: qemu driver data ++ * @path: path of the save image ++ * ++ * Returns true if the save image file identified by @path does not exist or ++ * has a corrupt header. Returns false otherwise. ++ */ ++ ++bool ++qemuSaveImageIsCorrupt(virQEMUDriver *driver, const char *path) ++{ ++ g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); ++ VIR_AUTOCLOSE fd = -1; ++ virQEMUSaveHeader header; ++ ++ if ((fd = qemuDomainOpenFile(cfg, NULL, path, O_RDONLY, NULL)) < 0) ++ return true; ++ ++ if (saferead(fd, &header, sizeof(header)) != sizeof(header)) ++ return true; ++ ++ if (memcmp(header.magic, QEMU_SAVE_MAGIC, sizeof(header.magic)) != 0 || ++ memcmp(header.magic, QEMU_SAVE_PARTIAL, sizeof(header.magic)) == 0) ++ return true; ++ ++ return false; ++} ++ + + /** + * qemuSaveImageOpen: +@@ -531,11 +560,10 @@ qemuSaveImageGetCompressionProgram(const char *imageFormat, + * @bypass_cache: bypass cache when opening the file + * @wrapperFd: returns the file wrapper structure + * @open_write: open the file for writing (for updates) +- * @unlink_corrupt: remove the image file if it is corrupted + * + * Returns the opened fd of the save image file and fills the appropriate fields +- * on success. On error returns -1 on most failures, -3 if corrupt image was +- * unlinked (no error raised). ++ * on success. On error returns -1 on most failures, -3 if a corrupt image was ++ * detected. + */ + int + qemuSaveImageOpen(virQEMUDriver *driver, +@@ -545,8 +573,7 @@ qemuSaveImageOpen(virQEMUDriver *driver, + virQEMUSaveData **ret_data, + bool bypass_cache, + virFileWrapperFd **wrapperFd, +- bool open_write, +- bool unlink_corrupt) ++ bool open_write) + { + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + VIR_AUTOCLOSE fd = -1; +@@ -580,17 +607,6 @@ qemuSaveImageOpen(virQEMUDriver *driver, + + header = &data->header; + if (saferead(fd, header, sizeof(*header)) != sizeof(*header)) { +- if (unlink_corrupt) { +- if (unlink(path) < 0) { +- virReportSystemError(errno, +- _("cannot remove corrupt file: %1$s"), +- path); +- return -1; +- } else { +- return -3; +- } +- } +- + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("failed to read qemu header")); + return -1; +@@ -598,17 +614,6 @@ qemuSaveImageOpen(virQEMUDriver *driver, + + if (memcmp(header->magic, QEMU_SAVE_MAGIC, sizeof(header->magic)) != 0) { + if (memcmp(header->magic, QEMU_SAVE_PARTIAL, sizeof(header->magic)) == 0) { +- if (unlink_corrupt) { +- if (unlink(path) < 0) { +- virReportSystemError(errno, +- _("cannot remove corrupt file: %1$s"), +- path); +- return -1; +- } else { +- return -3; +- } +- } +- + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("save image is incomplete")); + return -1; +diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h +index 0e58dd14b6..dc49f8463f 100644 +--- a/src/qemu/qemu_saveimage.h ++++ b/src/qemu/qemu_saveimage.h +@@ -69,6 +69,11 @@ qemuSaveImageStartVM(virConnectPtr conn, + virDomainAsyncJob asyncJob) + ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6); + ++bool ++qemuSaveImageIsCorrupt(virQEMUDriver *driver, ++ const char *path) ++ ATTRIBUTE_NONNULL(2); ++ + int + qemuSaveImageOpen(virQEMUDriver *driver, + virQEMUCaps *qemuCaps, +@@ -77,8 +82,7 @@ qemuSaveImageOpen(virQEMUDriver *driver, + virQEMUSaveData **ret_data, + bool bypass_cache, + virFileWrapperFd **wrapperFd, +- bool open_write, +- bool unlink_corrupt) ++ bool open_write) + ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); + + int +diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c +index 5273348aeb..b9c3983472 100644 +--- a/src/qemu/qemu_snapshot.c ++++ b/src/qemu/qemu_snapshot.c +@@ -2379,8 +2379,7 @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, + memdata->path = snapdef->memorysnapshotfile; + memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path, + &savedef, &memdata->data, +- false, NULL, +- false, false); ++ false, NULL, false); + + if (memdata->fd < 0) + return -1; +-- +2.50.1.319.g90c0775e97 + diff --git a/0002-libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0002-libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..24b1745 --- /dev/null +++ b/0002-libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,71 @@ +From a1f48bca077e2f3377f29d746efd4310b8a2910f Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:43:57 +0100 +Subject: [PATCH 2/6] libxl: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/libxl/libxl_driver.c | 20 +++++++++++++++----- + 1 file changed, 15 insertions(+), 5 deletions(-) + +diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c +index 107477250a..9dcf2dcf2e 100644 +--- a/src/libxl/libxl_driver.c ++++ b/src/libxl/libxl_driver.c +@@ -1027,13 +1027,18 @@ libxlDomainCreateXML(virConnectPtr conn, const char *xml, + if (flags & VIR_DOMAIN_START_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + +- if (!(def = virDomainDefParseString(xml, driver->xmlopt, +- NULL, parse_flags))) ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) + goto cleanup; + + if (virDomainCreateXMLEnsureACL(conn, def) < 0) + goto cleanup; + ++ g_clear_pointer(&def, virDomainDefFree); ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, ++ NULL, parse_flags))) ++ goto cleanup; ++ + if (!(vm = virDomainObjListAdd(driver->domains, &def, + driver->xmlopt, + VIR_DOMAIN_OBJ_LIST_ADD_LIVE | +@@ -2813,6 +2818,14 @@ libxlDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flag + if (flags & VIR_DOMAIN_DEFINE_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ goto cleanup; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) ++ goto cleanup; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ + if (!(def = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags))) + goto cleanup; +@@ -2820,9 +2833,6 @@ libxlDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flag + if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, &def, + driver->xmlopt, + 0, +-- +2.50.1.319.g90c0775e97 + diff --git a/0003-lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0003-lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..ea3afba --- /dev/null +++ b/0003-lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,73 @@ +From a6dcfee896f67bb8bdfdbb0b406ac7649fbb4c0f Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:49:01 +0100 +Subject: [PATCH 3/6] lxc: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/lxc/lxc_driver.c | 22 +++++++++++++++++----- + 1 file changed, 17 insertions(+), 5 deletions(-) + +diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c +index 80cf07d2e5..c0a93c0444 100644 +--- a/src/lxc/lxc_driver.c ++++ b/src/lxc/lxc_driver.c +@@ -409,6 +409,15 @@ lxcDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (!(caps = virLXCDriverGetCapabilities(driver, false))) + goto cleanup; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ goto cleanup; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) ++ goto cleanup; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ + if (!(def = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags))) + goto cleanup; +@@ -416,9 +425,6 @@ lxcDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) +- goto cleanup; +- + if (virSecurityManagerVerify(driver->securityManager, def) < 0) + goto cleanup; + +@@ -1066,13 +1072,19 @@ lxcDomainCreateXMLWithFiles(virConnectPtr conn, + if (!(caps = virLXCDriverGetCapabilities(driver, false))) + goto cleanup; + +- if (!(def = virDomainDefParseString(xml, driver->xmlopt, +- NULL, parse_flags))) ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) + goto cleanup; + + if (virDomainCreateXMLWithFilesEnsureACL(conn, def) < 0) + goto cleanup; + ++ g_clear_pointer(&def, virDomainDefFree); ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, ++ NULL, parse_flags))) ++ goto cleanup; ++ + if (virSecurityManagerVerify(driver->securityManager, def) < 0) + goto cleanup; + +-- +2.50.1.319.g90c0775e97 + diff --git a/0004-vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0004-vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..92ae236 --- /dev/null +++ b/0004-vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,64 @@ +From 7285c10a7e70c430f85af7a2b3954892ab3c6d6b Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 16:03:26 +0100 +Subject: [PATCH 4/6] vz: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/vz/vz_driver.c | 18 ++++++++++++------ + 1 file changed, 12 insertions(+), 6 deletions(-) + +diff --git a/src/vz/vz_driver.c b/src/vz/vz_driver.c +index 571735f940..2d8878fe7f 100644 +--- a/src/vz/vz_driver.c ++++ b/src/vz/vz_driver.c +@@ -789,6 +789,15 @@ vzDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (flags & VIR_DOMAIN_DEFINE_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) ++ return NULL; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ + if ((def = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; +@@ -796,9 +805,6 @@ vzDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) +- goto cleanup; +- + dom = virDomainObjListFindByUUID(driver->domains, def->uuid); + if (dom == NULL) { + virResetLastError(); +@@ -2966,9 +2972,9 @@ vzDomainMigratePrepare3Params(virConnectPtr conn, + | VZ_MIGRATION_COOKIE_DOMAIN_NAME) < 0) + return -1; + +- if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, +- NULL, +- VIR_DOMAIN_DEF_PARSE_INACTIVE))) ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(dom_xml, driver->xmlopt, ++ VIR_DOMAIN_DEF_PARSE_INACTIVE))) + return -1; + + if (dname) { +-- +2.50.1.319.g90c0775e97 + diff --git a/0005-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0005-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..d55a0a8 --- /dev/null +++ b/0005-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,178 @@ +From eb4322dfe8fff544d6dac01b2748c20f78f00d69 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 16:23:30 +0100 +Subject: [PATCH 5/6] ch: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +This is one of the more complex ones since there is also a function that +reads relevant metadata from a save image XML. In order not to extract +the parsing out of the function (and make the function basically trivial +and all callers more complex) add a callback to the function which will +be used to check the ACLs. And since this function is called in APIs +that perform ACL checks both with and without flags, add two of them for +good measure. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/ch/ch_driver.c | 76 ++++++++++++++++++++++++++++++++-------------- + 1 file changed, 53 insertions(+), 23 deletions(-) + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index 8ec90e1192..662857f88e 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -216,14 +216,19 @@ chDomainCreateXML(virConnectPtr conn, + if (flags & VIR_DOMAIN_START_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(vmdef = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainCreateXMLEnsureACL(conn, vmdef) < 0) ++ return NULL; ++ ++ g_clear_pointer(&vmdef, virDomainDefFree); + + if ((vmdef = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; + +- if (virDomainCreateXMLEnsureACL(conn, vmdef) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, + &vmdef, + driver->xmlopt, +@@ -347,6 +352,15 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (flags & VIR_DOMAIN_START_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(vmdef = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, vmdef) < 0) ++ return NULL; ++ ++ g_clear_pointer(&vmdef, virDomainDefFree); ++ + if ((vmdef = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags)) == NULL) + goto cleanup; +@@ -354,9 +368,6 @@ chDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) + if (virXMLCheckIllegalChars("name", vmdef->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, vmdef) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, &vmdef, + driver->xmlopt, + 0, &oldDef))) +@@ -920,16 +931,24 @@ chDomainSaveXMLRead(int fd) + return g_steal_pointer(&xml); + } + +-static int chDomainSaveImageRead(virCHDriver *driver, ++static int chDomainSaveImageRead(virConnectPtr conn, + const char *path, +- virDomainDef **ret_def) ++ virDomainDef **ret_def, ++ unsigned int flags, ++ int (*ensureACL)(virConnectPtr, virDomainDef *), ++ int (*ensureACLWithFlags)(virConnectPtr, ++ virDomainDef *, ++ unsigned int)) + { ++ virCHDriver *driver = conn->privateData; + g_autoptr(virCHDriverConfig) cfg = virCHDriverGetConfig(driver); + g_autoptr(virDomainDef) def = NULL; + g_autofree char *from = NULL; + g_autofree char *xml = NULL; + VIR_AUTOCLOSE fd = -1; + int ret = -1; ++ unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + + from = g_strdup_printf("%s/%s", path, CH_SAVE_XML); + if ((fd = virFileOpenAs(from, O_RDONLY, 0, cfg->user, cfg->group, 0)) < 0) { +@@ -942,9 +961,23 @@ static int chDomainSaveImageRead(virCHDriver *driver, + if (!(xml = chDomainSaveXMLRead(fd))) + goto end; + +- if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL, +- VIR_DOMAIN_DEF_PARSE_INACTIVE | +- VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ if (ensureACL || ensureACLWithFlags) { ++ /* Parse only the IDs for ACL checks */ ++ g_autoptr(virDomainDef) aclDef = virDomainDefIDsParseString(xml, ++ driver->xmlopt, ++ parse_flags); ++ ++ if (!aclDef) ++ goto end; ++ ++ if (ensureACL && ensureACL(conn, aclDef) < 0) ++ goto end; ++ ++ if (ensureACLWithFlags && ensureACLWithFlags(conn, aclDef, flags) < 0) ++ goto end; ++ } ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, NULL, parse_flags))) + goto end; + + *ret_def = g_steal_pointer(&def); +@@ -965,10 +998,9 @@ chDomainSaveImageGetXMLDesc(virConnectPtr conn, + + virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); + +- if (chDomainSaveImageRead(driver, path, &def) < 0) +- goto cleanup; +- +- if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) ++ if (chDomainSaveImageRead(conn, path, &def, flags, ++ virDomainSaveImageGetXMLDescEnsureACL, ++ NULL) < 0) + goto cleanup; + + ret = virDomainDefFormat(def, driver->xmlopt, +@@ -1068,10 +1100,9 @@ chDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + goto cleanup; + + path = chDomainManagedSavePath(driver, vm); +- if (chDomainSaveImageRead(driver, path, &def) < 0) +- goto cleanup; +- +- if (virDomainManagedSaveGetXMLDescEnsureACL(dom->conn, def, flags) < 0) ++ if (chDomainSaveImageRead(dom->conn, path, &def, flags, ++ NULL, ++ virDomainManagedSaveGetXMLDescEnsureACL) < 0) + goto cleanup; + + ret = virDomainDefFormat(def, driver->xmlopt, +@@ -1123,10 +1154,9 @@ chDomainRestoreFlags(virConnectPtr conn, + return -1; + } + +- if (chDomainSaveImageRead(driver, from, &def) < 0) +- goto cleanup; +- +- if (virDomainRestoreFlagsEnsureACL(conn, def) < 0) ++ if (chDomainSaveImageRead(conn, from, &def, flags, ++ virDomainRestoreFlagsEnsureACL, ++ NULL) < 0) + goto cleanup; + + if (chDomainSaveRestoreAdditionalValidation(driver, def) < 0) +-- +2.50.1.319.g90c0775e97 + diff --git a/0006-qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/0006-qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000..744c722 --- /dev/null +++ b/0006-qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,395 @@ +From 2a326c415a7e1cdd49989cc7e46b88d9ca90dd97 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 14:33:41 +0100 +Subject: [PATCH 6/6] qemu: Check ACLs before parsing the whole domain XML +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Utilise the new virDomainDefIDsParseString() for that. + +This is one of the more complex ones since there is also a function that +reads relevant metadata from a save image XML. In order _not_ to extract +the parsing out of the function (and make the function basically trivial +and all callers more complex) add a callback to the function which will +be used to check the ACLs. + +Fixes: CVE-2025-12748 +Reported-by: Святослав Терешин +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/qemu/qemu_driver.c | 90 ++++++++++++++++++++------------------- + src/qemu/qemu_migration.c | 23 +++++++++- + src/qemu/qemu_migration.h | 4 +- + src/qemu/qemu_saveimage.c | 25 +++++++++-- + src/qemu/qemu_saveimage.h | 4 +- + src/qemu/qemu_snapshot.c | 4 +- + 6 files changed, 97 insertions(+), 53 deletions(-) + +diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c +index a1b1edcbbf..1f7e587f61 100644 +--- a/src/qemu/qemu_driver.c ++++ b/src/qemu/qemu_driver.c +@@ -1556,11 +1556,17 @@ static virDomainPtr qemuDomainCreateXML(virConnectPtr conn, + if (flags & VIR_DOMAIN_START_RESET_NVRAM) + start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM; + +- if (!(def = virDomainDefParseString(xml, driver->xmlopt, +- NULL, parse_flags))) +- goto cleanup; ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; + + if (virDomainCreateXMLEnsureACL(conn, def) < 0) ++ return NULL; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ ++ if (!(def = virDomainDefParseString(xml, driver->xmlopt, ++ NULL, parse_flags))) + goto cleanup; + + if (!(vm = virDomainObjListAdd(driver->domains, &def, +@@ -5780,7 +5786,7 @@ qemuDomainRestoreInternal(virConnectPtr conn, + if (flags & VIR_DOMAIN_SAVE_RESET_NVRAM) + reset_nvram = true; + +- if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) ++ if (qemuSaveImageGetMetadata(driver, NULL, path, ensureACL, conn, &def, &data) < 0) + goto cleanup; + + sparse = data->header.format == QEMU_SAVE_FORMAT_SPARSE; +@@ -5793,9 +5799,6 @@ qemuDomainRestoreInternal(virConnectPtr conn, + if (fd < 0) + goto cleanup; + +- if (ensureACL(conn, def) < 0) +- goto cleanup; +- + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + int hookret; + +@@ -5923,10 +5926,9 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path, + + virCheckFlags(VIR_DOMAIN_SAVE_IMAGE_XML_SECURE, NULL); + +- if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) +- goto cleanup; +- +- if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) ++ if (qemuSaveImageGetMetadata(driver, NULL, path, ++ virDomainSaveImageGetXMLDescEnsureACL, ++ conn, &def, &data) < 0) + goto cleanup; + + ret = qemuDomainDefFormatXML(driver, NULL, def, flags); +@@ -5956,7 +5958,9 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, + else if (flags & VIR_DOMAIN_SAVE_PAUSED) + state = 0; + +- if (qemuSaveImageGetMetadata(driver, NULL, path, &def, &data) < 0) ++ if (qemuSaveImageGetMetadata(driver, NULL, path, ++ virDomainSaveImageDefineXMLEnsureACL, ++ conn, &def, &data) < 0) + goto cleanup; + + fd = qemuSaveImageOpen(driver, path, false, false, NULL, true); +@@ -5964,9 +5968,6 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, + if (fd < 0) + goto cleanup; + +- if (virDomainSaveImageDefineXMLEnsureACL(conn, def) < 0) +- goto cleanup; +- + if (STREQ(data->xml, dxml) && + (state < 0 || state == data->header.was_running)) { + /* no change to the XML */ +@@ -6038,7 +6039,8 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + goto cleanup; + } + +- if (qemuSaveImageGetMetadata(driver, priv->qemuCaps, path, &def, &data) < 0) ++ if (qemuSaveImageGetMetadata(driver, priv->qemuCaps, path, ++ NULL, NULL, &def, &data) < 0) + goto cleanup; + + ret = qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags); +@@ -6102,7 +6104,7 @@ qemuDomainObjRestore(virConnectPtr conn, + bool sparse = false; + g_autoptr(qemuMigrationParams) restoreParams = NULL; + +- ret = qemuSaveImageGetMetadata(driver, NULL, path, &def, &data); ++ ret = qemuSaveImageGetMetadata(driver, NULL, path, NULL, NULL, &def, &data); + if (ret < 0) { + if (qemuSaveImageIsCorrupt(driver, path)) { + if (unlink(path) < 0) { +@@ -6464,6 +6466,15 @@ qemuDomainDefineXMLFlags(virConnectPtr conn, + if (flags & VIR_DOMAIN_DEFINE_VALIDATE) + parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; + ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(def = virDomainDefIDsParseString(xml, driver->xmlopt, parse_flags))) ++ return NULL; ++ ++ if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) ++ return NULL; ++ ++ g_clear_pointer(&def, virDomainDefFree); ++ + if (!(def = virDomainDefParseString(xml, driver->xmlopt, + NULL, parse_flags))) + return NULL; +@@ -6471,9 +6482,6 @@ qemuDomainDefineXMLFlags(virConnectPtr conn, + if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) + goto cleanup; + +- if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) +- goto cleanup; +- + if (!(vm = virDomainObjListAdd(driver->domains, &def, + driver->xmlopt, + 0, &oldDef))) +@@ -10769,10 +10777,9 @@ qemuDomainMigratePrepareTunnel(virConnectPtr dconn, + return -1; + } + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepareTunnelEnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepareTunnelEnsureACL))) + return -1; + + return qemuMigrationDstPrepareTunnel(driver, dconn, +@@ -10822,10 +10829,9 @@ qemuDomainMigratePrepare2(virConnectPtr dconn, + return -1; + } + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepare2EnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepare2EnsureACL))) + return -1; + + /* Do not use cookies in v2 protocol, since the cookie +@@ -11045,10 +11051,9 @@ qemuDomainMigratePrepare3(virConnectPtr dconn, + QEMU_MIGRATION_DESTINATION))) + return -1; + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepare3EnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepare3EnsureACL))) + return -1; + + return qemuMigrationDstPrepareDirect(driver, dconn, +@@ -11148,10 +11153,9 @@ qemuDomainMigratePrepare3Params(virConnectPtr dconn, + return -1; + } + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepare3ParamsEnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepare3ParamsEnsureACL))) + return -1; + + return qemuMigrationDstPrepareDirect(driver, dconn, +@@ -11193,10 +11197,9 @@ qemuDomainMigratePrepareTunnel3(virConnectPtr dconn, + QEMU_MIGRATION_DESTINATION))) + return -1; + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepareTunnel3EnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepareTunnel3EnsureACL))) + return -1; + + return qemuMigrationDstPrepareTunnel(driver, dconn, +@@ -11245,10 +11248,9 @@ qemuDomainMigratePrepareTunnel3Params(virConnectPtr dconn, + QEMU_MIGRATION_DESTINATION))) + return -1; + +- if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname))) +- return -1; +- +- if (virDomainMigratePrepareTunnel3ParamsEnsureACL(dconn, def) < 0) ++ if (!(def = qemuMigrationAnyPrepareDef(driver, NULL, dom_xml, dname, &origname, ++ dconn, ++ virDomainMigratePrepareTunnel3ParamsEnsureACL))) + return -1; + + return qemuMigrationDstPrepareTunnel(driver, dconn, +diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c +index 9109c4526d..9059f9aa3a 100644 +--- a/src/qemu/qemu_migration.c ++++ b/src/qemu/qemu_migration.c +@@ -4030,7 +4030,9 @@ qemuMigrationAnyPrepareDef(virQEMUDriver *driver, + virQEMUCaps *qemuCaps, + const char *dom_xml, + const char *dname, +- char **origname) ++ char **origname, ++ virConnectPtr sconn, ++ int (*ensureACL)(virConnectPtr, virDomainDef *)) + { + virDomainDef *def; + char *name = NULL; +@@ -4041,6 +4043,24 @@ qemuMigrationAnyPrepareDef(virQEMUDriver *driver, + return NULL; + } + ++ if (ensureACL) { ++ g_autoptr(virDomainDef) aclDef = NULL; ++ ++ /* Avoid parsing the whole domain definition for ACL checks */ ++ if (!(aclDef = virDomainDefIDsParseString(dom_xml, driver->xmlopt, ++ VIR_DOMAIN_DEF_PARSE_INACTIVE))) ++ return NULL; ++ ++ if (dname) { ++ VIR_FREE(aclDef->name); ++ aclDef->name = g_strdup(dname); ++ } ++ ++ if (ensureACL(sconn, aclDef) < 0) { ++ return NULL; ++ } ++ } ++ + if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, + qemuCaps, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) +@@ -4969,6 +4989,7 @@ qemuMigrationSrcRun(virQEMUDriver *driver, + if (!(persistDef = qemuMigrationAnyPrepareDef(driver, + priv->qemuCaps, + persist_xml, ++ NULL, NULL, + NULL, NULL))) + goto error; + } else { +diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h +index 36865040df..50910ecb1f 100644 +--- a/src/qemu/qemu_migration.h ++++ b/src/qemu/qemu_migration.h +@@ -134,7 +134,9 @@ qemuMigrationAnyPrepareDef(virQEMUDriver *driver, + virQEMUCaps *qemuCaps, + const char *dom_xml, + const char *dname, +- char **origname); ++ char **origname, ++ virConnectPtr sconn, ++ int (*ensureACL)(virConnectPtr, virDomainDef *)); + + int + qemuMigrationDstPrepareTunnel(virQEMUDriver *driver, +diff --git a/src/qemu/qemu_saveimage.c b/src/qemu/qemu_saveimage.c +index aa030798ce..145a0f4832 100644 +--- a/src/qemu/qemu_saveimage.c ++++ b/src/qemu/qemu_saveimage.c +@@ -614,16 +614,21 @@ qemuSaveImageIsCorrupt(virQEMUDriver *driver, const char *path) + * @driver: qemu driver data + * @qemuCaps: pointer to qemuCaps if the domain is running or NULL + * @path: path of the save image ++ * @ensureACL: ACL callback to check against the definition or NULL ++ * @conn: parameter for the @ensureACL callback + * @ret_def: returns domain definition created from the XML stored in the image + * @ret_data: returns structure filled with data from the image header + * +- * Open the save image file, read libvirt's save image metadata, and populate +- * the @ret_def and @ret_data structures. Returns 0 on success and -1 on failure. ++ * Open the save image file, read libvirt's save image metadata, optionally ++ * check ACLs before parsing the whole domain definition and populate the ++ * @ret_def and @ret_data structures. Returns 0 on success and -1 on failure. + */ + int + qemuSaveImageGetMetadata(virQEMUDriver *driver, + virQEMUCaps *qemuCaps, + const char *path, ++ int (*ensureACL)(virConnectPtr, virDomainDef *), ++ virConnectPtr conn, + virDomainDef **ret_def, + virQEMUSaveData **ret_data) + { +@@ -631,6 +636,8 @@ qemuSaveImageGetMetadata(virQEMUDriver *driver, + VIR_AUTOCLOSE fd = -1; + virQEMUSaveData *data; + g_autoptr(virDomainDef) def = NULL; ++ unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + int rc; + + if ((fd = qemuDomainOpenFile(cfg, NULL, path, O_RDONLY, NULL)) < 0) +@@ -640,10 +647,20 @@ qemuSaveImageGetMetadata(virQEMUDriver *driver, + return rc; + + data = *ret_data; ++ ++ if (ensureACL) { ++ /* Parse only the IDs for ACL checks */ ++ g_autoptr(virDomainDef) aclDef = virDomainDefIDsParseString(data->xml, ++ driver->xmlopt, ++ parse_flags); ++ ++ if (!aclDef || ensureACL(conn, aclDef) < 0) ++ return -1; ++ } ++ + /* Create a domain from this XML */ + if (!(def = virDomainDefParseString(data->xml, driver->xmlopt, qemuCaps, +- VIR_DOMAIN_DEF_PARSE_INACTIVE | +- VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) ++ parse_flags))) + return -1; + + *ret_def = g_steal_pointer(&def); +diff --git a/src/qemu/qemu_saveimage.h b/src/qemu/qemu_saveimage.h +index 89c6941385..15b73eb395 100644 +--- a/src/qemu/qemu_saveimage.h ++++ b/src/qemu/qemu_saveimage.h +@@ -98,9 +98,11 @@ int + qemuSaveImageGetMetadata(virQEMUDriver *driver, + virQEMUCaps *qemuCaps, + const char *path, ++ int (*ensureACL)(virConnectPtr, virDomainDef *), ++ virConnectPtr conn, + virDomainDef **ret_def, + virQEMUSaveData **ret_data) +- ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5); ++ ATTRIBUTE_NONNULL(6) ATTRIBUTE_NONNULL(7); + + int + qemuSaveImageOpen(virQEMUDriver *driver, +diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c +index d4994dd54e..5aa7d1b3a7 100644 +--- a/src/qemu/qemu_snapshot.c ++++ b/src/qemu/qemu_snapshot.c +@@ -2486,8 +2486,8 @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, + g_autoptr(virDomainDef) savedef = NULL; + + memdata->path = snapdef->memorysnapshotfile; +- if (qemuSaveImageGetMetadata(driver, NULL, memdata->path, &savedef, +- &memdata->data) < 0) ++ if (qemuSaveImageGetMetadata(driver, NULL, memdata->path, NULL, NULL, ++ &savedef, &memdata->data) < 0) + return -1; + + memdata->fd = qemuSaveImageOpen(driver, memdata->path, +-- +2.50.1.319.g90c0775e97 + diff --git a/libvirt.spec b/libvirt.spec index 038b473..f7f4735 100644 --- a/libvirt.spec +++ b/libvirt.spec @@ -113,7 +113,7 @@ Summary: Library providing a simple virtualization API Name: libvirt Version: 9.10.0 -Release: 5%{?dist} +Release: 6%{?dist} License: LGPLv2+ URL: https://libvirt.org/ Source0: https://libvirt.org/sources/libvirt-%{version}.tar.xz @@ -140,6 +140,21 @@ Patch0016: 0003-v4-1-4-src-Add-ARM-CCA-support-in-qemu-driver-to-lau.patch Patch0017: 0004-v4-2-4-src-Add-ARM-CCA-support-in-domain-capabilitie.patch Patch0018: 0005-v4-3-4-src-Add-ARM-CCA-support-in-domain-schema.patch +# CVE-2025-12748 +Patch0019: 0001-ch-Enable-ETHERNET-Network-mode-support.patch +Patch0020: 0001-ch_driver-Add-domainSave-domainSaveFlags-callbacks.patch +Patch0021: 0001-ch_driver-Add-domainManagedSave-callback.patch +Patch0022: 0001-ch_driver-Implement-more-save-callbacks.patch +Patch0023: 0001-ch_driver-Implement-domain-restore-callbacks.patch +Patch0024: 0001-bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0025: 0002-libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0026: 0003-lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0027: 0004-vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0028: 0005-ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0029: 0001-qemu-Move-unlinking-corrupt-save-image-file-to-calle.patch +Patch0030: 0001-qemu-Decompose-qemuSaveImageOpen.patch +Patch0031: 0006-qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch + Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-daemon-config-network = %{version}-%{release} Requires: libvirt-daemon-config-nwfilter = %{version}-%{release} @@ -2121,6 +2136,9 @@ exit 0 %changelog +* Fri Dec 19 2025 cunshunxia - 9.10.0-6 +- fix CVE-2025-12748 + * Sat Sep 20 2025 Xu Raoqing - 9.10.0-5 - [Type] enhancement - [DESC] Add CCA support -- Gitee