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

sd-boot: Measure initrd into TPM (attempt #2) #12716

Closed
josephlr opened this issue Jun 1, 2019 · 32 comments
Closed

sd-boot: Measure initrd into TPM (attempt #2) #12716

josephlr opened this issue Jun 1, 2019 · 32 comments
Labels
RFE 🎁 Request for Enhancement, i.e. a feature request sd-boot/sd-stub/bootctl

Comments

@josephlr
Copy link
Contributor

josephlr commented Jun 1, 2019

Describe the solution you'd like

#2587 added support for measuring the kernel command line into the TPM. This is to allow for "Measured Boot" where all components of the boot chain (firmware, bootloader, kernel, etc..) are measured into the TPM for later cryptographic attestation. However, that patch didn't include measuring a key component of the boot chain: the initrd. #6124 attempted to add this support, but @haraldh correctly pointed out that it doesn't make sense to measure the disk and then have the kernel reload the initrd from disk, as this is quite insecure.

It's worth looking at how @mjg59's Grub patches handle this. For context, the Linux kernel has two entry points:

  • EFISTUB: the standard UEFI entry point
    • This allows the kernel bzImage to masquerade as a normal EFI binary
    • It has the magic initrd command line parameter
    • Used by systemd-boot when using normal boot.c code
    • Used by Grub with chainloader
  • EFI handover:
    • Similar to the other kernel entry points, you set the bootparams struct (née Zero Page) and jump to an address.
    • Requires bootloader to load initrds into memory before jumping
    • Used by systemd-boot when using shim.c code
    • Used by (non-mainline) Grub with linuxefi.

In Matthew's patches to GRUB, the command line params can be measured into PCR 8 (like systemd-boot) and the initrds can be measured into PCR 9 (unlike systemd-boot) when the kernel is loaded with linuxefi.

My proposal is two-fold:

  1. If a systemd-boot loader entry uses linux and initrd parameters, we load the kernel using the EFI handover protocol, similar to what we already do in the stub code.
  2. If the tpm option is enabled, measure any initrds into the TPM. Which PCR would be determined by a initrd-pcrindex option, defaulting to PCR 9.

Advantages to this approach

  • Linux is always loaded the same way with systemd-boot. We would use the EFI handover entry point for the normal bootloader and the stub.
  • The PCR measurements are similar between Grub and systemd-boot. This makes it easier for users who only care about UEFI/Secure Boot/Measured Boot to switch to systemd-boot.
  • Almost all the code already exists in systemd-boot, due to the existence of the stub.

Questions for @haraldh

  • If I submitted a PR for (1) or (2), would you consider merging it?
  • Would you want the changes in (1) to be the new default, or have it be behind either a compile-time flag or an additional field in the entry file?

Describe alternatives you've considered

  1. Load initrd(s) from disk and don't measure them (current situation)
    • Not measuring the initramfs defeats the point of measuring anything, as it isn't possible to verify that the boot chain is uncompromised (an attacker can just swap out the initramfs and compromise the system).
  2. Embed initrd, cmdline, kernel in the systemd-boot EFI image
    • This is the current point of systemd-boot stub functionality. However, this is not an option if the initramfs needs to be it's own separate file. Most distros rely on the initrmfs to:
      • update out of band with the kernel
      • be customized for each user's machine
  3. Have the kernel measure the initrd(s) when when it loads them.
    • Note that the kernel would have to do this in the EFI stub code before that code jumps to the handover entry point.
    • As described above, Grub (with linuxefi support) already measures the initrd(s) into PCR 9, if we also added this functionality to the kernel, we would measure the initrd(s) twice unless we were very careful with our versions and configurations.
@poettering
Copy link
Member

it appears strange to me to not verify kernel and initrd in one go, i.e. use only https://systemd.io/BOOT_LOADER_SPECIFICATION.html#type-2-efi-unified-kernel-images

But it all boils down to: if your patch is minimal, and pretty, and convincing we can merge it, but in general I think the current simplicity (i.e. treat everything as an EFI binary, let the firmware figure out everything, consider kernel and initrd a single unit as boot loader spec type #2 images do it) is very very sexy, and complicating stuff by splitting up and adding yet another validator to all steps of the boot is really ugly in comparison.

What I don't understand is your usecase though: you want to be able measure (and thus sign I assume?) the initrd independently of the kernel because you want to update them independently. But how can that even work?

I mean, the initrd generally contains kernel modules, hence you need to update the initrd at least as often as the kernel. And the initrd cannot be really be dynamically adjusted to the host, because that would mean everybody has to have their own signing keys (or is that what you are working towards?). Which hence means the only thing you gain is that you can update the same, immutable initrd slightly more often than the kernel attached to it. But is that worth it? At least in my experience, the userspace included in the initrd changes roughly at the same time as the kernel, so why do that work?

Hence, please enlighten me, what precisely are you trying to do?

@poettering poettering added RFE 🎁 Request for Enhancement, i.e. a feature request sd-boot/sd-stub/bootctl labels Jun 18, 2019
@mjg59
Copy link
Contributor

mjg59 commented Jun 22, 2019

Measurement doesn't imply signing. The initrd can be dynamically generated locally and TPM values updated such that loading a modified initrd will block release of disk decryption keys. Further, if the dynamic aspects of the initrd are well controlled, a remote server can synthesise the measurement and verify the initrd via remote attestation even if the initrd was generated locally.

@haraldh
Copy link
Member

haraldh commented Jun 24, 2019

@josephlr : PR for (1) or (2) would be cool :)

@haraldh
Copy link
Member

haraldh commented Jun 24, 2019

not sure if this should be the default though...

@poettering
Copy link
Member

BTW, i wonder if it wouldn't be smarter to move initrd loading/validation into the EFI stub shipped along with systemd? i.e. why sd-boot instead of the stub?

@systemd systemd deleted a comment Feb 12, 2020
@systemd systemd deleted a comment Feb 20, 2020
@kowalski7cc
Copy link

I found that Fedora 33 uses initd and linux separated files when systemd-boot is installed:
image

@poettering
Copy link
Member

poettering commented Sep 20, 2021

So I think we should do this, and do it like this:

  1. Change the EFI stub we have to parse the kernel command line it loads itself, and parse out the initrd= parameter. Then, let's load the initrd in the EFI stub, measure it and remove the initrd= expression from the command line we pass on to the kernel. That way if you have a kernel image with the stub it can load the initrd as usual, but will do so in an EFI measured way. This probably a 50 line patch to src/boot/efi/stub.c.

  2. Change sd-boot to implement the LINUX_EFI_INITRD_MEDIA_GUID protocol, and then no longer pass the initrd= parameter to the EFI binary we load. The Linux kernel will inquire this protocol when looking for the initrd. It's a protocol that can provide a file to whoever it inquires it. We would implement this protocol in sd-boot and load the configured initrd that way, measuring it while doing so. This is probably a bit longer a patch, and would be to sd-boot. This way any EFI Linux kernel (i.e. even those without our EFI stub) would be able to acquire the initrd from us, and we'd measure it whenever it asks for it.

These two things together would give us complete coverage: arbitrary systemd-EFI-stub-equipped kernels invoked via any EFI loader (i.e. even non-sd-boot), and arbitrary EFI stub kernels (that do not use our systemd stub) when invoked via sd-boot.

Would be delighted to merge/review a patch for either of these approaches, and of course ideally both.

@mjg59
Copy link
Contributor

mjg59 commented Sep 21, 2021

This sounds like a solid plan - it avoids any TOCTOU issues.

@mimir-d
Copy link

mimir-d commented Dec 7, 2021

Hi. We're trying to switch to systemd-boot as well, but we're hitting the same problem with the missing measurements due to having separate initrds.

The requirements that we have are:

  1. measure kernel (preferrably into PCR9, whereas currently the measurement goes into PCR4 along with the bootloader; however, we acknowledge that this might not be possible with Load/StartImage from boot services, so this PCR index criteria is optional).
  2. measure initrd - currently not covered, as illustrated above
  3. multiple initrd - since the measurement would be done thru the LINUX_EFI_INITRD_MEDIA_GUID protocol, the implementation detail here is that LoadFile2 would present the concatenated initrds in a single buffer, but the measurements into PCR9 would be separate hashes
  4. anything else that goes into controlling the booting process needs to be measured before use (as per "measured boot" semantics) - the cmdline is already done here, there probably isn't anything more to do but specifying for completeness

At this point this comment is just about reaching consensus on the implementation details. Further on, we can provide a patch where all of this is happening. Thanks for your input

@josephlr
Copy link
Contributor Author

josephlr commented Mar 1, 2022

So looking at #20918, it seems like the Stub (but not the normal systemd-boot) uses the LINUX_EFI_INITRD_MEDIA_GUID protocol to load the initramfs.

This means that when using the Stub the following events will be measured (into the following PCRs):

Note that the actual Linux kernel isn't measured into PCR 4, as it is not loaded from disk, it's just part of the stub binary (so is measured as part of measuring the stub). See the stub implementation here.

However, when using the systemd-boot normally, we get the following measurements:

  • (PCR 4) - systemd-boot itself with type EV_EFI_BOOT_SERVICES_APPLICATION
    • This measurement is done by the firmware
  • (PCR 4) - The Linux Kernel with type EV_EFI_BOOT_SERVICES_APPLICATION
    • This measurement happens when the image is loaded (but not yet run), the actual call to the TPM is handled by firmware.
  • (PCR 8) - The kernel command line with type EV_IPL

Given this, I think doing the following would be enough to meet @mimir-d's requirements:

  • Change boot.c to load the initramfs (1 or multiple) from disk
  • Change boot.c to use LINUX_EFI_INITRD_MEDIA_GUID
  • Change linux.c to work for both the normal and stub codepaths, so that we can avoid code duplication.
    • This likely means choosing between the following boot protocols based on the kernel's support:
      • Using the EFI handover protocol
      • Using the normal EFI entry point, but with LINUX_EFI_INITRD_MEDIA_GUID.

Note that we don't actually need systemd-boot to do any more measuring itself. That will be done by the kernel.

After these changes, we would get the following measurements with systemd-boot (normal not stub):

  • (PCR 4) - systemd-boot itself with type EV_EFI_BOOT_SERVICES_APPLICATION
  • (PCR 4) - The Linux Kernel with type EV_EFI_BOOT_SERVICES_APPLICATION
  • (PCR 8) - The kernel command line with type EV_IPL
  • (PCR 9) - The kernel initramfs with type EV_EVENT_TAG

@josephlr
Copy link
Contributor Author

josephlr commented Mar 1, 2022

@mimir-d I'm going to go though your requirements, and see if my above solution meets them:

  1. measure kernel (preferrably into PCR9, whereas currently the measurement goes into PCR4 along with the bootloader; however, we acknowledge that this might not be possible with Load/StartImage from boot services, so this PCR index criteria is optional).

It's required that the kernel be initially loaded by the firmware (via a call to LoadImage). This is because we need the firmware to check that the Kernel Image complies with the Secure Boot policy. Thus, we will always get a PCR 4 measurement for the kernel binary. Having a second, duplicate measurement into PCR 8 would be of questionable use.

  1. measure initrd - currently not covered, as illustrated above

This would be handled by the kernel, provided the bootloader implements LINUX_EFI_INITRD_MEDIA_GUID

  1. multiple initrd - since the measurement would be done thru the LINUX_EFI_INITRD_MEDIA_GUID protocol, the implementation detail here is that LoadFile2 would present the concatenated initrds in a single buffer, but the measurements into PCR9 would be separate hashes

This would be handled when the bootloader loads the initrds from disk. They would just be concatenated before being passed via LINUX_EFI_INITRD_MEDIA_GUID. This would then result in a single measurement into PCR 9.

  1. anything else that goes into controlling the booting process needs to be measured before use (as per "measured boot" semantics) - the cmdline is already done here, there probably isn't anything more to do but specifying for completeness

With Kernel binary, Kernel cmdline, and initrd all measured, I think we are good here. This would mostly be a matter of documenting security properties (and making sure that future changes respect those security properties).

@anitazha
Copy link
Member

anitazha commented Mar 8, 2022

@josephlr is this something you would work on soon? I am also interested in working on a PR for this issue.

@josephlr
Copy link
Contributor Author

josephlr commented Mar 8, 2022

@anitazha I would want to get consensus around the checklist in #12716 (comment) before we start implementing.

I'm not sure how much time I'll have in Q1/Q2 to work on implementation, but I would be happy to review any changes in this space.

@poettering
Copy link
Member

@anitazha I would want to get consensus around the checklist in #12716 (comment) before we start implementing.

Check list makes sense to me.

@medhefgo
Copy link
Contributor

medhefgo commented Mar 9, 2022

Mind you, I am already adding LINUX_EFI_INITRD_MEDIA_GUID support to boot.c in #22550. So you may wanna wait for that to land or use it as a base.

@mimir-d
Copy link

mimir-d commented Jun 12, 2022

Appologies for my (very) late reply. We eventually turned to using the systemd-stub (with the embedded boot kernel, initramfs, cmdline) and measure the whole blob into PCR4 (by virtue of firmware). This would have a very infrequent lifecycle as it is reasonably stable, so it was acceptable to skip on the individual parts measurements.
It may be that @anitazha 's team may still want the initial form of requirements, should confirm.

@anitazha
Copy link
Member

I tried out #22550 some months ago and on a new enough kernel the LINUX_EFI_INITRD_MEDIA_GUID feature to measure initrd works. Our teams don't have a current need for it now that our partners switched to sd-stub, but it at least works if we decide to change to sd-boot.

@poettering
Copy link
Member

OK, let's close this. I think the automatic measurement of initrds into PCR 9 in current kernels should be enough. We can close this one here, sd-boot doesn't need to do this anymore.

@robret77
Copy link

robret77 commented May 13, 2024

Big fat warning:

Unfortunately a simple evil maid attack is possible when the kernel EFI stub is loaded by sd-boot. The attack is rooted in the fact that the kernel is doing conditional TPM measurements. It only measures the initrd when LINUX_EFI_INITRD_MEDIA_GUID / LoadFile2 is used and that can be abused.

To enable use of LoadFile2, and therefore having the kernel measure the initrd, the following kind of sd-boot entry can be used:

$ cat /boot/efi/loader/entries/intended.conf 
title   Intended Measured Linux Boot
linux   /vmlinuz
initrd  /initrd.img
options lockdown=integrity root=/dev/mapper/ubuntu--vg-ubuntu--lv ro

The hash of initrd will be measured, it will be measured exactly once. Looks good so far. TPM PCR 9 and 12 measurements (this list is complete and in order, no other measurements are done on this two PCR's):

PCR 12 (sd-boot): hash of kernel command line, prepended with "initrd=<path of initrd, using backslashes as separator> "
PCR 9 (kernel EFI stub): hash of kernel command line, prepended with "initrd=<path of initrd, using backslashes as separator> "
PCR 9 (kernel EFI stub): hash of given initrd

What would happen now, when the EFI loader is used and the kernel command line is prepended with "initrd=<path of initrd, using backslashes as separator> "?

$ cat /boot/efi/loader/entries/evilmaid.conf 
title   Evil Maid Measured Linux Boot
efi     /vmlinuz
options initrd=\initrd.img lockdown=integrity root=/dev/mapper/ubuntu--vg-ubuntu--lv ro

TPM PCR 9 and 12 measurements (this list is complete and in order, no other measurements are done on this two PCR's):

PCR 12 (sd-boot): hash of kernel command line, including "initrd=<path of initrd, using backslashes as separator> " on the left hand side
PCR 9 (kernel EFI stub): hash of kernel command line, including "initrd=<path of initrd, using backslashes as separator> " on the left hand side

The hash of the initrd is now not measured anymore. This is the only difference of all PCR registers compared to the intended boot. In fact, the evil boot measures exactly one event less, that of the hash of initrd, which is also the final measuerent in PCR 9.

To complete the evil maid, the attacker will calculate the hash of the original initrd and provide a custom initrd which extends PCR 9 with the hash of the original initrd. The measured boot is now successfully broken, as all PCR registers cointain exactly the same values as on the intended boot, but with an evil initrd. Even eventual Secure Boot protections are circumvented.

Possible mitigations (one of them is sufficient):

  • sd-boot measures the initrd before executing
  • sd-boot measures if LINUX_EFI_INITRD_MEDIA_GUID / LoadFile2 is in use before executing
  • sd-boot measures the content of all entry conf files before parsing and measure the selected entry conf file before executing (like GRUB2 in PCR 8)
  • (let the kernel measure always, not conditional)

@arvidjaar
Copy link
Contributor

custom initrd which extends PCR 9 with the hash of the original initrd

Will not it happen after exit from EFI so it will not match due to different order of events?

@robret77
Copy link

robret77 commented May 14, 2024

The order will be different, but not the PCR values. When you seal a secret into the TPM using e.g. tpm2_policypcr, only the PCR values are taken into account, not the order.

It would indeed be possible to detect this attack by using remote attestation, analyzing the order of the event log as well. But that is fully out of scope of tpm2_policypcr and out of scope for most FDE solutions as well.

To be a bit more specific, as far as I know, simply grabbing the output of tpm2_eventlog and tpm2_pcrread and analyzing it for the correct order inside the initrd is not a good idea, as tpm2_pcrread is unencrypted and can be faked by an TPM interposer / genie. Therefore a remote or local attestation has to be done which is using a cryptographical quote to attest the correctness of the event log. Then the order check can be applied. A lot of work for something that should not happen in first place.

And even then, what prevents the attacker to repackage the initrd, simply removing the code which is doing the remote or local attestation... Broken again? Yes

Only runtime remote attestation will detect this correctly, FDE can not as far as I know.

@arvidjaar
Copy link
Contributor

The order will be different, but not the PCR values.

Did you test it?

@robret77
Copy link

Yes, here are the two eventlogs (intended.yaml and evilmaid.yaml, using /v as /vmlinuz and /i as /initrd.img)

intended.yaml.txt

evilmaid.yaml.txt

@arvidjaar
Copy link
Contributor

Well, I actually meant "did you test unlocking of encrypted volume in this case". But yes, it sounds plausible. Personally I am not sure why kernel does not measure initrd from command like:

        status = efi_load_initrd_dev_path(&initrd, hard_limit);
        if (status == EFI_SUCCESS) {
                efi_info("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
                if (initrd.size > 0 &&
                    efi_measure_tagged_event(initrd.base, initrd.size,
                                             EFISTUB_EVT_INITRD) == EFI_SUCCESS)
                        efi_info("Measured initrd data into PCR 9\n");
        } else if (status == EFI_NOT_FOUND) {
                status = efi_load_initrd_cmdline(image, &initrd, soft_limit,
                                                 hard_limit);
                /* command line loader disabled or no initrd= passed? */
                if (status == EFI_UNSUPPORTED || status == EFI_NOT_READY)
                        return EFI_SUCCESS;
                if (status == EFI_SUCCESS)
                        efi_info("Loaded initrd from command line option\n");
        }

Nothing in commit message explains it. It should really be reported to the kernel.

@robret77
Copy link

When I remember correctly from the kernel mailing list, measurements are done conditionally to not break existing things too much. The kernel arrived a bit too late to the party.

@robret77
Copy link

@robret77
Copy link

robret77 commented May 14, 2024

Anyhow, with GRUB2 this attack will be detected as it requires changes to the boot config. As changes to the grub.cfg will be reflected in PCR 8 the attack is then not possible (even without measurement of the initrd itself). Wouldn't it be nice to measure all boot configs in sd-boot as well (incl. EFI blob sections and entry conf files) before parsing? That would allow sd-boot to be easily one step further as a trusted sd-boot.

I would also definitely feel better if the bootloader completely measures the next stage (vmlinuz, initrd and the cmd line) before execution, instead of having the vmlinuz measuring the initrd and cmd line while vmlinuz (better said, the vmlinuz EFI stub) is already be executed. That alone violates what the TPM is intended for, to form a chain of trust. Of course it does not break the chain necessarily, but it weakens unnecessarily it's strength a bit as it could fake itself. The boot phase is not a place where it's good to save some code or work.

BTW LOAD_FILE2 is also not available when the kernel is directly booted by an UEFI boot entry. So that is not an option as well.

@poettering
Copy link
Member

but i am not sure i follow here, i mean, sure if you only look at the kernel's own initrd measurements, and the two measurements of the kernel command line, then yeah, it's not safe. But measuring the kernel image itself is also done, so also bind against that?

@arvidjaar
Copy link
Contributor

But measuring the kernel image itself is also done, so also bind against that?

The kernel will not change so measurements will be the same.

@arvidjaar
Copy link
Contributor

Here we go: https://lore.kernel.org/linux-efi/20210615092105.288331-5-ilias.apalodimas@linaro.org/T/

Quoting

This is not what we probably want in the long run

Well, three years is a long run, so may be this should be revisited.

@robret77
Copy link

Here we go: https://lore.kernel.org/linux-efi/20210615092105.288331-5-ilias.apalodimas@linaro.org/T/

Quoting

This is not what we probably want in the long run

Well, three years is a long run, so may be this should be revisited.

I'll approach the kernel mailing list to ask for revisiting. Thanks for your input.

@robret77
Copy link

robret77 commented May 14, 2024

but i am not sure i follow here, i mean, sure if you only look at the kernel's own initrd measurements, and the two measurements of the kernel command line, then yeah, it's not safe. But measuring the kernel image itself is also done, so also bind against that?

The kernel EFI stub is not an unified kernel image (UKI) unlike the sd-stub, it's the kernel only and requires explicit measurement of the initrd. That is done with LoadFile2. Therefore a blind spot will arise in it's current state, which the evil maid can abuse when sd-boot using the EFI/chain loader or direct UEFI boot is used (without LoadFile2). It's for sure at least a kernel problem. On the other hand, it's also a responsability of the bootloader, that can't be denied. GRUB2 fixed it long time ago already and measures the initrd and configs always.

Of course someone would bind to more PCR registers then 9 and 12. Most probably it's best to bind to PCR 7 as well and sign the kernel EFI stub, the vmlinuz, with custom Secure Boot keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFE 🎁 Request for Enhancement, i.e. a feature request sd-boot/sd-stub/bootctl
Development

No branches or pull requests

10 participants