Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RfC] OvmfPkg/AmdSev: add support for systemd-boot and secure boot #6537

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

kraxel
Copy link
Member

@kraxel kraxel commented Dec 13, 2024

  • OvmfPkg: Add SdBootDxe
  • OvmfPkg/PlatformBootManagerLib: setup boot entry for systemd-boot
  • OvmfPkg/AmdSev: whitespace fixup
  • OvmfPkg/AmdSev: add support for system-boot to amdsev image
  • OvmfPkg/AmdSev: add secure boot support
  • OvmfPkg/AmdSev/BlobVerifierLibSevHashesConstructor: add logging

Description

The AmdSev build variant is a restrictively configured firmware build
for confidential computing. It will not load efi binaries from
storage devices or network. It is intended to be used in one of two
operating modes instead:

  • Direct kernel boot. The hashes of kernel + initrd are passed to the
    firmware in a special page (and that page is part of the launch
    measurement). This makes sure the VM actually runs what it is
    supposed to run, and there is proof for that.

  • Launch the grub boot loader included in the firmware image. The idea
    is that grub supports booting from encrypted storage, so the
    filesystem encryption can be used to ensure the integrity of the
    confidential virtual machine.

The former works fine, the latter never really took off and is kind-of
stuck in research experiment stage. Meanwile the world is moving into
another direction: Instead of booting from encrypted filesystems use
UKIs (unified kernel image) and secure boot and measured boot to ensure
integrity of linux kernel and initrd.

This patch series offers a alternative approach: It allows to compile
in the systemd-boot bootloader instead of grub. systemd-boot supports
loading UKIs from the ESP. One of the advantages of a UKI is that the
secure boot signature covers both kernel and initrd (instead of only the
kernel in traditional setups), thereby plugging one of the big holes in
linux secure boot support.

The patch series also adds secure boot support to the AmdSev build.

Known issues

SBAT revocation does not work. It is handled by shim.efi on linux
systems, which is not present in the boot path here.

Testing instructions

Install systemd-boot. On Fedora dnf install -y systemd-boot-unsigned
can be used.

Compile OvmfPkg/AmdSev/AmdSevX64.dsc with
-D SECURE_BOOT_ENABLE=TRUE and -D SD_BOOT_ENABLE=TRUE.

Fetch the Fedora 41 UKI cloud image.

Set a root password for the image:

virt-customize -a Fedora-Cloud-Base-UEFI-UKI-41-1.4.x86_64.qcow2 --root-password password:${secret}

Install virt-firmware.
Available on PyPI, your linux distro might also have packages.

Enroll the secure boot keys to the firmware image, make sure to add
the fedora secure boot signing keys:

virt-fw-vars \
  --input Build/AmdSev/DEBUG_GCC5/FV/OVMF.fd \
  --output Build/AmdSev/DEBUG_GCC5/FV/OVMF.secboot.fd \
  --secure-boot \
  --enroll-generate="chuck norris was here" \
  --no-microsoft \
  --distro-keys fedora \
  --print --verbose

Boot:

qemu-system-x86_64 \
  -enable-kvm -cpu host \
  -M q35 -m 4G -nographic \
  -bios Build/AmdSev/DEBUG_GCC5/FV/OVMF.secboot.fd \
  -drive if=none,id=disk,format=qcow2,file=Fedora-Cloud-Base-UEFI-UKI-41-1.4.x86_64.qcow2 \
  -device virtio-scsi-pci \
  -device scsi-hd,drive=disk,bootindex=1

Login, use bootctl to verify secure boot is active.

This allow adding the systemd-boot bootloader to OVMF firmware images.

Signed-off-by: Gerd Hoffmann <[email protected]>
The AmdSev build includes a pre-build binary of the grub boot loader.
This patch allows to include systemd-boot instead.

Use '-D SD_BOOT_ENABLE=TRUE' build option to include systemd-boot
instead of grub.

Signed-off-by: Gerd Hoffmann <[email protected]>
Add secure boot option to AmdSev build.

The EFI variable store lives in normal memory and is not protected
against manipulation at runtime (due to lack of SMM mode emulation
support in CC modes).  The variable store is not persistent though,
it will be re-initialized from the firmware image on reset, so
manipulations can not compromise secure boot on next boot.  This is
similar to the IntelTdx build variant.

Signed-off-by: Gerd Hoffmann <[email protected]>
Temporary debug patch (the amdsev build crashes when running under svsm).

