[libvirt] [PATCH 0/2] qemu: Limit backing chain to max 200 layers (blockdev-add saga)

More than 256 deep element tree break with our XML parser. Backing chains are one of the simple ways to get there. Forbid them. Note that the practical limit/sane approach is way less than 200. Peter Krempa (2): qemu: domain: Refactor cleanup in qemuDomainDetermineDiskChain qemu: Prevent storage causing too much nested XML src/qemu/qemu_domain.c | 91 +++++++++++++++++++++++++++++++----------- src/qemu/qemu_domain.h | 4 ++ src/qemu/qemu_driver.c | 6 +++ 3 files changed, 78 insertions(+), 23 deletions(-) -- 2.21.0

Use VIR_AUTOUNREF and get rid of the cleanup label. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c7eb0b5e9a..8cb5e14ea4 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10006,21 +10006,18 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, virStorageSourcePtr disksrc, bool report_broken) { - virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + VIR_AUTOUNREF(virQEMUDriverConfigPtr) cfg = virQEMUDriverGetConfig(driver); virStorageSourcePtr src; /* iterator for the backing chain declared in XML */ virStorageSourcePtr n; /* iterator for the backing chain detected from disk */ qemuDomainObjPrivatePtr priv = vm->privateData; - int ret = -1; uid_t uid; gid_t gid; if (!disksrc) disksrc = disk->src; - if (virStorageSourceIsEmpty(disksrc)) { - ret = 0; - goto cleanup; - } + if (virStorageSourceIsEmpty(disksrc)) + return 0; /* There is no need to check the backing chain for disks without backing * support */ @@ -10032,13 +10029,13 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, if (report_broken) virStorageFileReportBrokenChain(errno, disksrc, disksrc); - goto cleanup; + return -1; } /* terminate the chain for such images as the code below would do */ if (!disksrc->backingStore && !(disksrc->backingStore = virStorageSourceNew())) - goto cleanup; + return -1; /* host cdrom requires special treatment in qemu, so we need to check * whether a block device is a cdrom */ @@ -10048,8 +10045,7 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, virFileIsCDROM(disksrc->path) == 1) disksrc->hostcdrom = true; - ret = 0; - goto cleanup; + return 0; } src = disksrc; @@ -10059,16 +10055,16 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, int rv = virStorageFileSupportsAccess(src); if (rv < 0) - goto cleanup; + return -1; if (rv > 0) { if (qemuDomainStorageFileInit(driver, vm, src, disksrc) < 0) - goto cleanup; + return -1; if (virStorageFileAccess(src, F_OK) < 0) { virStorageFileReportBrokenChain(errno, src, disksrc); virStorageFileDeinit(src); - goto cleanup; + return -1; } virStorageFileDeinit(src); @@ -10079,33 +10075,27 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, /* We skipped to the end of the chain. Skip detection if there's the * terminator. (An allocated but empty backingStore) */ - if (src->backingStore) { - ret = 0; - goto cleanup; - } + if (src->backingStore) + return 0; qemuDomainGetImageIds(cfg, vm, src, disksrc, &uid, &gid); if (virStorageFileGetMetadata(src, uid, gid, report_broken) < 0) - goto cleanup; + return -1; for (n = src->backingStore; virStorageSourceIsBacking(n); n = n->backingStore) { if (qemuDomainValidateStorageSource(n, priv->qemuCaps) < 0) - goto cleanup; + return -1; if (qemuDomainPrepareDiskSourceData(disk, n, cfg, priv->qemuCaps) < 0) - goto cleanup; + return -1; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0) - goto cleanup; + return -1; } - ret = 0; - - cleanup: - virObjectUnref(cfg); - return ret; + return 0; } -- 2.21.0

