Allow Secure Boot (SB) for QEMU- and KVM-based guests

https://blueprints.launchpad.net/nova/+spec/allow-secure-boot-for-qemu-kvm-guests

Problem description

Today, Nova’s libvirt driver only has support for generic UEFI boot, but not Secure Boot (the goal of which is to: “make sure no unsigned kernel code runs on the machine”) for QEMU and KVM guests. Secure Boot protects guests from boot-time malware, and validates that the code executed by the guest firmware is trusted.

More precisely, the libvirt driver has the OVMF (the open source implementation of UEFI for virtual machines) binary file’s path hard-coded in a variable:

[...]
DEFAULT_UEFI_LOADER_PATH = {
    "x86_64": "/usr/share/OVMF/OVMF_CODE.fd",
    "aarch64": "/usr/share/AAVMF/AAVMF_CODE.fd"
}
[...]

The above only provides generic UEFI boot 1, but not Secure Boot. Also it is not robust to hardcode OVMF binary file paths this way.

This specification proposes to extend the existing support for UEFI boot in Nova’s libvirt driver to also support Secure Boot. Refer to the sections Proposed change and Work Items for what needs to be done to support the Secure Boot for KVM / QEMU guests. In this spec, we focus only the x86_64 architecture.

NB: Nova’s Hyper-V driver already has support for Secure Boot; it was added in commit: 29dab99 – “Hyper-V: Adds Hyper-V UEFI Secure Boot” 2.

Use Cases

A non-exhaustive list:

  • Protect the Nova instances being launched from boot-time malware from the guest side.

  • Secure Boot will prevent the Nova instance from running untrusted code by requiring a trusted signature on UEFI binaries. More detail on it, refer to the “Testing Secure Boot” guide here 3.

  • Secure Boot will allow trustworthy code in Nova instances to: (a) enable the Secure Boot operational mode (for protecting itself), and; (b) prevent malicious code in the guests from circumventing the actual security of the Secure Boot operational mode.

  • And, as a refresher, benefits of using OVMF are listed in the “Motivation” section of the OVMF white paper 4. And for a more detailed treatment of Secure Boot, refer to this 5.

Proposed change

To allow Secure Boot for KVM and QEMU guests, the following are the rough set of planned changes:

  • Reuse the existing Nova metadata property, os_secure_boot (added for Hyper-V support) to allow user to request Secure Boot support.

  • In the initial implemetation, Nova will only support the default UEFI keys, which will work with most distributions (Debian, Ubuntu, SUSE, Fedora, CentOS and RHEL)—as they provide a variables file (“VARS”) with default UEFI keys enrolled. (If you don’t trust the default UEFI keys, then it is equivalent to you not trusting the filesystem where your compute node is running.) If later desired, we can reuse the existing image metadata property, os_secure_boot_signature that lets you specify bootloader’s signature.

  • Make Nova use libvirt’s interface for auto-selecting firmware binaries; this was added in libvirt 5.2 6. Why?

    Problem: Today, Nova does not have a sensible way of knowing which firmware binary to select. All it sees is the firmware binary path that is hard-coded, which is ugly and fragile. Not least of all, it is non-trivial to tell whether that binary supports Secure Boot or not.

    Solution: Here is where libvirt’s firmware auto-selection comes into picture. It takes advantage of a lot of work done in QEMU and OVMF, and fixes the above mentioned problem by providing a robust interface. As in, libvirt can now pick up the correct OVMF binary, with Secure Boot (SB) and System Management Mode (SMM) enabled, with a convenient XML config:

    <os firmware='efi'>
      <loader secure='yes'/>
    </os>
    

    We will use the libvirt’s formal interface that allows auto-selecting firmware binaries—it is also far less code for Nova. And we will document that Nova will only support Secure Boot given they have MIN_LIBVIRT_SECURE_BOOT_VERSION and MIN_QEMU_SECURE_BOOT_VERSION constants.

    This libvirt feature takes advantage of QEMU’s firmware description schema 7.

  • Make Nova programatically query the getDomainCapabilities() API to check if libvirt supports the relevant Secure Boot-related features. Introduce a _has_uefi_secure_boot_support() method to check if libvirt can support the feature. This can be done by checking for the presence of efi and secure XML attributes from the output of the getDomainCapabilities() API.

  • In the initial implementation, there will be no scheduler support to isolate hosts that are not Secure Boot-capable, similar to existing basic UEFI boot support. Nova will error-out if the host hypervisor does not support Secure Boot.

Low-level background on different kinds of OVMF builds

[Thanks: Laszlo Ersek, OVMF maintainer, for the below discussion. I added, with permission, a good chunk of verbatim text from Laszlo.]

One feature that can be built into OVMF is the “Secure Boot Feature”. This is different from the operational mode called “Secure Boot” (SB). If the firmware contains the feature, then the guest can enable or disable the operational mode. If the firmware does not contain the feature, then the guest cannot enable the operational mode.