Signed-off-by: Gerd Hoffmann <[email protected]>
@kraxel kraxel changed the title OvmfPkg/AmdSev: add support for systemd-boot and secure boot [RfC] OvmfPkg/AmdSev: add support for systemd-boot and secure boot Dec 13, 2024
@kraxel
Copy link
Member Author

kraxel commented Dec 13, 2024

Paging @osteffenrh @tlendacky @berrange @vittyvk @poettering for comments.

@osteffenrh
Copy link
Contributor

Tested it: works.
Thanks, this is a nice improvement.

Can we have a way to specify the location of the sdboot efi file? So we don't always need to install it system-wide.

Also, I still get

- Prebuild Start -

Can't find grub mkimage

even with -D SD_BOOT_ENABLE=TRUE, which should disable grub, IIUC.

@vittyvk
Copy link

vittyvk commented Dec 13, 2024

IMO getting rid of grub in all things CVM is certainly a good idea :-)

What I'm wondering is what's the difference between including sd-boot and including shim. If we want to have boot menu in the change, then sd-boot can be loaded (and measured) from ESP as well. You'll have to use BOOTX64.CSV or rename sd-boot binary to grubx64.efi (sic!) but it is possible. As an advantage, including shim will provide working SBAT mechanism.

Also, if we want to rely on measured boot, we need a trusted vTPM. It's OK if our vTPM is stateless and implemented as part of firmware which gets into launch measurements but it's not OK if the vTPM is implemented outside of the guest.

Finally, would it be possible to make the mechanism flexible? E.g. make it possible to include a 'virtuial ESP' in the firmware and this 'virtual ESP' can have anything, e.g. full shim -> sd-boot -> UKI chain. This way we can get reliable launch measurements covering everything without the need to have a vTPM. I understand this is a significant change to the idea of the PR, basically, I'm wondering if such 'flexible' approach would make sense or not.

@berrange
Copy link

The AmdSev build variant is a restrictively configured firmware build for confidential
computing. It will not load efi binaries from storage devices or network. It is
intended to be used in one of two operating modes instead:

* Direct kernel boot.  The hashes of kernel + initrd are passed to the
  firmware in a special page (and that page is part of the launch
  measurement).  This makes sure the VM actually runs what it is
  supposed to run, and there is proof for that.

* Launch the grub boot loader included in the firmware image.  The idea
  is that grub supports booting from encrypted storage, so the
  filesystem encryption can be used to ensure the integrity of the
  confidential virtual machine.

The former works fine, the latter never really took off and is kind-of
stuck in research experiment stage. Meanwile the world is moving
into another direction: Instead of booting from encrypted filesystems
use UKIs (unified kernel image) and secure boot and measured boot
to ensure integrity of linux kernel and initrd.

Conceptually there were significant issues with the embedded grub idea.
EFI firmware is something normally provided by the hypervisor platform/
vendor, while the bootloader is something normally provided by the guest
OS vendor. Taking the guest OS bootloader and embedding it in the firmware
is thus mixing components from two distinct worlds.

When you further consider grub as a specific impl it gets worse, because
most of the major OS vendors have alot of downstream patches on top of
their grub base version. IOW, if the firmware provide ships an embedded
grub, we cannot have confidence that the behaviour will be compatible
with that expected by the guest OS and its grub.cfg handlnig.

Thus overall I tend to view the embedded grub as "neat trick" but not
something that could be considered supportable.

This patch series offers a alternative approach: It allows to compile
in the systemd-boot bootloader instead of grub. systemd-boot
supports loading UKIs from the ESP. One of the advantages of a
UKI is that the secure boot signature covers both kernel and initrd
(instead of only the kernel in traditional setups), thereby plugging
one of the big holes in linux secure boot support.

Considering sd-boot instead of grub addresses some of the big issues
with grub. sd-boot is a very simple program, with no huge vendor
forks, nor complicated configuration files to deal with. So comparatively
that's a step forward of course.

If the proposal is for sd-boot to simply load UKIs off the (unencrypted)
ESP, I'm left wondering why that is better than allowing EDK2 to just
do this itself ? There's nothing special about UKIs, they're just a signed
PE file which EDK2 already knows how to load off the ESP - if the AmdSev
build hadn't disabled that ability. If we want this feature, why not just
re-enable it in the EDK build instead of going to the trouble of embedding
sd-boot ?

@kraxel
Copy link
Member Author

kraxel commented Dec 13, 2024

