On Thu, Oct 06, 2022 at 10:45:40AM -0400, Robbie Harwood wrote: > Daniel P. Berrangé <berrange@xxxxxxxxxx> writes: > > > The way grub has to write its entire grub.conf into the TPM PCRs is > > totally impractical for anyone wishing to maintain attestation > > policies to verify the OS boot state from the TPM eventlog. > > So this has been mentioned in several places, but no one in grub > development has actually seen this problem articulated out, which makes > me worry that it's spreading FUD. Could you write up a bug report or > something concrete? I've not filed a bug report, as to I tended to view it as an inherant result of the goals of grub to be a flexible &configurable boot environment. Let me try to explain in more detail here though... Right now the default way more or less any Linux distro does SecureBoot is not too useful, since only the kernel is signed by the vendor. The content of both the initrd and cmdline is local to the installation. Mostly I'd say it solves the problem of enabling a Linux distro to boot in an environment where SecureBoot is active. It doesn't solve the problem of enabling the booted OS to be verified, or at least not in a manner that is practical to use for unknowledable users. Despite this we still have functionality that can tie LUKS volume key unlock to the state of a specific set of TPM PCRs, such disks are unlocked if-and-only-if the OS is launched in the expected configuration. This is only secure, if everything that can influence the configuration is covered by the PCRs we're using for unlock policy. We need PCRs to cover at minimum 1. Machine firmware 2. Bootloader(s) 3. Bootloader configuration 4. Booted kernel 5. Booted initrd 6. Booted cmdline Item 1 is OK-ish. It won't change unless you upgrade your firmware, so is relatively stable, though fwupd has made this more dynamic than in the past. Items 2 & 4 are OK because Microsoft signs shim, and the OS vendor signs grub and their kernels. So we can rely on the fact that if SecureBoot is signalled enabled by the relevant PCR, the bootloaders(s) & kernel are trusted (within scope of the SecureBoot keys that are enrolled). Item 5 and 6 are a problem, because as mentioned thse are not signed by the OS vendor with their secureboot key. The PCRs reflect their contents, but the expected PCR digests will change any time the initrd/cmdline are updated, meaning the LUKS keys needs to be re-sealed. One practical approach to this problem is to use prebuilt unified kernel images, such that the OS vendor's secureboot signature covers the kernel,initrd and cmdline. Verification now merely involves checking the PCR for SecureBOot state. The flipside is that we loose flexibility that exists today with per-host generated initrd/cmdline. This may matter for some scenarios (bare metal) but not for others (most VMs). Item 3 is a problem. Grub is highly configurable, via grub.conf, and its contents are tuned for each install. We need to validate any parts of grub.conf that applied to the current boot state. On a fairly typical VM the PCR eventlog recording the bootloader configuration contains something that looks similarish to: (hd0,gpt15)/EFI/ubuntu/grub.cfg grub_cmd: search.fs_uuid c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a root grub_cmd: set prefix=(hd0,gpt1)/boot/grub (hd0,gpt1)/boot/grub/x86_64-efi/command.lst (hd0,gpt1)/boot/grub/x86_64-efi/fs.lst (hd0,gpt1)/boot/grub/x86_64-efi/crypto.lst (hd0,gpt1)/boot/grub/x86_64-efi/terminal.lst grub_cmd: configfile (hd0,gpt1)/boot/grub/grub.cfg (hd0,gpt1)/boot/grub/grub.cfg grub_cmd: [ -s (hd0,gpt1)/boot/grub/grubenv ] (hd0,gpt1)/boot/grub/grubenv grub_cmd: set have_grubenv=true grub_cmd: load_env (hd0,gpt1)/boot/grub/grubenv grub_cmd: [ = 2 ] grub_cmd: [ = 1 ] grub_cmd: [ ] grub_cmd: set default=0 grub_cmd: [ xy = xy ] grub_cmd: menuentry_id_option=--id grub_cmd: export menuentry_id_option grub_cmd: [ ] grub_cmd: terminal_input console grub_cmd: terminal_output console grub_cmd: [ = 1 ] grub_cmd: [ xy = xy ] grub_cmd: set timeout_style=hidden grub_cmd: set timeout=0.1 grub_cmd: [ -n true ] grub_cmd: [ -n ] grub_cmd: set initrdless_boot_fallback_triggered=0 grub_cmd: save_env initrdless_boot_fallback_triggered grub_cmd: set menu_color_normal=white/black grub_cmd: set menu_color_highlight=black/light-gray grub_cmd: set partuuid=bf817bdf-6a3a-4221-8edb-2c1ca7c5537f grub_cmd: [ != 1 ] grub_cmd: [ -e (hd0,gpt1)/boot/grub/gfxblacklist.txt ] grub_cmd: hwmatch (hd0,gpt1)/boot/grub/gfxblacklist.txt 3 grub_cmd: [ = 0 ] grub_cmd: set linux_gfx_mode=keep grub_cmd: export linux_gfx_mode grub_cmd: menuentry Ubuntu --class ubuntu --class gnu-linux --class gnu --class os --id gnulinux-simple-c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a { if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi grub_cmd: submenu Advanced options for Ubuntu --id gnulinux-advanced-c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a { if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi grub_cmd: menuentry Ubuntu 21.04 (21.04) (on /dev/sda1) --class ubuntu --class gnu-linux --class gnu --class os --id osprober-gnulinux-simple-c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a { grub_cmd: submenu Advanced options for Ubuntu 21.04 (21.04) (on /dev/sda1) --id osprober-gnulinux-advanced-c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a { grub_cmd: set timeout_style=menu grub_cmd: [ 0.1 = 0 ] grub_cmd: menuentry UEFI Firmware Settings --id uefi-firmware { grub_cmd: [ -f (hd0,gpt1)/boot/grub/custom.cfg ] grub_cmd: [ -z (hd0,gpt1)/boot/grub -a -f (hd0,gpt1)/boot/grub/custom.cfg ] grub_cmd: setparams Ubuntu grub_cmd: recordfail grub_cmd: set recordfail=1 grub_cmd: [ -n true ] grub_cmd: [ -z ] grub_cmd: save_env recordfail grub_cmd: load_video grub_cmd: [ xy = xy ] grub_cmd: insmod all_video grub_cmd: gfxmode keep grub_cmd: set gfxpayload=keep grub_cmd: [ keep = keep ] grub_cmd: set vt_handoff=vt.handoff=7 grub_cmd: insmod gzio grub_cmd: [ xefi = xxen ] grub_cmd: insmod part_gpt grub_cmd: insmod ext2 grub_cmd: set root=hd0,gpt1 grub_cmd: [ xy = xy ] grub_cmd: search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt1 --hint-efi=hd0,gpt1 --hint-baremetal=ahci0,gpt1 c73d8355-1ac9-41b4-8edf-c1c1a9d5bd6a grub_cmd: [ = 1 ] grub_cmd: echo GRUB_FORCE_PARTUUID set, attempting initrdless boot. grub_cmd: linux /boot/vmlinuz-5.11.0-1008-gcp root=PARTUUID=bf817bdf-6a3a-4221-8edb-2c1ca7c5537f ro scsi_mod.use_blk_mq=Y ima_hash=sha256 console=ttyS0 panic=-1 grub_cmd: initrdfail grub_cmd: [ -n true ] grub_cmd: [ -n bf817bdf-6a3a-4221-8edb-2c1ca7c5537f ] grub_cmd: [ -z ] grub_cmd: set initrdfail=1 grub_cmd: [ -n ] grub_cmd: save_env initrdfail This is a really challenging eventlog when trying to validate the PCR state is matching some expected "known good" state. Given a single grub.conf, you get both a huge number of entries for every boot, plus a combinatorial expansion of possible entries that would be considered valid for a given install over time as kernels and/or grub itself are updated. A tiny change to the way grub2-mkconfig could result in a very different PCR eventlog, but which is semantically identical to what came before. Thus any attempt to validate the the grub.conf PCR eventlog, as it exists in typical distro deployments today, is going to be both complex and fragile, which is a bad combination. If we compare this to what is done when booting a unified kernel image with sd-boot + sd-shim. It has the fortune of being designed for a pretty narrow use case, thus not needing to address legacy non-EFI environments, and excluding much of the functionality that grub aims to provided. Its operation is also designed to be almost zero-conf - the loader.conf has just a handful of possible entries, of which none need to be verified directly, as they dont' affect what is boot, just what the sd-boot UI does (possible exception with the 'random-seed-mode' opt). Rather than being a bootloader, it is better considered to be a boot menu. It just lets the user pick between a selection of kernel images. If all the images are using unified kernel image format and signed by the OS vendor (or whatever SB cert is trusted), there is nothing about the bootloader config that needs verifying. All that's required is verifying that the bootloader was indeed sd-boot, and then verifying that SecureBoot was enabled with the desired cert enrolled. I guess in fairness to grub, maybe it is possible to achieve something similar if one were to have a cut down grub that had nearly all its functionality disabled, such that it merely became a boot menu for selecting between auto-detected unified kernel images found via the BootLoaderSpec. I guess I'd summarize the overall goal as being to find a way to verify the boot state in a way that does not depend on any local installation specific data, beyond what physical firmware is installed. The ultimate result being to be able to verify solely based on the PCR recording the machine firmware, the Secure Boot flag state and which Secure Boot certs were associted with the loaded image(s). Realistically you likely need to verify which bootloader was used, unless the OS vendor has different signing keys for each bootloader they distribute. Beyond that, as little as possible, ideally nothing as it risks creating combinatorial expansions of state to keep track of. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :| _______________________________________________ devel mailing list -- devel@xxxxxxxxxxxxxxxxxxxxxxx To unsubscribe send an email to devel-leave@xxxxxxxxxxxxxxxxxxxxxxx Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedoraproject.org/archives/list/devel@xxxxxxxxxxxxxxxxxxxxxxx Do not reply to spam, report it: https://pagure.io/fedora-infrastructure/new_issue