Ubuntu – How Shim verifies binaries in secure boot

bootbootloadergrub2secure-bootuefi

UEFI shim loader

shim is a trivial EFI application that, when run, attempts to open and
execute another application. It will initially attempt to do this via
the standard EFI LoadImage() and StartImage() calls. If these fail
(because secure boot is enabled and the binary is not signed with an
appropriate key, for instance) it will then validate the binary
against a built-in certificate. If this succeeds and if the binary or
signing key are not blacklisted then shim will relocate and execute
the binary.

I've been reading to understand how the verification procedure happens when the secure boot option is enabled:

Difference between vmlinuz *-generic and *-generic.efi.signed

How does Secure Boot actually work?

Managing EFI Boot Loaders for Linux: Controlling Secure Boot

I could tell now that the procedure goes like this:

Shim is first run by the machine's firmware. Now shim has to run the bootloader. What I don't understand is how shim verifies the binaries? For example, the above quoted paragraph states that shim attempts to start the other application via the standard EFI LoadImage() and StartImage() calls and if this fails shim tries to verify the binary from a builtin certificate. This built-in certificate then belongs to shim? Essentailly is that why shim is called Machine Owner Key Manager (MOK)? Because it has its own database of keys to verify binaries.

Put it simply, the machine's firmware has its own database of keys in NVRAM to verify binaries, and shim has it own database of keys to verify binaries?

After the bootloader has been verified and executed, where does the bootloader look to locate the keys of the signed kernel that it need to boot, from the firmware's database of keys for example?

Best Answer

Kaz Wolfe's answer is pretty good, but I want to emphasize and expand on a couple of points....

The last I checked, Shim basically provided a sort of parallel Secure Boot verification feature. It's intended to be used by GRUB, which is designed to launch Linux kernels that are not EFI programs. Thus, Shim registers itself with the EFI in a way that enables follow-on programs to call Shim to verify that binaries are signed. Shim does so in either of two ways:

  • Shim's built-in key -- Most Shim binaries, including the one provided as part of Ubuntu, include a built-in Secure Boot key. Ubuntu's Shim includes Canonical's public key, which validates Ubuntu's GRUB and Linux kernel. This key is therefore stored in RAM, and is rather temporary as these things go. The main point of Shim is to enable its follow-on program (GRUB) to perform a Secure Boot type verification -- but GRUB doesn't really do a Secure Boot verification per se, as described shortly. Without Shim, Canonical would need to rely on Microsoft to sign every new release of GRUB and every new Linux kernel, which would be somewhere between impractical and impossible.
  • Machine Owner Keys (MOKs) -- MOKs are basically an extension of Shim's built-in key, but they're intended for ordinary users to manipulate. You might use MOKs if you want to launch binaries that aren't signed with Canonical's key. MOKs, like the firmware's built-in Secure Boot keys, are stored in NVRAM; but they're more easily added to NVRAM, via a program called MokManager. Getting MOKs into NVRAM is still tedious enough that most people don't bother, and many people who do have problems with it; but it's easier than taking complete control of your Secure Boot subsystem, as described in my page that you referenced (Managing EFI Boot Loaders for Linux: Controlling Secure Boot).

In most cases, MOKs are not used; if you want to dual-boot Windows and Ubuntu, you'll probably do fine with the firmware's built-in keys and the key embedded in Ubuntu's Shim binary. You'd use MOKs if you want to add another Linux distribution, compile your own kernels, use a boot loader other than GRUB, use third-party kernel modules, etc.

In addition to those two sources, there's also the Secure Boot keys built into the firmware. I don't recall if Shim uses those keys. It would implicitly use them if it uses the EFI's LoadImage() and StartImage() calls (which it does, but I haven't reviewed the context for this answer). My recollection is that its own verification code does not use the firmware's Secure Boot keys when GRUB calls back to see if a kernel is signed, but I might not be remembering that correctly.

As to how Shim integrates into the Secure Boot system, the last I checked, it didn't. IIRC, to launch its follow-on program (GRUB), Shim implements its own binary-loading code, which resembles a stripped-down version of the code in the Tianocore UEFI sample implementation. This code calls Shim's own Secure Boot verification code, which checks a binary against its built-in key and the local MOK list, to launch a binary. (It may also use the firmware's own Secure Boot keys, but I'm not positive of that.) Once GRUB is loaded, it calls Shim's binary-verification function to verify the Linux kernel, which GRUB launches in its own way (not the way the EFI launches EFI programs). Thus, Shim doesn't really integrate itself into the firmware very deeply; it just makes one or two of its functions available to follow-on programs, leaving the LoadImage() and StartImage() EFI functions unchanged.

That said, EFI does provide ways to replace or supplement normal EFI system calls, and some tools do use these methods. For instance, the PreLoader program, which was a tool to do something similar to what Shim does, integrated itself more deeply into the firmware; it used EFI system calls designed to patch broken or obsolete functions to modify StartImage() so that it would check both the usual UEFI Secure Boot keys and the MOK. PreLoader has pretty well fallen by the wayside; its developers and Shim's developers have cooperated to focus on Shim rather than PreLoader as the standard Linux Secure Boot tool. AFAIK, Shim has not adopted PreLoader's deeper UEFI integration; however, it's been a while since I looked at the code very closely, so I may be out of date on this. That said....

My own rEFInd boot manager uses code that I took from the PreLoader program in order to "glue" Shim's binary verification code into the UEFI's normal verification subsystem. Thus, with rEFInd in the picture, any attempt to launch an EFI program using LoadImage() and StartImage() calls the Shim authentication code first and, if that fails, calls the standard UEFI Secure Boot authentication second. The gummiboot/systemd-boot boot manager does something similar. Both programs do this because they launch the Linux kernel by way of its EFI stub loader, which means that they rely on the EFI LoadImage() and StartImage() calls. This contrasts with GRUB, which is a full boot loader that launches the Linux kernel in its own way, so GRUB doesn't need these EFI system calls to recognize the Shim's key or the local MOK list.

I hope this helps clarify things, but I'm not sure it will. The details of how all this work are pretty messy, and it's been a while since I've dealt with them in any detail, so my own thoughts aren't as organized as they might be.