What I'm wondering is what's the difference between including sd-boot and including shim. If we want to have boot menu in the change, then sd-boot can be loaded (and measured) from ESP as well. You'll have to use BOOTX64.CSV or rename sd-boot binary to grubx64.efi (sic!) but it is possible. As an advantage, including shim will provide working SBAT mechanism.

One significant difference is that shim -> UKI requires a working EFI variable store (which we don't have right now for qemu CVMs).

sd-boot -> UKI works without that.

Finally, would it be possible to make the mechanism flexible? E.g. make it possible to include a 'virtuial ESP' in the firmware and this 'virtual ESP' can have anything, e.g. full shim -> sd-boot -> UKI chain.

edk2 has a pseudo filesystem used for direct kernel boot (i.e. qemu -kernel vmlinuz.efi). I have some pending patches adding shim support to the logic. Possibly we can piggyback on that, i.e. make things work simliar to qemu -shim shim.efi -kernel sdboot.efi or qemu -shim shim.efi -kernel uki.efi, except that the binaries are included in the firmware image instead of being loaded from qemu. For the UKI firmware image size constrains are problematic though.

I understand this is a significant change to the idea of the PR, basically, I'm wondering if such 'flexible' approach would make sense or not.

It's RfC for a reason. Let ideas and comments flow ;)

@kraxel
Copy link
Member Author

kraxel commented Dec 13, 2024

If the proposal is for sd-boot to simply load UKIs off the (unencrypted) ESP, I'm left wondering why that is better than allowing EDK2 to just do this itself ? There's nothing special about UKIs, they're just a signed PE file which EDK2 already knows how to load off the ESP - if the AmdSev build hadn't disabled that ability. If we want this feature, why not just re-enable it in the EDK build instead of going to the trouble of embedding sd-boot ?

That essentially boils down to the question what the long-term fate of the AmdSev build should be. Do we actually need that? If we allow the AmdSev build simply fetch the bootloader from disk the gap to the standard OVMF build just became a bit smaller. As far I know the remaining differences are:

  • Hash verification of direct kernel boot kernel+initrd is done by AmdSev only.
  • AmdSev supports passing secrets to the guest.
  • AmdSev has no network support.

The OvmfPkg code base has envolved quite a bit since the initial merge of the AmdSev code. There are ways to easily check what mode OVMF runs in (via PcdConfidentialComputingGuestAttr) now. So having the normal build behave in different ways depending on the environment is easier now. Folding the AmdSev functionaliy into the normal build might be the better long-term strategy ...

@tlendacky
Copy link
Contributor

Also adding @fitzthum

@berrange
Copy link

berrange commented Dec 13, 2024

That essentially boils down to the question what the long-term fate of the AmdSev build should be. Do we actually need that?

The design choices in the AmdSev build were to a significant extent dictated by limitations of the SEV/SEV-ES technology it was initially targetting. I'd consider SEV/SEV-ES to be dead-end technologies, so we should primarily be thinking about what choices make most sense in a SEV-SNP context. I think the ideal would be to consider the AmdSev build to be a legacy /and/or niche thing mostly just for SEV/SEV-ES (or minimalizst SEV-SNP without SVSM), and aim for SEV-SNP w/ SVSM to use a normal OVMF build feature set.

@tlendacky
Copy link
Contributor

One significant difference is that shim -> UKI requires a working EFI variable store (which we don't have right now for qemu CVMs).

Do you mean a persistent variable store? Because EFI variable store should work under CVMs, it is just in memory and not persistent for SNP.

@berrange
Copy link

Do you mean a persistent variable store? Because EFI variable store should work under CVMs, it is just in memory and not persistent for SNP.

For certain definitions of "work" :-) eg With shim, if it finds no boot entries, it'll populate boot entries based on the CSV file in the ESP, and then trigger a machine reset, expecting that the boot entries it just wrote will still exist. We can't do resets in CVMs without re-creating the QEMU VM, and thus loosing the boot entries shim just wrote. So basically shim ends in a forever boot loop.

IOW, from POV of real world application expectations, 'working EFI var store', implies a var store that is persistent across cold boots.

@fitzthum
Copy link
Contributor

@kraxel @berrange

Slightly tangential to the PR, but hopefully useful: Measured direct boot, and the AmdSev package is still useful for SNP (we use it for confidential containers, for instance), but there is a path to getting rid of the AmdSev package.

The AmdSev package was created partly out of a faulty assumption that the measured direct boot approach could only work with a separate firmware build. The idea was that this alternate firmware build would guarantee a secure startup or it would crash and that since it was a separate build the measurement of the binary would guarantee this behavior. While this works, it could potentially also work with a standard OVMF build.

A few months ago I revised the measured direct boot code a little bit so that it is simply triggered when the kernel hashes table exists. If the table exists, the firmware will crash if the measurements do not match. If the table does not exist, the boot will continue as normal. The existence of the hash table changes the measurement, which is enough to prove the properties of the boot to a verifier.

Currently this is only supported in the AmdSev package, but it seems like it would work fine in OVMF, which would open the door to removing AmdSev. The question is how we could provide similar TCB guarantees with OVMF, such as not exposing the shell.

@berrange
Copy link

What I'm wondering is what's the difference between including sd-boot and including shim. If we want to have boot menu in the change, then sd-boot can be loaded (and measured) from ESP as well. You'll have to use BOOTX64.CSV or rename sd-boot binary to grubx64.efi (sic!) but it is possible. As an advantage, including shim will provide working SBAT mechanism.

One significant difference is that shim -> UKI requires a working EFI variable store (which we don't have right now for qemu CVMs).

sd-boot -> UKI works without that.

IIUC, shim uses the varstore for two purposes. First so "fallback" can writing boot entries & trigger a reboot to launch the real shim & OS boot image. Second so that it can write out the distro's secureboot certs, which are needed to verify the bootloader / old kernel images / UKIs.

It seems like we still need shim to be there to provide the distro secureboot certs that 'sd-boot' will need in order to verify the UKI.

The only way around that is to have something on the host, either be told what certs to pre-enroll, or probe the disk image to auto-detect the certs. If the latter, then that thing on the host could alternatively locate the BOOT CSV file, pre-populate the boot entry(s), and then shim can work as normal. Or if it is told the distro, it doesn't need the BOOT CSV file, it can just blindly populate /EFI/<distro>/shimx64.efi, which would avoid need for persistent varstore for shim IIUC.

@berrange
Copy link

Currently this is only supported in the AmdSev package, but it seems like it would work fine in OVMF, which would open the door to removing AmdSev. The question is how we could provide similar TCB guarantees with OVMF, such as not exposing the shell.

If OVMF can detect that it is within a confidential VM with CPUID, then it could auto-disable the shell.

@kraxel
Copy link
Member Author

kraxel commented Dec 16, 2024

Do you mean a persistent variable store? Because EFI variable store should work under CVMs, it is just in memory and not persistent for SNP.

Yes. Kernel update process for the Fedora UKI cloud image linked in the description depends on persistent BootNNN + BootOrder + BootNext variables.

@kraxel
Copy link
Member Author

kraxel commented Dec 16, 2024

For certain definitions of "work" :-) eg With shim, if it finds no boot entries, it'll populate boot entries based on the CSV file in the ESP, and then trigger a machine reset, expecting that the boot entries it just wrote will still exist.

There is a workaround for that: The reset thing can be configured with an EFI variable. But, yes, that is one of the extra loops you have to hoop through if the variable store is not persistent.

@kraxel
Copy link
Member Author

kraxel commented Dec 16, 2024

The only way around that is to have something on the host, either be told what certs to pre-enroll, or probe the disk image to auto-detect the certs.

The instructions above do the former (enroll fedora cert into db).
The later should be possible too (check shim.efi on install media, extract certificates, enroll them in db and dbx).
The tricky part here is distro signing cert rotation though ...

@kraxel
Copy link
Member Author

kraxel commented Dec 16, 2024

Currently this is only supported in the AmdSev package, but it seems like it would work fine in OVMF, which would open the door to removing AmdSev. The question is how we could provide similar TCB guarantees with OVMF, such as not exposing the shell.

The approach taken by OvmfPkg/IntelTdx is to split the DXE firmware volume into two, and the binaries in the second firmware volume are not loaded in case CC is active. So everything you don't want use in a CC guest is moved over. The shell is the obvious first candidate. The network stack too. But also things like VirtioRngDxe (in a CC guest you want use RngDxe instead because that uses the rdrand cpu instruction instead of asking the host for randon numbers).

I think it makes sense to reuse the idea for the normal OVMF build.


[Binaries.AA64]
PE32|/usr/lib/systemd/boot/efi/systemd-bootaa64.efi|*

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kraxel Can you also add support for Binaries.LOONGARCH64 (systemd-bootloongarch64.efi)? I will be able to do platform testing for you, Tks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants