Adventures with the UEFI shim

Paul Moore paul at paul-moore.com
Mon Oct 12 01:39:26 BST 2020


On Sun, Oct 11, 2020 at 2:50 PM Peter Jones <pjones at redhat.com> wrote:
> On Thu, Oct 08, 2020 at 11:42:33AM -0400, Paul Moore wrote:
> > On Wed, Oct 7, 2020 at 3:17 PM Peter Jones <pjones at redhat.com> wrote:
> > > On Thu, Oct 01, 2020 at 03:16:08PM -0400, Paul Moore wrote:
> >
> > ...
> >
> > > > While most shim use cases involve using shim to load GRUB which in
> > > > turn loads the kernel/OS, I'm looking to use shim to boot the
> > > > kernel/OS directly.  Specifically, I want to use shim to boot a
> > > > combined kernel+initrd+cmdline EFI application using the systemd-boot
> > > > EFI stub.  Unfortunately I've been running into problems with shim's
> > > > ExitBootServices hook.  Looking at the code, it would appear that shim
> > > > expects (and requires) that some piece of the bootloader chain calls
> > > > into shim's verification protocol to authorize the kernel prior to
> > > > calling ExitBootServices.  This would normally be handled by GRUB, or
> > > > any other bootloader in the chain, but if there is no bootloader
> > > > beyond shim things fall apart.
> > > >
> > > > If this was anything but shim, the fix would be a small and
> > > > straightforward patch to replacements.c:exit_boot_services() but since
> > > > our project would eventually like to get a shim signed by Microsoft we
> > > > need to find a solution that is suitable for the rhboot/shim-review
> > > > crowd.
> > >
> > > Let me start by saying that I don't think there's a good answer here,
> > > for a lot of reasons.  The biggest one is that systemd-boot is LGPL and
> > > linux is GPL licensed, and Microsoft has stated repeatedly that they're
> > > not going to sign images that are GPL licensed.  That's a part of why
> > > shim exists in the first place.
> >
> > I think there may be a misunderstanding here, I'm not expecting
> > Microsoft to sign anything other than the UEFI shim.  The only
> > difference from the typical shim->GRUB->kernel solution is that I'm
> > thinking of skipping GRUB and booting the kernel+initrd+cmdline (with
> > the systemd-boot UEFI stub/wrapper to properly setup the
> > initrd/cmdline).
>
> Yeah, I think I was misunderstanding you.  My initial read was that
> everything was being packed in as one binary, but that now seems wrong.
> So let me ask explicitly just to avoid further confusion: are you
> actually saying you'll have one binary that's shim and second one that's
> the systemd-boot+kernel+initramfs?

Yes, plan "A" is to have two binaries: a Microsoft signed shim and a
single EFI binary which contains the kernel, initrd, and cmdline using
systemd-uboot (or similar).

Based on your responses thus far I don't think I'll have to resort to
it, but my plan B is to use three binaries: a Microsoft signed shim, a
secondary shim (to satisfy the "loader_is_participating" requirement
and do the PCR extensions), and the combined EFI kernel binary as
described above.

> > > If it weren't for that, I would say:
> > > - put a pubkey in shim like normal
> > > - sign your kernel with it
> > > - make systemd-boot call thezi shim lock protocol on the embedded kernel
> >
> > Yes, I'm already planning on steps 1 and 2 (those are needed pretty
> > much regardless of the design), I was just wondering if the UEFI shim
> > folks would be open to working on solution that doesn't require the
> > "loader_is_participating" check in shim's ExitBootServices hook.
> > Conceptually if shim has already verified the signature on the next
> > stage of the boot process that would seem to imply a certain level of
> > trust in that binary.  Would you be open to a mechanism for post-shim
> > loaders to signal shim that they do not need additional verification
> > via the shim protocol?
>
> I think that sounds like a reasonable trade-off, yes, though I do think
> we'd want to see the code during review, just to be sure.

That's fair.  Do you have any suggestions about how to skip the
"loader_is_participating" check that you would find acceptable
upstream?  As I said above, I think just removing it should be
reasonable and in keeping with the transitive trust of UEFI SB, but
I'm happy taking a different approach if that is what you guys want.

