[libvirt] [PATCH v2 11/15] qemu_firmware: Introduce qemuFirmwareFillDomain()

Laszlo Ersek lersek at redhat.com
Tue Mar 12 12:21:11 UTC 2019


On 03/07/19 10:29, Michal Privoznik wrote:
> And finally the last missing piece. This is what puts it all
> together.
>
> At the beginning, qemuFirmwareFillDomain() loads all possible
> firmware description files based on algorithm described earlier.
> Then it tries to find description which matches given domain.
> The criteria are:
>
>   - firmware is the right type (e.g. it's bios when bios was
>     requested in domain XML)
>   - firmware is suitable for guest architecture/machine type
>   - firmware allows desired guest features to stay enabled (e.g.
>     if s3/s4 is enabled for guest then firmware has to support
>     it too)
>
> Once the desired description has been found it is then used to
> set various bits of virDomainDef so that proper qemu cmd line is
> constructed as demanded by the description file. For instance,
> secure boot enabled firmware might request SMM -> it will be
> enabled if needed.
>
> Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
> ---
>  src/qemu/qemu_firmware.c | 329 +++++++++++++++++++++++++++++++++++++++
>  src/qemu/qemu_firmware.h |   7 +
>  2 files changed, 336 insertions(+)
>
> diff --git a/src/qemu/qemu_firmware.c b/src/qemu/qemu_firmware.c
> index a818f60c91..c8b337cf2a 100644
> --- a/src/qemu/qemu_firmware.c
> +++ b/src/qemu/qemu_firmware.c
> @@ -23,6 +23,8 @@
>  #include "qemu_firmware.h"
>  #include "configmake.h"
>  #include "qemu_capabilities.h"
> +#include "qemu_domain.h"
> +#include "qemu_process.h"
>  #include "virarch.h"
>  #include "virfile.h"
>  #include "virhash.h"
> @@ -1033,3 +1035,330 @@ qemuFirmwareFetchConfigs(char ***firmwares)
>
>      return 0;
>  }
> +
> +
> +static bool
> +qemuFirmwareMatchDomain(const virDomainDef *def,
> +                        const qemuFirmware *fw,
> +                        const char *path)
> +{
> +    size_t i;
> +    bool supportsS3 = false;
> +    bool supportsS4 = false;
> +    bool requiresSMM = false;
> +    bool supportsSEV = false;
> +
> +    for (i = 0; i < fw->ninterfaces; i++) {
> +        if ((def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_BIOS &&
> +             fw->interfaces[i] == QEMU_FIRMWARE_OS_INTERFACE_BIOS) ||
> +            (def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_EFI &&
> +             fw->interfaces[i] == QEMU_FIRMWARE_OS_INTERFACE_UEFI))
> +            break;
> +    }
> +
> +    if (i == fw->ninterfaces) {
> +        VIR_DEBUG("No matching interface in '%s'", path);
> +        return false;
> +    }
> +
> +    for (i = 0; i < fw->ntargets; i++) {
> +        size_t j;
> +
> +        if (def->os.arch != fw->targets[i]->architecture)
> +            continue;
> +
> +        for (j = 0; j < fw->targets[i]->nmachines; j++) {
> +            if (virStringMatch(def->os.machine, fw->targets[i]->machines[j]))
> +                break;
> +        }
> +
> +        if (j == fw->targets[i]->nmachines)
> +            continue;
> +
> +        break;
> +    }
> +
> +    if (i == fw->ntargets) {
> +        VIR_DEBUG("No matching machine type in '%s'", path);
> +        return false;
> +    }
> +
> +    for (i = 0; i < fw->nfeatures; i++) {
> +        switch (fw->features[i]) {
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S3:
> +            supportsS3 = true;
> +            break;
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S4:
> +            supportsS4 = true;
> +            break;
> +        case QEMU_FIRMWARE_FEATURE_AMD_SEV:
> +            supportsSEV = true;
> +            break;
> +        case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
> +            requiresSMM = true;
> +            break;
> +
> +        case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
> +        case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
> +        case QEMU_FIRMWARE_FEATURE_NONE:
> +        case QEMU_FIRMWARE_FEATURE_LAST:
> +            break;
> +        }
> +    }
> +
> +    if (def->pm.s3 == VIR_TRISTATE_BOOL_YES &&
> +        !supportsS3) {
> +        VIR_DEBUG("Domain requires S3, firmware '%s' doesn't support it", path);
> +        return false;
> +    }
> +
> +    if (def->pm.s4 == VIR_TRISTATE_BOOL_YES &&
> +        !supportsS4) {
> +        VIR_DEBUG("Domain requires S3, firmware '%s' doesn't support it", path);

(1) This debug message should refer to "S4", not "S3".

> +        return false;
> +    }
> +
> +    if (def->os.loader &&
> +        ((def->os.loader->secure == VIR_TRISTATE_BOOL_YES) != requiresSMM)) {
> +        VIR_DEBUG("Not matching secure boot/SMM in '%s'", path);
> +        return false;
> +    }

This check is too strict. Please refer to point (1) in:

  http://mid.mail-archive.com/9835d837-cfa4-d708-2f41-3d29e2254de4@redhat.com

There I wrote:

> (1) if REQUIRES_SMM is absent from the firmware descriptor, then
> @secure must be "no" in the domain config. Equivalently, if @secure is
> "yes", then the firmware descriptor must come with REQUIRES_SMM.

This means that "@secure *implies* REQUIRES_SMM":

  @secure --> REQUIRES_SMM

and that an equivalent form to write the exact same logical implication
is:

  !REQUIRES_SMM --> !@secure

But the C condition above is stricter than this. It will reject
(!@secure && REQUIRES_SMM). That's wrong. @secure=no *should* accept a
firmware both with and without REQUIRES_SMM. @secure=yes should only
accept a firmware with REQUIRES_SMM.

The way to write implications in the C language is to first transform
the logical predicate from the "implication operator" to the logical
negation operator and the disjunction ("or") operator. In general:

  A --> B

is equivalent to

  !A or B

(because: false implies anything; otherwise, if "A" is true, then "B"
must be true as well.)

Note that, from this spelling of the "implication operator", it is
evident that A-->B is equivalent to (!B)-->(!A):

  !(!B) or (!A)
  B     or !A
  !A    or B

(ignoring the short-circuit behavior of the || operator in the C
language, for this discussion).

This is why I wrote that the following two forms were equivalent:

  @secure --> REQUIRES_SMM
  !REQUIRES_SMM --> !@secure

... Anyway, so we know how to write "@secure --> REQUIRES_SMM" with
logical negation and logical-or. In the "if" condition however, we want
to catch the negation of that condition. Let's calculate that:

  !(A --> B)    // substitute formula with "or"
  !(!A or B)    // enter De Morgan, for pushing down the negation
  A and !B

(2) Therefore, in the C source code, we should write:

    if (def->os.loader &&
        def->os.loader->secure == VIR_TRISTATE_BOOL_YES &&
        !requiresSMM) {
        VIR_DEBUG("Domain restricts pflash programming to SMM, "
                  "but firmware '%s' doesn't support SMM", path);
        return false;
    }


... Unfortunately, the term "requires SMM" is quite confusing. It is a
single term that expresses:

- *both* that the firmware *supports* SMM (therefore it makes sense for
  the user to restrict pflash r/w access to code that runs in SMM, via
  @secure=yes),

- *and* that the firmware *requires* SMM emulation from the underlying
  QEMU board (hence the requirement on <smm state='on'/>, later).

Back to the patch:

On 03/07/19 10:29, Michal Privoznik wrote:
> +
> +    if (def->sev &&
> +        def->sev->sectype == VIR_DOMAIN_LAUNCH_SECURITY_SEV &&
> +        !supportsSEV) {
> +        VIR_DEBUG("Domain requires SEV, firmware '%s' doesn't support it", path);
> +        return false;
> +    }
> +
> +    VIR_DEBUG("Firmware '%s' matches domain requirements", path);
> +    return true;
> +}
> +
> +
> +static int
> +qemuFirmwareEnableFeatures(virQEMUDriverPtr driver,
> +                           virDomainDefPtr def,
> +                           const qemuFirmware *fw)
> +{
> +    VIR_AUTOUNREF(virQEMUDriverConfigPtr) cfg = virQEMUDriverGetConfig(driver);
> +    const qemuFirmwareMappingFlash *flash = &fw->mapping.data.flash;
> +    const qemuFirmwareMappingKernel *kernel = &fw->mapping.data.kernel;
> +    const qemuFirmwareMappingMemory *memory = &fw->mapping.data.memory;
> +    size_t i;
> +
> +    switch (fw->mapping.device) {
> +    case QEMU_FIRMWARE_DEVICE_FLASH:
> +        if (!def->os.loader &&
> +            VIR_ALLOC(def->os.loader) < 0)
> +            return -1;
> +
> +        def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_PFLASH;
> +        def->os.loader->readonly = VIR_TRISTATE_BOOL_YES;
> +
> +        if (STRNEQ(flash->executable.format, "raw")) {
> +            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> +                           _("unsupported flash format '%s'"),
> +                           flash->executable.format);
> +            return -1;
> +        }
> +
> +        VIR_FREE(def->os.loader->path);
> +        if (VIR_STRDUP(def->os.loader->path,
> +                       flash->executable.filename) < 0)
> +            return -1;
> +
> +        if (STRNEQ(flash->nvram_template.format, "raw")) {
> +            virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> +                           _("unsupported nvram template format '%s'"),
> +                           flash->nvram_template.format);
> +            return -1;
> +        }
> +
> +        VIR_FREE(def->os.loader->templt);
> +        if (VIR_STRDUP(def->os.loader->templt,
> +                       flash->nvram_template.filename) < 0)
> +            return -1;
> +
> +        if (qemuDomainNVRAMPathGenerate(cfg, def) < 0)
> +            return -1;
> +
> +        VIR_DEBUG("decided on firmware '%s' varstore template '%s'",
> +                  def->os.loader->path,
> +                  def->os.loader->templt);
> +        break;
> +
> +    case QEMU_FIRMWARE_DEVICE_KERNEL:
> +        VIR_FREE(def->os.kernel);
> +        if (VIR_STRDUP(def->os.kernel, kernel->filename) < 0)
> +            return -1;
> +
> +        VIR_DEBUG("decided on kernel '%s'",
> +                  def->os.kernel);
> +        break;
> +
> +    case QEMU_FIRMWARE_DEVICE_MEMORY:
> +        if (!def->os.loader &&
> +            VIR_ALLOC(def->os.loader) < 0)
> +            return -1;
> +
> +        def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_ROM;
> +        if (VIR_STRDUP(def->os.loader->path, memory->filename) < 0)
> +            return -1;
> +
> +        VIR_DEBUG("decided on loader '%s'",
> +                  def->os.loader->path);
> +        break;
> +
> +    case QEMU_FIRMWARE_DEVICE_NONE:
> +    case QEMU_FIRMWARE_DEVICE_LAST:
> +        break;
> +    }
> +
> +    for (i = 0; i < fw->nfeatures; i++) {
> +        switch (fw->features[i]) {
> +        case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
> +            switch (def->features[VIR_DOMAIN_FEATURE_SMM]) {
> +            case VIR_TRISTATE_SWITCH_OFF:
> +                virReportError(VIR_ERR_OPERATION_INVALID, "%s",
> +                               _("domain has SMM turned off "
> +                                 "but chosen firmware requires it"));
> +                return -1;
> +                break;
> +            case VIR_TRISTATE_SWITCH_ABSENT:
> +                VIR_DEBUG("Enabling SMM feature");
> +                def->features[VIR_DOMAIN_FEATURE_SMM] = VIR_TRISTATE_SWITCH_ON;
> +                break;
> +
> +            case VIR_TRISTATE_SWITCH_ON:
> +            case VIR_TRISTATE_SWITCH_LAST:
> +                break;
> +            }
> +            break;

Right, this is good!

> +
> +        case QEMU_FIRMWARE_FEATURE_NONE:
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S3:
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S4:
> +        case QEMU_FIRMWARE_FEATURE_AMD_SEV:
> +        case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
> +        case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
> +        case QEMU_FIRMWARE_FEATURE_LAST:
> +            break;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +
> +static void
> +qemuFirmwareSanityCheck(const qemuFirmware *fw,
> +                        const char *filename)
> +{
> +    size_t i;
> +    bool requiresSMM = false;
> +    bool supportsSecureBoot = false;
> +
> +    for (i = 0; i < fw->nfeatures; i++) {
> +        switch (fw->features[i]) {
> +        case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
> +            requiresSMM = true;
> +            break;
> +        case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
> +            supportsSecureBoot = true;
> +            break;
> +        case QEMU_FIRMWARE_FEATURE_NONE:
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S3:
> +        case QEMU_FIRMWARE_FEATURE_ACPI_S4:
> +        case QEMU_FIRMWARE_FEATURE_AMD_SEV:
> +        case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
> +        case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
> +        case QEMU_FIRMWARE_FEATURE_LAST:
> +            break;
> +        }
> +    }
> +
> +    if (supportsSecureBoot != requiresSMM) {
> +        VIR_WARN("Firmware description '%s' has invalid set of features: "
> +                 "%s = %d, %s = %d",
> +                 filename,
> +                 qemuFirmwareFeatureTypeToString(QEMU_FIRMWARE_FEATURE_REQUIRES_SMM),
> +                 requiresSMM,
> +                 qemuFirmwareFeatureTypeToString(QEMU_FIRMWARE_FEATURE_SECURE_BOOT),
> +                 supportsSecureBoot);
> +    }

This is also good. I feel tempted to suggest replacing "invalid" with
"dubious" or "questionable", but in fact your wording is clearer and
better. Outside of development, such configs can be considered invalid.

In summary:
- please fix the typo in (1),
- please fix the condition, and the debug message too, in (2),

then you can add:

Reviewed-by: Laszlo Ersek <lersek at redhat.com>

Thank you!
Laszlo

> +}
> +
> +
> +int
> +qemuFirmwareFillDomain(virQEMUDriverPtr driver,
> +                       virDomainObjPtr vm,
> +                       unsigned int flags)
> +{
> +    VIR_AUTOSTRINGLIST paths = NULL;
> +    size_t npaths = 0;
> +    qemuFirmwarePtr *firmwares = NULL;
> +    size_t nfirmwares = 0;
> +    const qemuFirmware *theone = NULL;
> +    size_t i;
> +    int ret = -1;
> +
> +    if (!(flags & VIR_QEMU_PROCESS_START_NEW))
> +        return 0;
> +
> +    if (vm->def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE)
> +        return 0;
> +
> +    if (qemuFirmwareFetchConfigs(&paths) < 0)
> +        return -1;
> +
> +    npaths = virStringListLength((const char **)paths);
> +
> +    if (VIR_ALLOC_N(firmwares, npaths) < 0)
> +        return -1;
> +
> +    nfirmwares = npaths;
> +
> +    for (i = 0; i < nfirmwares; i++) {
> +        if (!(firmwares[i] = qemuFirmwareParse(paths[i])))
> +            goto cleanup;
> +    }
> +
> +    for (i = 0; i < nfirmwares; i++) {
> +        if (qemuFirmwareMatchDomain(vm->def, firmwares[i], paths[i])) {
> +            theone = firmwares[i];
> +            VIR_DEBUG("Found matching firmware (description path '%s')",
> +                      paths[i]);
> +            break;
> +        }
> +    }
> +
> +    if (!theone) {
> +        virReportError(VIR_ERR_OPERATION_FAILED,
> +                       _("Unable to find any firmware to satisfy '%s'"),
> +                       virDomainOsDefFirmwareTypeToString(vm->def->os.firmware));
> +        goto cleanup;
> +    }
> +
> +    /* Firstly, let's do some sanity checks. If either of these
> +     * fail we can still start the domain successfully, but it's
> +     * likely that admin/FW manufacturer messed up. */
> +    qemuFirmwareSanityCheck(theone, paths[i]);
> +
> +    if (qemuFirmwareEnableFeatures(driver, vm->def, theone) < 0)
> +        goto cleanup;
> +
> +    vm->def->os.firmware = VIR_DOMAIN_OS_DEF_FIRMWARE_NONE;
> +
> +    ret = 0;
> + cleanup:
> +    for (i = 0; i < nfirmwares; i++)
> +        qemuFirmwareFree(firmwares[i]);
> +    VIR_FREE(firmwares);
> +    return ret;
> +}
> diff --git a/src/qemu/qemu_firmware.h b/src/qemu/qemu_firmware.h
> index 321169f56c..5d42b8d172 100644
> --- a/src/qemu/qemu_firmware.h
> +++ b/src/qemu/qemu_firmware.h
> @@ -21,7 +21,9 @@
>  #ifndef LIBVIRT_QEMU_FIRMWARE_H
>  # define LIBVIRT_QEMU_FIRMWARE_H
>
> +# include "domain_conf.h"
>  # include "viralloc.h"
> +# include "qemu_conf.h"
>
>  typedef struct _qemuFirmware qemuFirmware;
>  typedef qemuFirmware *qemuFirmwarePtr;
> @@ -40,4 +42,9 @@ qemuFirmwareFormat(qemuFirmwarePtr fw);
>  int
>  qemuFirmwareFetchConfigs(char ***firmwares);
>
> +int
> +qemuFirmwareFillDomain(virQEMUDriverPtr driver,
> +                       virDomainObjPtr vm,
> +                       unsigned int flags);
> +
>  #endif /* LIBVIRT_QEMU_FIRMWARE_H */
>




More information about the libvir-list mailing list