On Wed, Sep 04, 2019 at 05:34:18PM +0200, Peter Krempa wrote:
Use VIR_AUTOUNREF and get rid of the cleanup label.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Since libvirt stores the backing chain into the XML in a nested way it is the prime possibility to hit libxml2's parsing limit of 256 layers. Introduce code which will crawl the backing chain and verify that it's not too deep. The maximum nesting is set to 200 layers so that there's still some space left for attitional properties or nesting into snapshot XMLs. The check is applied to all disk use cases (starting, hotplug, media change) as well as block copy which changes image and snapshots. We simply report an error and refuse the operation. Without this check a restart of libvirtd would result in the status XML failing to be parsed and thus losing the VM. https://bugzilla.redhat.com/show_bug.cgi?id=1524278 Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 57 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_domain.h | 4 +++ src/qemu/qemu_driver.c | 6 +++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 8cb5e14ea4..af46aece76 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -9986,6 +9986,54 @@ qemuDomainStorageAlias(const char *device, int depth) } +/** + * qemuDomainStorageSourceValidateDepth: + * @src: storage source chain to validate + * @add: offsets the calculated number of images + * @diskdst: optional disk target to use in error message + * + * The XML parser limits the maximum element nesting to 256 layers. As libvirt + * reports the chain into the status and in some cases the config XML we must + * validate that any user-provided chains will not exceed the XML nesting limit + * when formatted to the XML. + * + * This function validates that the storage source chain starting @src is at + * most 200 layers deep. @add allows to modify the calculated value to offset + * the number to allow checking cases when new layers are going to be added + * to the chain. + * + * Returns 0 on success and -1 if the chain is too deep. Error is reported. + */ +int +qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src, + int add, + const char *diskdst) +{ + virStorageSourcePtr n; + size_t nlayers = 0; + + for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) + nlayers++; + + nlayers += add; + + if (nlayers > 200) { + if (diskdst) + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("backing chains more than 200 layers deep are not " + "supported for disk '%s'"), diskdst); + else + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("backing chains more than 200 layers deep are not " + "supported")); + + return -1; + } + + return 0; +} + + /** * qemuDomainDetermineDiskChain: * @driver: qemu driver object @@ -10075,8 +10123,12 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, /* We skipped to the end of the chain. Skip detection if there's the * terminator. (An allocated but empty backingStore) */ - if (src->backingStore) + if (src->backingStore) { + if (qemuDomainStorageSourceValidateDepth(disksrc, 0, disk->dst) < 0) + return -1; + return 0; + } qemuDomainGetImageIds(cfg, vm, src, disksrc, &uid, &gid); @@ -10095,6 +10147,9 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, return -1; } + if (qemuDomainStorageSourceValidateDepth(disksrc, 0, disk->dst) < 0) + return -1; + return 0; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index d097f23342..33eb501e2a 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -802,6 +802,10 @@ int qemuDomainCheckDiskPresence(virQEMUDriverPtr driver, virDomainObjPtr vm, unsigned int flags); +int qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src, + int add, + const char *diskdst); + int qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainDiskDefPtr disk, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 78f5471b79..aa9efc684f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15315,6 +15315,9 @@ qemuDomainSnapshotDiskDataCollectOne(virQEMUDriverPtr driver, dd->disk = disk; + if (qemuDomainStorageSourceValidateDepth(disk->src, 1, disk->dst) < 0) + return -1; + if (!(dd->src = virStorageSourceCopy(snapdisk->src, false))) return -1; @@ -18330,6 +18333,9 @@ qemuDomainBlockCopyCommonValidateUserMirrorBackingStore(virStorageSourcePtr mirr _("backingStore of mirror without VIR_DOMAIN_BLOCK_COPY_SHALLOW doesn't make sense")); return -1; } + + if (qemuDomainStorageSourceValidateDepth(mirror, 0, NULL) < 0) + return -1; } return 0; -- 2.21.0

On 9/4/19 10:34 AM, Peter Krempa wrote:
Since libvirt stores the backing chain into the XML in a nested way it is the prime possibility to hit libxml2's parsing limit of 256 layers.
Nasty that libxml2 is forcing us into this arbitrary limit, but it is some rather hairy setup to get there.
Introduce code which will crawl the backing chain and verify that it's not too deep. The maximum nesting is set to 200 layers so that there's still some space left for attitional properties or nesting into snapshot
additional
XMLs.
The check is applied to all disk use cases (starting, hotplug, media change) as well as block copy which changes image and snapshots.
We simply report an error and refuse the operation.
Without this check a restart of libvirtd would result in the status XML failing to be parsed and thus losing the VM.
https://bugzilla.redhat.com/show_bug.cgi?id=1524278
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
+ * + * This function validates that the storage source chain starting @src is at + * most 200 layers deep. @add allows to modify the calculated value to offset
s/allows to modify/modifies/
+ * the number to allow checking cases when new layers are going to be added + * to the chain.
Or maybe even: @add represents any pre-existing chain depth when preparing to add this source to a chain.
+ * + * Returns 0 on success and -1 if the chain is too deep. Error is reported. + */ +int +qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src, + int add, + const char *diskdst)
Otherwise makes sense to me. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

On Wed, Sep 04, 2019 at 05:34:19PM +0200, Peter Krempa wrote:
Since libvirt stores the backing chain into the XML in a nested way it is the prime possibility to hit libxml2's parsing limit of 256 layers.
Introduce code which will crawl the backing chain and verify that it's not too deep. The maximum nesting is set to 200 layers so that there's still some space left for attitional properties or nesting into snapshot XMLs.
The check is applied to all disk use cases (starting, hotplug, media change) as well as block copy which changes image and snapshots.
We simply report an error and refuse the operation.
Without this check a restart of libvirtd would result in the status XML failing to be parsed and thus losing the VM.
https://bugzilla.redhat.com/show_bug.cgi?id=1524278
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 57 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_domain.h | 4 +++ src/qemu/qemu_driver.c | 6 +++++ 3 files changed, 66 insertions(+), 1 deletion(-)
With the suggestions pointed out by Eric: Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (3)
-
Eric Blake
-
Ján Tomko
-
Peter Krempa