diff --git a/bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..567e229ecabe2beba9893527aae133924a57a3b4 --- /dev/null +++ b/bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,72 @@ +From acb752d92d91b1e96158d876f8bb097d127c082e Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:31:12 +0100 +Subject: [PATCH 2/8] 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 4203b13f94..c48bca3c1e 100644 +--- a/src/bhyve/bhyve_driver.c ++++ b/src/bhyve/bhyve_driver.c +@@ -505,6 +505,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; +@@ -512,9 +521,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; + +@@ -878,11 +884,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 + diff --git a/ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..8cd5b648df347304d37d05e6050c3d1301569b92 --- /dev/null +++ b/ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,82 @@ +From 1666a8f2a07472b20ba76ef49c77ff941037d5da Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 16:23:30 +0100 +Subject: [PATCH 6/8] 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 | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +diff --git a/src/ch/ch_driver.c b/src/ch/ch_driver.c +index 96de5044ac..722b74cc1f 100644 +--- a/src/ch/ch_driver.c ++++ b/src/ch/ch_driver.c +@@ -202,14 +202,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, +@@ -284,6 +289,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; +@@ -291,9 +305,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, NULL))) +-- +2.50.1 + diff --git a/conf-Add-virDomainDefIDsParseString.patch b/conf-Add-virDomainDefIDsParseString.patch new file mode 100644 index 0000000000000000000000000000000000000000..32eb41dc7c41b131081c4c3b2abf21d2ac23c00d --- /dev/null +++ b/conf-Add-virDomainDefIDsParseString.patch @@ -0,0 +1,87 @@ +From a7d8522fee17f09ba033077be50d72b0001d0ae2 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 14:33:31 +0100 +Subject: [PATCH 1/8] conf: Add virDomainDefIDsParseString + +This function performs only parsing with the underlying +virDomainDefParseIDs() function to get needed metadata for any ACL +checks, but nothing else to avoid extraneous allocations and any +parser-induced DoS over ACL-forbidden connections. + +Signed-off-by: Martin Kletzander +Reviewed-by: Michal Privoznik +--- + src/conf/domain_conf.c | 29 +++++++++++++++++++++++++++++ + src/conf/domain_conf.h | 3 +++ + src/libvirt_private.syms | 1 + + 3 files changed, 33 insertions(+) + +diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c +index 3a64a11b14..f84563b2af 100644 +--- a/src/conf/domain_conf.c ++++ b/src/conf/domain_conf.c +@@ -20026,6 +20026,35 @@ virDomainDefParse(const char *xmlStr, + return virDomainDefParseNode(ctxt, xmlopt, parseOpaque, flags); + } + ++virDomainDef * ++virDomainDefIDsParseString(const char *xmlStr, ++ virDomainXMLOption *xmlopt, ++ unsigned int flags) ++{ ++ g_autoptr(virDomainDef) def = NULL; ++ g_autoptr(xmlDoc) xml = NULL; ++ g_autoptr(xmlXPathContext) ctxt = NULL; ++ bool uuid_generated = false; ++ ++ xml = virXMLParseWithIndent(NULL, xmlStr, _("(domain_definition)"), ++ "domain", &ctxt, "domain.rng", false); ++ ++ if (!xml) ++ return NULL; ++ ++ def = virDomainDefNew(xmlopt); ++ if (!def) ++ return NULL; ++ ++ if (virDomainDefParseIDs(def, ctxt, flags, &uuid_generated) < 0) ++ return NULL; ++ ++ if (uuid_generated) ++ memset(def->uuid, 0, VIR_UUID_BUFLEN); ++ ++ return g_steal_pointer(&def); ++} ++ + virDomainDef * + virDomainDefParseString(const char *xmlStr, + virDomainXMLOption *xmlopt, +diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h +index d70c2633a7..ccdcc3a47b 100644 +--- a/src/conf/domain_conf.h ++++ b/src/conf/domain_conf.h +@@ -3811,6 +3811,9 @@ virDomainDiskDef *virDomainDiskDefParse(const char *xmlStr, + virStorageSource *virDomainDiskDefParseSource(const char *xmlStr, + virDomainXMLOption *xmlopt, + unsigned int flags); ++virDomainDef * virDomainDefIDsParseString(const char *xmlStr, ++ virDomainXMLOption *xmlopt, ++ unsigned int flags); + virDomainDef *virDomainDefParseString(const char *xmlStr, + virDomainXMLOption *xmlopt, + void *parseOpaque, +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index 6ab78d504b..6165a182c8 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -351,6 +351,7 @@ virDomainDefHasUSB; + virDomainDefHasVcpusOffline; + virDomainDefHasVDPANet; + virDomainDefHasVFIOHostdev; ++virDomainDefIDsParseString; + virDomainDefLifecycleActionAllowed; + virDomainDefMaybeAddController; + virDomainDefMaybeAddInput; +-- +2.50.1 + diff --git a/libvirt.spec b/libvirt.spec index 2d1bf016a5af76a7c75f9b8186f6e82db78a5e5d..d3394855f79fe69d02171e73a9347e06263b5979 100644 --- a/libvirt.spec +++ b/libvirt.spec @@ -262,7 +262,7 @@ Summary: Library providing a simple virtualization API Name: libvirt Version: 9.10.0 -Release: 18 +Release: 19 License: LGPLv2+ URL: https://libvirt.org/ @@ -357,6 +357,15 @@ Patch0083: v4-1-4-src-Add-ARM-CCA-support-in-qemu-driver-to-lau.patch Patch0084: v4-2-4-src-Add-ARM-CCA-support-in-domain-capabilitie.patch Patch0085: v4-3-4-src-Add-ARM-CCA-support-in-domain-schema.patch Patch0086: hostdev-add-numa-configure-for-host-pci-device.patch +Patch0087: conf-Add-virDomainDefIDsParseString.patch +Patch0088: bhyve-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0089: libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0090: lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0091: vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0092: ch-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0093: qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch +Patch0094: qemu-snapshot-Set-umask-for-qemu-img-when-creating-e.patch + Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-daemon-config-network = %{version}-%{release} @@ -2648,6 +2657,17 @@ exit 0 %endif %changelog +* Mon Dec 1 2025 ZixinLi - 9.10.0-19 +- Fix CVE-2025-12748 CVE-2025-13193 +- conf: Add virDomainDefIDsParseString +- bhyve: Check ACLs before parsing the whole domain XML +- libxl: Check ACLs before parsing the whole domain XML +- lxc: Check ACLs before parsing the whole domain XML +- vz: Check ACLs before parsing the whole domain XML +- ch: Check ACLs before parsing the whole domain XML +- qemu: Check ACLs before parsing the whole domain XML +- qemu: snapshot: Set umask for 'qemu-img' when creating external inactive snapshots + * Mon Sep 01 2025 PengruiZhang - 9.10.0-18 - add qemu VIRTCCA capability detection - add virtcca test xml diff --git a/libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..1c1737015e32e3dd3b3341df201ef929246f7c84 --- /dev/null +++ b/libxl-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,71 @@ +From 3e2342f5150c88bdd720bfc80210e53581e23895 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:43:57 +0100 +Subject: [PATCH 3/8] 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 c98d2d737a..e73b04e628 100644 +--- a/src/libxl/libxl_driver.c ++++ b/src/libxl/libxl_driver.c +@@ -1036,13 +1036,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 | +@@ -2822,6 +2827,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; +@@ -2829,9 +2842,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 + diff --git a/lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..1a842b09cf98edb226228417fb9317714a592fe7 --- /dev/null +++ b/lxc-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,73 @@ +From 021fa60cfba61a058ea4f828747336dece766ee4 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 15:49:01 +0100 +Subject: [PATCH 4/8] 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 25cbfc57c9..94d1f726f6 100644 +--- a/src/lxc/lxc_driver.c ++++ b/src/lxc/lxc_driver.c +@@ -408,6 +408,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; +@@ -415,9 +424,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; + +@@ -1077,13 +1083,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 + diff --git a/qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..921f37c2970e55c3d25d1939cf25433f9e77fe6e --- /dev/null +++ b/qemu-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,382 @@ +From e83f6a57bb5aa479ba365cf005a6b4a4846ebec1 Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 14:33:41 +0100 +Subject: [PATCH 7/8] 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 | 88 +++++++++++++++++++-------------------- + src/qemu/qemu_migration.c | 23 +++++++++- + src/qemu/qemu_migration.h | 4 +- + src/qemu/qemu_saveimage.c | 21 ++++++++-- + src/qemu/qemu_saveimage.h | 4 +- + src/qemu/qemu_snapshot.c | 3 +- + 6 files changed, 92 insertions(+), 51 deletions(-) + +diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c +index df275c403c..a91d635bd3 100644 +--- a/src/qemu/qemu_driver.c ++++ b/src/qemu/qemu_driver.c +@@ -1605,11 +1605,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, +@@ -5753,13 +5759,10 @@ qemuDomainRestoreInternal(virConnectPtr conn, + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, + (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, +- &wrapperFd, false, false); ++ &wrapperFd, false, false, conn, ensureACL); + if (fd < 0) + goto cleanup; + +- if (ensureACL(conn, def) < 0) +- goto cleanup; +- + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + int hookret; + +@@ -5888,14 +5891,12 @@ 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, false, ++ conn, virDomainSaveImageGetXMLDescEnsureACL); + + if (fd < 0) + goto cleanup; + +- if (virDomainSaveImageGetXMLDescEnsureACL(conn, def) < 0) +- goto cleanup; +- + ret = qemuDomainDefFormatXML(driver, NULL, def, flags); + + cleanup: +@@ -5925,14 +5926,12 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, + state = 0; + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- false, NULL, true, false); ++ false, NULL, true, false, ++ conn, virDomainSaveImageDefineXMLEnsureACL); + + 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 */ +@@ -6006,7 +6005,8 @@ qemuDomainManagedSaveGetXMLDesc(virDomainPtr dom, unsigned int flags) + } + + if ((fd = qemuSaveImageOpen(driver, priv->qemuCaps, path, &def, &data, +- false, NULL, false, false)) < 0) ++ false, NULL, false, false, ++ NULL, NULL)) < 0) + goto cleanup; + + ret = qemuDomainDefFormatXML(driver, priv->qemuCaps, def, flags); +@@ -6070,7 +6070,7 @@ qemuDomainObjRestore(virConnectPtr conn, + virFileWrapperFd *wrapperFd = NULL; + + fd = qemuSaveImageOpen(driver, NULL, path, &def, &data, +- bypass_cache, &wrapperFd, false, true); ++ bypass_cache, &wrapperFd, false, true, NULL, NULL); + if (fd < 0) { + if (fd == -3) + ret = 1; +@@ -6413,6 +6413,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; +@@ -6420,9 +6429,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))) +@@ -10637,10 +10643,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, +@@ -10690,10 +10695,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 +@@ -10912,10 +10916,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, +@@ -11020,10 +11023,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, +@@ -11065,10 +11067,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, +@@ -11117,10 +11118,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 616aa043fd..5b7196abab 100644 +--- a/src/qemu/qemu_migration.c ++++ b/src/qemu/qemu_migration.c +@@ -3857,7 +3857,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; +@@ -3868,6 +3870,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 | +@@ -4801,6 +4821,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 6da44189ce..d1817cc9ec 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 89112e3e44..d74210e728 100644 +--- a/src/qemu/qemu_saveimage.c ++++ b/src/qemu/qemu_saveimage.c +@@ -530,6 +530,8 @@ qemuSaveImageGetCompressionProgram(const char *imageFormat, + * @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 ++ * @conn: parameter for the @ensureACL callback ++ * @ensureACL: ACL callback to check against the definition or NULL + * + * 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 +@@ -544,7 +546,9 @@ qemuSaveImageOpen(virQEMUDriver *driver, + bool bypass_cache, + virFileWrapperFd **wrapperFd, + bool open_write, +- bool unlink_corrupt) ++ bool unlink_corrupt, ++ virConnectPtr conn, ++ int (*ensureACL)(virConnectPtr, virDomainDef *)) + { + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + VIR_AUTOCLOSE fd = -1; +@@ -555,6 +559,8 @@ qemuSaveImageOpen(virQEMUDriver *driver, + int oflags = open_write ? O_RDWR : O_RDONLY; + size_t xml_len; + size_t cookie_len; ++ unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE | ++ VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + + if (bypass_cache) { + int directFlag = virFileDirectFdFlag(); +@@ -660,10 +666,19 @@ qemuSaveImageOpen(virQEMUDriver *driver, + } + } + ++ 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 e541792153..765c182f49 100644 +--- a/src/qemu/qemu_saveimage.h ++++ b/src/qemu/qemu_saveimage.h +@@ -78,7 +78,9 @@ qemuSaveImageOpen(virQEMUDriver *driver, + bool bypass_cache, + virFileWrapperFd **wrapperFd, + bool open_write, +- bool unlink_corrupt) ++ bool unlink_corrupt, ++ virConnectPtr conn, ++ int (*ensureACL)(virConnectPtr, virDomainDef *)) + ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); + + int +diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c +index 73ff533827..bd4c737e1e 100644 +--- a/src/qemu/qemu_snapshot.c ++++ b/src/qemu/qemu_snapshot.c +@@ -2083,7 +2083,8 @@ qemuSnapshotRevertExternalPrepare(virDomainObj *vm, + memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path, + &savedef, &memdata->data, + false, NULL, +- false, false); ++ false, false, ++ NULL, NULL); + + if (memdata->fd < 0) + return -1; +-- +2.50.1 + diff --git a/qemu-snapshot-Set-umask-for-qemu-img-when-creating-e.patch b/qemu-snapshot-Set-umask-for-qemu-img-when-creating-e.patch new file mode 100644 index 0000000000000000000000000000000000000000..b210f9f706f9c94bfb7907666ffb07017e1b661d --- /dev/null +++ b/qemu-snapshot-Set-umask-for-qemu-img-when-creating-e.patch @@ -0,0 +1,36 @@ +From 3cbbb4959b9b2155f57c27b5897ee105751a7247 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Wed, 12 Nov 2025 17:52:05 +0100 +Subject: [PATCH 8/8] qemu: snapshot: Set umask for 'qemu-img' when creating + external inactive snapshots + +External inactive snapshots are created by invoking 'qemu-img' which +creates the file. Currently qemu-img creates image with mode 644 based +on default umask as libvirt doesn't set any. + +Having a world-readable image is obviously wrong so set the umask to +077 to have the file readable only by the owner. + +Resolves: https://bugs.debian.org/1120119 +Signed-off-by: Peter Krempa +--- + src/qemu/qemu_snapshot.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c +index bd4c737e1e..9016482d5b 100644 +--- a/src/qemu/qemu_snapshot.c ++++ b/src/qemu/qemu_snapshot.c +@@ -233,6 +233,9 @@ qemuSnapshotCreateQcow2Files(virQEMUDriver *driver, + NULL))) + return -1; + ++ /* ensure that new files are only readable by the user */ ++ virCommandSetUmask(cmd, 0077); ++ + /* adds cmd line arg: backing_fmt=format,backing_file=/path/to/backing/file */ + virBufferAsprintf(&buf, "backing_fmt=%s,backing_file=", + virStorageFileFormatTypeToString(defdisk->src->format)); +-- +2.50.1 + diff --git a/vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch b/vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch new file mode 100644 index 0000000000000000000000000000000000000000..49df5694fe771fbcb8f98ca5d4e8fa337e30a925 --- /dev/null +++ b/vz-Check-ACLs-before-parsing-the-whole-domain-XML.patch @@ -0,0 +1,64 @@ +From 3c7c03683df13c38b65bd8bb0c0192c7c3d979ea Mon Sep 17 00:00:00 2001 +From: Martin Kletzander +Date: Thu, 6 Nov 2025 16:03:26 +0100 +Subject: [PATCH 5/8] 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 c7ceec2339..1f8cad41a5 100644 +--- a/src/vz/vz_driver.c ++++ b/src/vz/vz_driver.c +@@ -795,6 +795,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; +@@ -802,9 +811,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(); +@@ -2972,9 +2978,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 +