> > > So I guess the questions are:
> > >
> > > - What's your reasoning for wanting to go the systemd-boot packed
> > >   binary route?  There may be more options, depending on which parts of
> > >   that design are critical to you.
> >
> > I want to be able to verify not just the kernel, but the initrd and
> > kernel command line as well.  If I bundle the kernel+initrd+cmdline
> > together into one EFI application using the systemd-boot EFI stub (or
> > similar), I can sign the resulting EFI binary and load it directly.
> >
> > I am open to other solutions that provide similar verification of the
> > kernel+initrd+cmdline.
> >
> > > - If you can share, what does your timeline for needing something signed
> > >   look like?
> >
> > It's fuzzy.  First and foremost I need to find a solution that meets
> > our needs, and is acceptable to Microsoft and the rhboot/shim-review
> > crowd.  My hope is that we can find a solution that can be integrated
> > directly into shim, since that seems like the quickest path forward
> > (least amount of changes).  If there is no acceptable way around the
> > "loader_is_participating" issue, then my plan B looks to be to create
> > a small second stage loader[1] which simply verifies a EFI binary via
> > the shim protocol and then hands off execution.
> >
> > Parallel to the technical discussions I've been working on making sure
> > we have everything in place for the actual Microsoft signing.  Oddly
> > enough that has proved to be the easier of the two tasks.  Go figure.
>
> I think there are a couple of things we could do.  If I recall
> correctly, systemd-boot packs the kernel into a section called ".linux"?
> (For now I'll assume that's at least the right idea.)
>
> My initial thought is that we could change shim to cache the hashes of
> loaded objects on a per-section basis, and systemd-boot call
> shim->verify_buffer() the normal way, but on only that section.  Then
> we'd make verify_buffer consult the cache and see if the addresses
> passed in match a section we've already validated, and if so, hash it
> and compare.  If it matches, we call that success.

Well, my plan is to sign the combined
systemd-boot+kernel+initrd+cmdline EFI binary so all of the sections
should be verified just by virtue of the shim verifying the signature
on the combined binary.  This is why I'm looking at using the
systemd-boot (or similar approach); it's a relatively easy way to
leverage the UEFI SB/shim verification to protect the initrd and
cmdline.  The only significant change I would need would be the shim
protocol check in ExitBootServices (the "loader_is_participating"
issue) as there is no second stage bootloader in this scenario, the
second stage is the kernel/OS (we are already discussing this above).

> That being said, since this method generates top-level images that
> differ on a per-vendor/per-product basis, we probably need all the work
> regarding https://github.com/rhboot/shim/issues/223 to land before we
> could deploy such a scheme.

Unless I'm missing something, the approach I'm suggesting shouldn't
introduce any additional revocation burdens.  The shim is still built
with a set of authorization certificates that are used to authorize
the EFI kernel+initrd+cmdline, and since these certificates only exist
in the shim binary, revoking the shim binary via a hash in dbx should
be sufficient.  Yes, vendors will want their own shim builds, but how
is this any different from what we have now?

Also, if anything, removing the second stage bootloader and booting
the kernel/OS directly from shim reduces the complexity of the code
path in between shim and the kernel calling ExitBootServices.  I think
we all know that's a win.

> > [1] You may ask, why not just use GRUB?  Beyond the simple-is-good
> > argument,
>
> That's a fair point, though I would suggest putting some time into
> auditing the systemd-boot loader code; I don't know for sure how much
> they've done.

If the systemd-boot EFI stub is a problem, I'm open to providing a
bare-bones stub.  Writing a stub which sets up the kernel, initrd, and
cmdline from it's own sections before jumping to the kernel's entry
point shouldn't be too difficult.  In case it isn't clear,
systemd-boot's stub isn't special as far as I'm concerned, it's just a
convenient way to bundle everything together so it can be
signed/protected.

