An overview of our Secure Boot implementation
I've written rather a lot about how we're implementing our Secure Boot support, but there's been a range of subtle changes over time. Here's an overview of how it all fits together.
An overview of our Secure Boot implementation
I've written rather a lot about how we're implementing our Secure Boot support, but there's been a range of subtle changes over time. Here's an overview of how it all fits together.
Getting started with UEFI development
It's not difficult to write a UEFI application under Linux. You'll need three things:
#include <efi.h>
#include <efilib.h>
EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
conout = systab->ConOut;
InitializeLib(image, systab);
uefi_call_wrapper(conout->OutputString, 2, conout, L"Hello World!\n\r");
return EFI_SUCCESS;
}The includes are fairly obvious. efi.h gives you UEFI functions defined by the specification, and efilib.h gives you functions provided by gnu-efi. The runtime will call efi_main() rather than main(), so that's our main function. It returns an EFI_STATUS which will in turn be returned to whatever executed the binary. The EFI_HANDLE image is a pointer to the firmware's context for this application, which is used in certain calls. The EFI_SYSTEM_TABLE *systab is a pointer to the UEFI system table, which in turn points to various other tables.typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
EFI_TEXT_RESET Reset;
EFI_TEXT_STRING OutputString;
EFI_TEXT_TEST_STRING TestString;
EFI_TEXT_QUERY_MODE QueryMode;
EFI_TEXT_SET_MODE SetMode;
EFI_TEXT_SET_ATTRIBUTE SetAttribute;
EFI_TEXT_CLEAR_SCREEN ClearScreen;
EFI_TEXT_SET_CURSOR_POSITION SetCursorPosition;
EFI_TEXT_ENABLE_CURSOR EnableCursor;
SIMPLE_TEXT_OUTPUT_MODE *Mode;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
The majority of these are functions - the only exception is the SIMPLE_TEXT_OUTPUT_MODE pointer, which points to the current mode. In an ideal world you'd be able to just call these directly, but sadly UEFI and Linux calling conventions are different and we need some thunking. That's where the uefi_call_function() call comes in. This takes a UEFI function as its first argument (in this case, conout->OutputString), the number of arguments (2, in this case) and then the arguments. Checking the spec for OutputString, we see this:typedef
EFI_STATUS
(EFIAPI *EFI_TEXT_STRING) (
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
IN CHAR16 *String
);The first argument is a pointer to the specific instance of the simple text output protocol. This permits multiple instances of the same protocol to exist on a single machine, without having to duplicate all of the code. The second argument is simply a UCS-2 string. Our conout pointer is a pointer to the protocol, so we pass that as the first argument. For the second argument, we pass L"Hello World!\n", with the L indicating that this is a wide string rather than a simple 8-bit string. uefi_call_function() then rearranges these arguments into the appropriate calling convention and calls the UEFI function. The firmware then prints "Hello World!" on the screen. Success.#include <efi.h>
#include <efilib.h>
EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
EFI_LOADED_IMAGE *loaded_image = NULL;
EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL;
EFI_STATUS status;
InitializeLib(image, systab);
status = uefi_call_wrapper(systab->BootServices->HandleProtocol,
3,
image,
&loaded_image_protocol,
(void **) &loaded_image);
if (EFI_ERROR(status)) {
Print(L"handleprotocol: %r\n", status);
}
Print(L"Image base : %lx\n", loaded_image->ImageBase);
Print(L"Image size : %lx\n", loaded_image->ImageSize);
Print(L"Image file : %s\n", DevicePathToStr(loaded_image->FilePath));
return EFI_SUCCESS;
}UEFI handles can have multiple protocols attached to them. A handle may represent a piece of physical hardware, or (as in this case) it can represent a software object. In this case we're going to get a pointer to the loaded image protocol that's associated with the binary we're running. To do this we call the HandleProtocol() function from the Boot Services table. Boot services are documented in section 6 of the UEFI specification and are the interface to the majority of UEFI functionality. HandleProtocol takes an image and a protocol identifier, and hands back a pointer to the protocol. UEFI protocol identifiers are all GUIDs and defined in the specification. If you call HandleProtocol on a handle that doesn't implement the protocol you request, you'll simply get an error back in the status argument. No big deal.#include <efi.h>
#include <efilib.h>
EFI_STATUS
efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab)
{
EFI_STATUS status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
InitializeLib(image_handle, systab);
status = uefi_call_wrapper(systab->BootServices->LocateProtocol,
3,
&gop_guid,
NULL,
&gop);
Print(L"Framebuffer base is at %lx\n", gop->Mode->FrameBufferBase);
return EFI_SUCCESS;
}This will find the first (where first is an arbitrary platform ordering) instance of a protocol and return a pointer to it. If there may be multiple instances of a protocol (as there often are with the graphics output protocol) you should use LocateHandleBuffer() instead. This returns an array of handles that implement the protocol you asked for - you can then use HandleProtocol() to give you the instance on the specific handle. There's various helpers in gnu-efi like LibLocateHandle() that make these boilerplate tasks easier. You can find the full list in /usr/include/efi/efilib.h. ARCH = $(shell uname -m | sed s,i[3456789]86,ia32,)
LIB_PATH = /usr/lib64
EFI_INCLUDE = /usr/include/efi
EFI_INCLUDES = -nostdinc -I$(EFI_INCLUDE) -I$(EFI_INCLUDE)/$(ARCH) -I$(EFI_INCLUDE)/protocol
EFI_PATH = /usr/lib64/gnuefi
EFI_CRT_OBJS = $(EFI_PATH)/crt0-efi-$(ARCH).o
EFI_LDS = $(EFI_PATH)/elf_$(ARCH)_efi.lds
CFLAGS = -fno-stack-protector -fpic -fshort-wchar -mno-red-zone $(EFI_INCLUDES)
ifeq ($(ARCH),x86_64)
CFLAGS += -DEFI_FUNCTION_WRAPPER
endif
LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(EFI_PATH) -L$(LIB_PATH) \
$(EFI_CRT_OBJS) -lefi -lgnuefi
TARGET = test.efi
OBJS = test.o
SOURCES = test.c
all: $(TARGET)
test.so: $(OBJS)
$(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS)
%.efi: %.so
objcopy -j .text -j .sdata -j .data \
-j .dynamic -j .dynsym -j .rel \
-j .rela -j .reloc -j .eh_frame \
--target=efi-app-$(ARCH) $^ $@There's rather a lot going on here, but the important stuff is the CFLAGS line and everything after that. First, we need to disable the stack protection code - there's nothing in the EFI runtime for it to call into, so we'll build a binary with unresolved symbols otherwise. Second, we need to build position independent code, since UEFI may relocate us anywhere. short-wchar is needed to indicate that strings like L"Hi!" should be 16 bits per character, rather than gcc's default of 32 bits per character. no-red-zone tells the compiler not to assume that there's 256 bytes of stack available to it. Without this, the firmware may modify stack that the binary was depending on.Ted Ts'o is a rape apologist and why this matters
(This post contains some discussion of rape and sexual assault but does not go into any specifics)
There was a brief controversy at Linux.conf.au back in 2011. The final keynote speaker gave a compelling presentation on online privacy, including some slides containing sexualised imagery. This was against the terms of the conference policies, and resulted in an apology from the conference organisers and the speaker. The situation was unfortunate but well handled, and that should have been the end of it.
Afterwards, there was some pushback on the conference mailing list. Concerns were raised about the policy being overly restrictive and the potential for it to be used to stifle speech that influential groups disagreed with. I don't agree with these arguments, but discussion of why policies have been implemented is completely natural and provides an opportunity for a community to determine what its expected standards are.
And then Ted Ts'o effectively called rape victims liars[1]. At first I assumed that this was just some sort of horrific failure to understand the implications of what he was saying, so I emailed him to check. The reply I got drew a pretty clear distinction between the case of a drunk college student raping another drunk college student in their room and the case of knifepoint rape in a dark park. You know, the difference between accidental rape and rape rape. The difference between the one any of us might have done and the one that only bad people do. Legitimate rape and the "rape" that those feminists talk about. The distinction that lets rapists convince themselves that they didn't really rape anyone because they weren't holding a knife at the time.
Ted Ts'o argues that only a small percentage of rape really counts as what people think of as rape. Ted Ts'o is a rape apologist.
There's an ongoing scandal in the UK at the moment. A well known DJ, Jimmy Savile, died last year. He grew up in a working class family, but through hard work and natural talent was one of the most significant figures in promoting pop music in the UK in the 50s and 60s, and worked in various parts of the BBC for the best part of 30 years. He spent significant amounts of time raising money for charity, and it's estimated that he raised over £40 million for various causes. Since his death, around 300 people have accused him of sexually abusing them. The BBC is desperately trying to explain why it cancelled an expose shortly before it aired. Multiple people who worked there at the time claim that everyone knew he was involved in indecent activities, but saying anything would risk both their career and the charities that depended on his fundraising. Nobody said anything, and he was allegedly free to continue his abuse.
Ted Ts'o is a significant figure in the Linux kernel community. He has expressed abhorrent beliefs that damage that community. Condemnation was limited to a mailing list with limited readership, meaning, effectively, that nobody said anything. Last week the Ada Initiative published a blog post pointing out the damage that did, and I realised that my effective silence was not only helping to alienate 50% of the population from involving themselves with Linux, it was also implicitly supporting my community leadership. I was giving the impression that I was basically fine with our community leaders telling people that it wasn't really rape if you were both drunk enough. I was increasing the chances of members of our community being sexually assaulted. Silence is endorsement. Saying nothing is not ok.
In the absence of an apology and explanation from Ted, I'll be interacting with him to the bare minimum that I'm compelled to as a result of my job. I won't be attending any Linux Foundation events he's involved in organising. If I'm running any events, I won't be inviting him. At a time when we're finally making progress in making our community more open and supportive, we don't need leaders who undermine that work. Support organisations who encourage that progress, not the people who help drag us back.
Footnotes
[1]The original archive has vanished. I've put up a copy of the relevant thread here. Throughout, Ted states that he's actually arguing against the idea that women need to be frightened of sexual assault, and not against the definition of rape. Except saying things like This one does a pretty good job of taking apart the Koss / Ms. Magazine study, which is the source for the "1 in 4" number. For example, it points out that over half of those cases were ones where undergraduates were plied with alcohol, and did not otherwise involve using physical force or other forms of coercion
is difficult to read in any way other than "Half of the people you're counting as having been raped haven't really been raped", and favourably referring to an article that asserts that the rate of false rape reports is probably close to 50% is pretty strong support for the idea that many rape victims are liars. comments
Disabling Secure Boot signature validation
One of the benefits of the Shim approach of bridging trust between the Microsoft key and our own keys is that we can define whatever trust policy we want. Some of the feedback we've received has indicated that people really do want the ability to disable signature validation without having to go through the firmware. The problem is in ensuring that this can't be done either accidentally or via trivial social engineering.
We've come up with one possible solution for this. A tool run at the OS level generates a random password and hashes it. This hash is appended to the desired secure boot state and stored in an EFI variable. On reboot, Shim notices that this variable is set and drops to a menu. The user then selects "Change signature enforcement" and types the same password again. The system is then rebooted and Shim now skips the signature validation.
This approach avoids automated attacks - if malware sets this variable, the user will have no idea which password is required. Any social engineering attack would involve a roughly equivalent number of steps to disabling Secure Boot in the firmware UI, so it's not really any more attractive than just doing that. We're fairly confident that this meets everyone's expectations of security, but also guarantees that people who want to run arbitrary kernels and bootloaders can do so. comments
Further UEFI bootloader work
I've been continuing to work on Shim this week, and it's now getting pretty close to feature complete. The biggest change has been migrating the MokList variable from a custom format to the one used by the UEFI spec databases, which means it's easier for the kernel to import the MOK entries and use them for validating module signatures.
The other benefit of this format is that it supports hashes as well as certificates, and so I've added support for enrolling hashes through the MokManager UI. This means that distributions can now ship with a signed copy of Shim without having to sign any other components of their distribution. When the user attempts to boot off the media they'll be faced with a menu and a countdown. If the countdown reaches 0, the system will simply fall back to the next entry in the bootlist. If they hit a key, they can choose to enrol a hash. The user then navigates the filesystem explorer, chooses the bootloader, confirms that they want to enrol it and then exits. Shim then verifies the bootloader against the hash and boots successfully.
The big advantage of this over the Linux Foundation approach is that once a hash has been enrolled the need for physical end-user presence is removed - ie, if you enrol the hash, you don't need to hit a key every time you boot. This is still slightly sub-optimal in that if you update your bootloader you'll need to enrol a new hash, but that can be partially automated by calling MokUtil in the postinst - the user then simply needs to confirm that they want to enrol the hash, rather than having to choose it manually. Completely transparent updates are going to require a signed bootloader and an enrolled signing key.
A couple of people have asked whether we're planning on implementing the Linux Foundation approach of simply asking the user whether they want to boot an unsigned file. We've considered it, but at the moment are leaning towards "no" - it's simply too easy to use to trick naive users into running untrusted code. Users are trained to click through pretty much any security prompt that they see, and if an attacker replaces a legitimate bootloader with one that asks them to press "y" to make their computer work, they'll press "y". If that bootloader then launches a trojaned Windows bootloader that launches a trojaned Windows kernel, that's kind of a problem. This could be somewhat mitigated by limiting this feature to removable media, and we're seriously considering that, but there are still some risks associated. We might just end up writing the code but disabling it at build time, and then anyone who wants to distribute with that policy can do so at their own risk.
Meanwhile, Peter Jones is working on tidying up the code we're using for the actual signing and will be publishing that once the last couple of kinks are worked out. We're using hardware cryptography, so even if someone compromises the build systems they won't be able to obtain the private key that we use. However, should something disastrous and unanticipated happen, we do have a plan in place for migrating to new keys with minimal user impact. We'll document the code and infrastructure we're using in order to make it as easy as possible for other distributions to implement equivalent functionality.
As I've mentioned before, our goal is to make it as easy as possible for distributions to implement whatever level of Secure Boot policy they want without having to engage with Microsoft themselves. Shim allows distributions to ship an OS that has no signed binaries at all, or alternatively to ship an OS that uses filesystem-level cryptography to ensure that even userspace is completely signed. I'm expecting to see a range of options available, and I hope that the majority of users will find something to suit their needs. comments
Linux Foundation approach to Secure Boot
James Bottomley just published a description of the Linux Foundation's Secure Boot plan, which is pretty much as I outlined in the second point here - it's a bootloader that will boot untrusted images as long as a physically present end-user hits a key on every boot, and if a user switches their machine to setup mode it'll enrol the hash of the bootloader in order to avoid prompting again. In other words, it's less useful than shim. Just use shim instead. comments
Handling UEFI Secure Boot in smaller distributions
The plan for supporting UEFI Secure Boot in Fedora is still pretty much as originally planned, but it's dependent upon building a binary which has the Fedora key embedded, and then getting that binary signed by Microsoft. Easy enough for us to do, but not necessarily practical for smaller distributions. There's a few possible solutions for them.
The importance of trustworthy power structures
Direct harassment and overt sexism are certainly a significant part of why the gender ratios in the Linux community[1] are so skewed, but they're not the whole story. Part of feeling comfortable with a community is the knowledge that those in positions of power can be relied upon to engage in appropriate action when undesirable situations arise - no matter how compelling your code of conduct, no matter how detailed your diversity policy, if someone contravenes them and nothing happens, those documents are worthless and your community is unwelcoming. You can't rely on enforcement unless the community can trust its leadership.
Setting a good example in terms of behaviour is obviously vital, but it's not sufficient. Leaders who engage in sexist behaviour or who harass community members are clearly untrustworthy and undermine any attempts the rest of the community may be making. Failing to step in when they see examples of unacceptable behaviour is as bad. But less obvious is that it's possible to destroy that community trust without clearly contravening those community standards.
An example of this is Todd Akin, a candidate for the upcoming US Senate elections. In August he justified his support for making abortion illegal even in cases of rape, on the basis that women can't become pregnant through being victims of "legitimate rape". Of course, this had little to do with his fiscal or wider social policies, positions that are more directly relevant to most of his potential constituents, and in an election year that's primarily about the economy and healthcare it might be expected that a single issue would have little effect on the election standings. Instead, there was a huge uproar and a previously safe victory has now turned into a real chance of a loss.
Why? A significant part of it is because many people now have difficulty believing that they can trust him to represent them. If he understands women so little that he's willing to make entirely spurious justifications for his positions, how could any woman trust him to consider any other problems they may face? If he's willing to use fake science to back himself up, how could anyone trust him to consider any issue involving science? Through a single statement to a journalist he demonstrated to many that he was unsuited to be their representative in government. Many people who would otherwise have voted for him simply don't feel that they can rely on him to be there for them. Whether he wins his election or not, that distrust is going to remain and it's going to compromise his ability to work with his constituents.
It's the same in technical communities. If a member of a project's technical leadership frequently and loudly expresses their disdain for Python coders, anyone advocating for increased use of Python in that project is unlikely to feel that they can get unbiased opinions from that leader. If the project as a whole is uninterested in adopting Python then that's probably for the best, but if the rest of the project is open to using Python then there's a problem. People working with Python are less likely to join the project, and perhaps the project will lose out as a result.
But it's worth remembering that technical communities are still communities. If a member of a project's technical leadership vociferously argues that slavery benefited minority groups in the long run, members of those minority groups are going to feel that they're not going to be well represented by that leader. If they blog about how homosexuality is a lifestyle choice then homosexuals are unlikely to flock to the project. Likewise, if they downplay the prevalence or impact of rape, women are going to feel marginalised and find something better to do with their time.
It's impossible to separate these things. No matter how technically competent a community leader is, no matter how much code review they perform or how much mentorship they provide, if they're expressing unacceptable social opinions then they're diminishing the community. People I know and respect have left technical communities simply because people in positions of responsibility have engaged in this kind of behaviour without it causing them any problems.
We shouldn't be willing to give people a pass simply because they aren't actually groping anyone or because they're not members of the KKK. Those who drive people away from the community on the basis of race, gender or sexual orientation deserve vocal condemnation, and if they're unwilling to change their behaviour then the community should instead act to drive them away.
We're pretty bad at that in the Linux world at the moment. Too many people have behaved in this way and are left with positions of power or responsibility. There's no easy solution to the problem, but the Ada Initiative is continuing to work to improve awareness and work with communities to reduce the alienation that results from this kind of behaviour. If you want this to be anything other than a straight white male dominated community, you should give them money.
[1] (and computing in general, though to a lesser extent) comments
UEFI Bootkits
The Register covered a story on a UEFI-based attack on Windows 8. It's actually covering this technical writeup, which is a discussion of a proof of concept implementation of an attack on the Windows 8 kernel from the firmware environment. The first approach they describe is pretty straightforward, in that they simply patch the Windows bootloader directly. Since this is what's reading the kernel off disk and launching it, patching that code means you can modify the kernel in-place and run it with built-in privilege elevation exploits.UEFI Secure boot in Fedora: status update
There's been good progress in Fedora's implementation of UEFI Secure Boot, so time for a quick update.
New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.
Keep up with the latest Advogato features by reading the Advogato status blog.
If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!