[personal profile] mjg59
Shim is the first stage bootloader we've been implementing for supporting Secure Boot. It's called Shim because it serves as an object to fit the gap between the Microsoft trust root and our own trust root. As originally envisaged it would do nothing other than load and execute appropriately signed binaries, but it's got a little more complicated than that now. It is, however, basically feature complete at this point - I don't expect it to grow significantly further.

(For those of you following along at home, the Shim source code I'm talking about is here)

First, it checks whether the user has disabled local signature verification in check_mok_sb(). This is done by reading the MokSBState UEFI variable and verifying that it has the correct variable attributes, ensuring that it was created in the trusted boot services environment. If so, and if it's set appropriately, signature verification is disabled for the rest of Shim. Shim then pauses for a couple of seconds while displaying a message indicating that it's running in insecure mode.

Next, Shim checks whether the user has set any variables that indicate that a Mok request has been made in check_mok_request(). If so, it launches MokManager. The mechanism by which this is done is described later.

Shim then copies the Mok key database into a runtime variable, which makes it accessible to the kernel (mirror_mok_list()). The kernel can then use the Mok keys when performing module signature verification. Next, as the last step before launching the next stage bootloader, Shim installs a UEFI protocol that permits other applications to call back into Shim. We're now ready to branch off from Shim into the actual bootloader.

To do this, Shim has to do a few things. First, we need to get the bootloader off disk. This involves finding Shim's path, which we can do by opening the loaded image protocol on Shim's image handle, turning that into a string, walking backwards along it until we find a directory separator, stripping the filename and appending the path to the second stage bootloader. This is then turned back into a binary device path, all of this happening in generate_path(). The binary is then read off disk in load_image(), using the simple filesystem protocol. We open the device, open a reference to the file, check how large the file is, allocate a buffer to hold the file and then read it off disk into that buffer. Everything so far has been fairly straightforward, but now things get more complicated.

handle_image() is the real meat of Shim. First it has to examine the header data in read_header(), copying the relevant bits into a context structure that will be used later. Some basic sanity checks on the binary are also performed here. If we're running in secure mode (ie, Secure Boot is enabled and we haven't been toggled into insecure mode) we then need to verify that the binary matches the signature and hasn't been blacklisted.

verify_buffer() has to implement the Microsoft Authenticode specification, which details which sections of the binary have to be hashed and in which order. The actual hashing is done in generate_hash(), which calls into the crypto code to add each binary section to the hashed data. The comments in generate_hash() describe what's going on there clearly enough. The crypto code itself is a copy of the crypto library extracted from the Tiano source tree. It's a relatively thin layer on top of OpenSSL, so the code's been fairly well tested.

Once we have the hash, we need to verify the signature. First, we ensure that the Mok database hasn't been modified from the OS (verify_mok()). Next, we check whether the binary's hash or signature have been blacklisted (check_blacklist()). This is done for each of the SHA1 and SHA256 hashes of the binary (the two hashes implemented in Tiano) and the certificate. If any of these match an entry in the dbx variable or a built-in blacklist, Shim will refuse to run them. Next, we simply do the same for the whitelist. If the binary's hash is either in the db variable or the Mok list, or if it's signed with a certificate that chains back to an entry in either db or the Mok list, we'll run it. Finally, the signature is checked against any built-in keys. Certificate verification is carried out with the AuthenticodeVerify() function, which again comes from Tiano's crypto library and is mostly implemented in OpenSSL.

If the binary passed signature validation we return to handle_image() and now load the binary's sections into their desired addresses. That buffer is handed to relocate_coff() which runs through the binary's relocation entries and fixes them up. There's one last subtlety, which is that because we've been fixing this up by hand, the image handle still refers to the shim binary. We fix the ImageBase and ImageSize values in the loaded image protocol to match the newly relocated binary, which is vital because otherwise grub is unable to find its built in modules. Finally, we jump into the binary's entry point. What the binary does next is up to it - if it's a bootloader, there's a good chance that it'll simply launch an OS and we'll never return. If it does return, we restore ImageBase and ImageSize, uninstall the protocol we installed and exit ourselves. If Shim exits (either because the second stage bootloader wasn't present, didn't verify, or exited) then control will simply pass on to the next boot option in the UEFI priority list. If there's nothing else, the platform will do something platform defined.