Another feature that can be built into OVMF is called “SMM” (Secure Management Mode). This means a driver stack that consists of a set of privileged drivers that run in SMM, and another, interfacing set of unprivileged drivers that only format requests for the privileged half, and parse responses from it. Once the SMM feature is built into OVMF, then SMM emulation by the QEMU platform is non-optional, it is required.

The Secure Boot Feature and the SMM feature stack are orthogonal. You can build OVMF in all four configurations. However, if you want to allow trustworthy code in your guests to enable the Secure Boot operational mode (for protecting itself), and also want to prevent malicious code in your guests from circumventing the actual security of the Secure Boot operational mode, then you have to build both features into OVMF.

NB: Different distributions ship different kinds of builds. E.g. Fedora ships both variants of OVMF firmware binaries: one without either SB or SMM, and the other with both SB or SMM. Other distributions ship different builds as well, and under different pathnames. Even if they ship an SB+SMM OVMF build, the path name for the firmware binary may be different.

OVMF binary files and variable store (“VARS”) file paths

Each distribution has its own (but slightly different) path name of OVMF:

  • SUSE:
    • package name: “qemu-ovmf-x86_64”;

    • /usr/share/qemu/ovmf-x86_64-opensuse-code.bin is the firmware binary built with SB and SMM

    • /usr/share/qemu/ovmf-x86_64-opensuse-vars.bin is the variable store template that matches the above binary

  • Fedora:
    • package name: “edk2-ovmf” (x86_64)

    • /usr/share/edk2/ovmf/OVMF_CODE.fd is a firmware binary built without either SB or SMM

    • /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd is a firmware binary built with both SB and SMM

    • /usr/share/edk2/ovmf/OVMF_VARS.fd is the variable store template that matches both of the above binaries

    • /usr/share/edk2/ovmf/OVMF_VARS.secboot.fd is the variable store template with the default UEFI keys enrolled

  • RHEL-7.6:
    • package name: “ovmf” (x86_64)

    • /usr/share/OVMF/OVMF_CODE.secboot.fd is the firmware binary, built with SB plus SMM

    • /usr/share/OVMF/OVMF_VARS.secboot.fd is the matching variable store template

  • Debian:
    • package name: “ovmf” (x86_64)

    • /usr/share/OVMF/OVMF_CODE.fd is the firmware binary built with SB plus SMM.

  • Ubuntu:
    • same as Debian