> > we also want some additional TPM PCR extensions beyond what
> > is commonly supplied by shim+GRUB.  The existing PCR7 measurements are
> > close, but we want more stability than PCR7 currently provides; we
> > want PCR stability across firmware changes and db/dbx might change
> > across firmware updates.  As we are not planning to use GRUB, we are
> > planning on using PCR8 or PCR9 to measure the SecureBoot variable, PK,
> > KEK, and EFI binary (kernel+initrd+cmdline) signing certificate.  The
> > other PCR will likely contain just a measurement of the EFI binary
> > itself.  In a perfect world we would also work with shim to see if we
> > could get these extensions into shim as a build time option, but it's
> > not worth discussing that if we can't find a shim-based solution to
> > the "loader_is_participating" issue we are facing.
>
> That actually sounds like something we should investigate for upstream
> as well, whether it's optional or not.  Anything to increase the PCR
> agility is a win in my book, so long as it doesn't actually /harm/ some
> other use case.  That said, obviously we don't want to break existing
> installations, so we do have to be careful with changes there in
> upstream, and make sure downstreams have a path forward.

I haven't verified the patch yet via the TPM event log and PCRs, but
I've got something written and it's pretty minor.  A lot of that is
due to the fact that it is outside of the policy enforcement code, and
is just a simple TPM measurement call.  Beyond just making it a simple
build time option, I've considered making the additional PCR
extensions conditional on if the default second stage loader is used;
the additional PCR extensions inside shim are really only useful if
you are booting directly into the kernel/OS.  However, like I said
above, if you've got strong thoughts on how this should be done, I'm
open to making changes.

> At any rate, the TPM is mostly tangential to the security boundary shim
> is there to help establish and protect, and currently shim is really
> only measuring things because the tpm doesn't work if we don't. So
> changes to tpm validation may be a good or a bad idea, but aren't
> generally something we'll care about in review.

I'm glad to hear that, I suspected this would be the case.

> > I realize this will complicate the rhboot/shim-review process, but my
> > goal is to make this loader as small as possible to ease review (and
> > likely "borrow" a lot of code from shim itself as I have no specific
> > licensing requirements on my end).
> >
> > > > As an aside, how do people do dev/test of the UEFI shim when UEFI
> > > > Secure Boot is enabled?  I originally thought I could use a Microsoft
> > > > signed shim to boot my development shim which would finally boot the
> > > > kernel/OS, but I'm running afoul of the nested EFI service hooks.  Any
> > > > advice you can provide would be appreciated.
> > >
> > > We use Qemu with OVMF (see the "edk2" packages in fedora for example
> > > builds) and enroll our own certs and self-sign everything.  The biggest
> > > "gotcha" there is to be sure you have one cert that's in 'db' and signs
> > > shim, and a completely different cert that shim trusts, which signs
> > > anything shim might be loading.  That said, my statement here is
> > > obviously not written with your attempt to use systemd-boot in mind.
> >
> > You wouldn't happen to have a doc/guide written up on this, would you?
> >  Bonus points if it uses QEMU directly and doesn't require libvirt.
> > The past two days I started playing around with using QEMU+OVMF to
> > ease UEFI development, but I haven't properly tried to get UEFI Secure
> > Boot working yet.
>
> I use a qemu qcow2 disk image that's already got an OS installed in it,
> which I set up in libvirt the first time, and then deploy with
> https://pjones.fedorapeople.org/deploy-qemu.sh .  I think the only
> meaningful thing libvirt is doing for me is file paths:
>
> /usr/share/OVMF/OVMF_CODE.fd for the firmware code comes from edk2-ovmf,
> /var/lib/libvirt/qemu/nvram/efi-test-x64-sb_VARS.fd is copied from
> edk2-ovmf's /usr/share/OVMF/OVMF_VARS.secboot.fd , and
> /var/lib/libvirt/images/efi-test-x64.qcow2 is my disk image.  You can
> set the Secure Boot security databases up by dropping your certs them as
> DER file onto /boot/efi in the booted image, then rebooting into the
> firmware menu and adding them there.
>
> Hope this helps.

Thanks.  The simple UEFI dev image I setup this week is a bit simpler,
but it's pretty similar in the important bits (pflash OVMF
firmware/vars); the only notable difference is that I build up a small
disk image with my UEFI build artifacts as part of my build process
which I then assign to the guest.  The part I'm most curious about is
enrolling the new PK/KEK/etc. in OVMF but I guess I'll just play
around with that.

--
paul moore
www.paul-moore.com



More information about the Efi mailing list