And that's how Shim works.

Boot time?

Date: 2012-10-31 06:14 am (UTC)
From: [identity profile] gedmin.as
Have you measured the effect that adding a shim bootloader and signature checking has on boot time?

Now, all I need…

Date: 2012-10-31 10:12 am (UTC)
From: (Anonymous)
… is an EFI version of SeaBIOS, that can be loaded from shim in unsigned mode, and can then load my real bootloader using traditional ways. Win! ☺

Date: 2012-10-31 09:39 pm (UTC)
From: (Anonymous)
What kind of keys are used? Is it all RSA, all DSA, all ECDSA, or are they in a type-value wrapper that allows multiple key types? If RSA, what's the padding scheme?

Date: 2012-10-31 09:43 pm (UTC)
From: [identity profile] pjones.id.fedoraproject.org
So there's actually a list of supported keys and hashes. MS requires that SHA-256 hashes signed by RSA-2048 keys be supported by the hardware platform, and that's what their bootloader (and shim) are hashed by and signed by.

As for the padding scheme - this is just PKCS-7. It's just a SHA-256 sum being signed.

Date: 2012-11-01 12:10 am (UTC)
From: (Anonymous)
PKCS #7 doesn't specify a padding format -- those are in PKCS #1, either the v1.5 static format or the v2.1 PSS format.

Date: 2012-11-01 12:34 am (UTC)
From: (Anonymous)
Oh, did a bit more research and saw that RSASSA-PSS isn't compatible with true PKCS #7, which Authenticode is based on. If they had used one of the newer CMS specs, the newer algorithms would be available.

Date: 2012-11-01 01:42 am (UTC)
From: [identity profile] pjones.id.fedoraproject.org
It isn't really true pkcs7 either - see the authenticode spec, it's basically a copy of pkcs7 with minor modifications. But you're basically correct.

Re start_image()

Date: 2012-10-31 11:04 pm (UTC)
From: [personal profile] lersek
generate_path() seems to allocate memory for *PathName (with AllocatePool()) and for *grubpath (with FileDevicePath()).

The caller of generate_path(), start_image(), doesn't seem to make any use of *grubpath (called "path" there). It seems to be leaked. PathName is also leaked if load_image() succeeds but handle_image() fails.

But my main point is: I believe you should not overwrite the EFI_LOADED_IMAGE object (even temporarily) that you located for the shim binary itself with HandleProtocol() / LOADED_IMAGE_PROTOCOL. I suspect that, after you've loaded the grub binary from disk and verified / relocated it, you should install it with the LoadImage() boot service as a child image. LoadImage() can load an image from a preexistent memory buffer. (See the SourceBuffer/SourceSize parameters.)

Afterwards the grub image could be / should be started by the StartImage boot service.

LoadImage() might even do the entire relocation / verification automatically. What's the reason for manual relocation? Have you perhaps found specific circumstances that make LoadImage()/StartImage() unsuitable?


Re: Re start_image()

Date: 2012-11-01 01:00 am (UTC)
From: [personal profile] lersek
Oh right, that's the point precisely... We trust the Machine Owner's Keys equally, even though they are absent from db. However, LoadImage() doesn't know about the MOK list. Thanks.


Date: 2012-11-03 02:37 pm (UTC)
From: (Anonymous)
Does EFI STUB loading continue to work?

Date: 2012-11-08 12:55 am (UTC)
From: (Anonymous)
I have one UEFI related question. If I disable secure boot will I have the CSM (Compatibility Support Module) enabled?

Date: 2012-11-08 04:40 pm (UTC)
From: (Anonymous)
Do you think most computers (or motherboards) will have an CSM?


Date: 2012-11-27 06:48 pm (UTC)
From: (Anonymous)
Would you please include some detail on how netboot is meant to be implemented? I cannot seem to get a successful build past erroring out with:

Failed to find fs
Failed to load grub


Matthew Garrett

About Matthew

Power management, mobile and firmware developer on Linux. Security developer at Google. Ex-biologist. @mjg59 on Twitter. Content here should not be interpreted as the opinion of my employer.

Page Summary

Expand Cut Tags

No cut tags