This is one of the tricky parts, but thankfully, the libvirt release 5.2 vastly simplifies the OVMF file name handling — by providing an interface to auto-select firmware (which in turn, takes advantage of the firmware description files from QEMU (provided by QEMU 2.9 and above).

Alternatives

None.

Data model impact

None.

REST API impact

None.

Security impact

With this feature, KVM- and QEMU-based Nova instances can get Secure Boot support. Thus protecting the guests from boot-time malware, and ensures the code that the firmware executes only trusted code.

Notifications impact

None.

Other end user impact

None.

Performance Impact

None.

Other deployer impact

None.

Developer impact

None.

Upgrade impact

None.

Implementation

Assignee(s)

Primary assignee:

Kashyap Chamarthy <kchamart@redhat.com>

Work Items

Taking the x86_64 architecture as an example here. The following are the work items for enabling Secure Boot support for QEMU and KVM guests:

  1. Make sure Nova configures the SMM (System Management Mode) hypervisor feature in the guest XML when Secure Boot is requested:

    <features>
      [...]
      <smm state='on'/>
    </features>
    

    Note that when using libvirt’s firmware auto-selection feature, libvirt will auto-add the SMM feature when starting the guest when SB is requested, because SMM and SB go hand-in-hand.

  2. Make sure the OVMF loader and nvram related guest XML snippet looks as follows (for a Fedora guest with Q35 machine type using an OVMF build with SMM + SB enabled):

    <os>
      <type arch='x86_64' machine='pc-q35-3.0'>hvm</type>
      <loader readonly='yes' secure='yes' type='pflash'>/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd</loader>
      <nvram template='/export/vmimages/fedora_VARS.secboot.fd'>/var/lib/libvirt/qemu/nvram/fedora_VARS.secboot.fd</nvram>
      <boot dev='hd'/>
    </os>
    

    Note that Nova doesn’t need to worry about the NVRAM store, from a file management point of view — because with libvirt’s firmware auto-selection feature, it also detects the NVRAM store associated with the firmware image, copies it into the guest’s private path, and asks the guest to use it.

    NB-1: The paths for the UEFI binary are different for different distributions — but libvirt will handle that for us.

    NB-2: Q35 machine type is mandatory for Secure Boot with OVMF.

  3. For guests to truly get Secure Boot, we need to ensure that the non-volatile store (“VARS”) file (in the above example, fedora_VARS.secboot.fd) has the default UEFI keys enrolled.

    There are two ways to achieve that. The first, use the “VARS” template file (with UEFI keys enrolled) that is shipped by your Linux distribution; this is the preferred method. The second, you can enroll the default UEFI keys in the “VARS” file, using the UefiShell.iso + EnrollDefaultKeys.efi utilities shipped by various Linux distributions (as part of their EDK2 / OVMF packages), and place it in the appropriate location. There is a tool (refer below) some Linux distributions ship which automates the key enrollment process. The tool is used as follows:

    1. Run the ovmf-vars-generator tool (adjust the parameters based on distibution) once:

      $> ./ovmf-vars-generator \
            --ovmf-binary /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd \
            --uefi-shell-iso /usr/share/edk2/ovmf/UefiShell.iso \
            --ovmf-template-vars /usr/share/edk2/ovmf/OVMF_VARS.fd \
            --fedora-version 29 \
            --kernel-path /tmp/kernel \
            --kernel-url /path/to/vmlinuz \
            template_VARS.fd
      ...
      INFO:root:Created and verified template_VARS.fd
      
    2. Reboot the guest with a pointer to a unique copy of the above template_VARS.fd. At which point, you will actually see Secure Boot enabled. Which can be verified via dmesg:

      (fedora-vm)$ dmesg | grep -i secure
      [    0.000000] secureboot: Secure boot enabled
      [    0.000000] Kernel is locked down from EFI secure boot; see man kernel_lockdown.7
      

    However, as noted earlier, no need to run the above steps manually. Most common Linux distributions (SUSE, Fedora, RHEL) already ship a “VARS” file with default UEFI keys enrolled. Debian and Ubuntu are actively working on it 8.

    If your distribution doesn’t ship a “VARS” file with default UEFI keys enrolled, here 9 is a little Python tool, ovmf-vars-generator that will automate the above three steps. This is packaged in Fedora as a sub-RPM of EDK2/OVMF, called ‘edk2-qosb’. Ubuntu has included this tool in its firmware package.

  4. Document the way to generate the above-mentioned “VARS” file using the tool ovmf-vars-generator. This tool is already shipped as a sub-package (called: ‘edk2-qosb’) of the main ‘edk2’ / OVMF in different distributions. And Ubuntu and Debian are also working to ship this script.

Dependencies

  • For the SMM (System Management Mode) feature, only the QEMU Q35 machine type is supported.

  • QEMU >=2.4 to get Secure Boot support.

  • QEMU >=4.1.0 (releases in July/August 2019) to get the firmware descriptor documents that conform to QEMU’s firmware.json specification. Here 10 are some examples of the said “firmware descriptor documents”. (NB: This does not block the spec for Train, and is a convenient-to-have.)

  • libvirt >=5.3 (releases in May 2019) for the firmware auto-selection feature and the ability to query the availability of efi 11 firmware via the getDomainCapabilities() API.

Testing

This feature should be possible (assuming the earlier-mentioned minimum libvirt and QEMU versions are available) to test in the upstream gating environment. Where the Nova instance should be able to boot a KVM guest with Secure Boot (using OVMF), and verify in dmesg that Secure Boot is actually in effect.

Documentation Impact

Document how to boot x86_64 Nova instances with Secure Boot for QEMU and KVM guests using OVMF. And update Glance’s “Useful image properties” documentation 12.

References

1

The blueprint that added initial support for booting from a UEFI image: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/boot-from-uefi.html

2

https://specs.openstack.org/openstack/nova-specs/specs/ocata/implemented/hyper-v-uefi-secureboot.html

3

https://wiki.ubuntu.com/UEFI/SecureBoot/Testing

4

The OVMF whitepaper: http://www.linux-kvm.org/downloads/lersek/ovmf-whitepaper-c770f8c.txt

5

An overview of Secure Boot: http://www.rodsbooks.com/efi-bootloaders/secureboot.html

6

The libvirt feature that allows auto-selection of firmware: https://libvirt.org/git/?p=libvirt.git;a=commitdiff;h=1dd24167b (“news: Document firmware autoselection for QEMU driver”)

7

QEMU’s firmware schema file that describes the different uses and properties of virtual machine firmware: https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/firmware.json

8

Refer to the first point: “debian/patches/enroll-default-keys.patch: Build EnrollDefaultKeys.efi to provide an automated way of injecting Microsoft signing keys in VMs that need them.” – https://launchpad.net/ubuntu/+source/edk2/0~20190309.89910a39-1ubuntu1

9

A tool to generate OVMF variables file with default Secure Boot keys enrolled – https://github.com/puiterwijk/qemu-ovmf-secureboot/

10

The EDK2 firmware descriptor files are located here: https://git.qemu.org/?p=qemu.git;a=tree;f=pc-bios/descriptors. E.g. the descriptor for “UEFI firmware for x86_64, with Secure Boot and SMM”: https://git.qemu.org/?p=qemu.git;a=blob;f=pc-bios/descriptors/50-edk2-x86_64-secure.json;

11

The BIOS-related libvirt guest XML attributes: https://libvirt.org/formatdomain.html#elementsOSBIOS

12

https://docs.openstack.org/glance/rocky/admin/useful-image-properties.html

History

Revisions

Release Name

Description

Train

Introduced