[personal profile] mjg59
(Discussion of a presentation I gave at Kiwicon last month)

Kexec is a Linux kernel feature intended to allow the booting of a replacement kernel at runtime. There's a few reasons you might want to do that, such as using Linux as a bootloader[1], rebooting without having to wait for the firmware to reinitialise or booting into a minimal kernel and userspace that can be booted on crash in order to save system state for later analysis.

But kexec's significantly more flexible than this. The kexec system call interface takes a list of segments (ie, pointers to a userspace buffer and the desired target destination) and an entry point. The kernel relocates those segments and jumps to the entry point. That entry point is typically code referred to as purgatory, due to the fact that it lives between the world of the first kernel and the world of the second kernel. The purgatory code sets up the environment for the second kernel and then jumps to it. The first kernel doesn't need to know anything about what the second kernel is or does. While it's conventional to load Linux, you can load just about anything.

The most important thing to note here is that none of this is signed. In other words, despite us having a robust in-kernel mechanism for ensuring that only signed modules can be inserted into the kernel, root can still load arbitrary code via kexec and execute it. This seems like a somewhat irritating way to patch the running kernel, so thankfully there's a much more straightforward approach.

I modified kexec to add an additional loader and uploaded the code here. Build and install it. Make sure that /sys/module/module/parameters/sig_enforce on your system is "Y". Then, as root, do something like:
kexec --type="dummy" --address=`printf "0x%x" $(( $(grep "B sig_enforce" /proc/kallsyms | awk '{print "0x"$1}') & 0x7fffffff))` --value=0 --load-preserve-context --mem-max=0x10000 /bin/true
to load it[2]. Now do kexec -e and watch colours flash and check /sys/module/module/parameters/sig_enforce again.

The beauty of this approach is that it doesn't rely on any kernel bugs - it's using kernel functionality that was explicitly designed to let you do this kind of thing (ie, run arbitrary code in ring 0). There's not really any way to fix it beyond adding a new system call that has rather tighter restrictions on the binaries that can be loaded. If you're using signed modules but still permit kexec, you're not really adding any additional security.

But that's not the most interesting way to use kexec. If you can load arbitrary code into the kernel, you can load anything. Including, say, the Windows kernel. ReactOS provides a bootloader that's able to boot the Windows 2003 kernel, and it shouldn't be too difficult for a sufficiently enterprising individual to work out how to get Windows 8 booting. Things are a little trickier on UEFI - you need to tell the firmware which virtual→physical map to use, and you can only do it once. If Linux has already done that, it's going to be difficult to set up a different map for Windows. Thankfully, there's an easy workaround. Just boot with the "noefi" kernel argument and the kernel will skip UEFI setup, letting you set up your own map.

Why would you want to do this? The most obvious reason is avoiding Secure Boot restrictions. Secure Boot, if enabled, is explicitly designed to stop you booting modified kernels unless you've added your own keys. But if you boot a signed Linux distribution with kexec enabled (like, say, Ubuntu) then you're able to boot a modified Windows kernel that will still believe it was booted securely. That means you can disable stuff like the Early Launch Anti-Malware feature or driver signing, or just stick whatever code you want directly into the kernel. In most cases all you'd need for this would be a bootloader, kernel and an initrd containing support for the main storage, an ntfs driver and a copy of kexec-tools. That should be well under 10MB, so it'll easily fit on the EFI system partition. Copy it over the Windows bootloader and you should be able to boot a modified Windows kernel without any terribly obvious graphical glitches in the process.

And that's the story of why kexec is disabled on Fedora when Secure Boot is enabled.

[1] That way you only have to write most drivers once
[2] The address section finds the address of the sig_enforce symbol in the kernel, and the value argument tells the dummy code what value to set that address to. --load-preserve-context informs the kernel that it should save hardware state in order to permit returning to the original kernel. --mem-max indicates the highest address that the kernel needs to back up. /bin/true is just there to satisfy the argument parser.

Signing kexec blobs?

Date: 2013-12-03 11:27 pm (UTC)
From: (Anonymous)
Wouldn't it be possible to check the signature of any code that is uploaded via kexec? In fact any time a memory page is made executable is a good time to check that the code is authorized for execution in the particular context.

Re: Signing kexec blobs?

Date: 2013-12-03 11:59 pm (UTC)
From: (Anonymous)
Yay - self modifying code :). I'd expect the kernel command line to be signed as well, though - it's an obvious attack vector.

Actually, this got me thinking - chunks of the kernel can get paged out to disk (presumably after the signature is verified). Is the signature checked again when the vmm pages it back in? Or could I try writing to /dev/hda to subvert things?

Re: Signing kexec blobs?

Date: 2013-12-04 01:05 am (UTC)
From: (Anonymous)

If the Linux kernel doesn't use VMM to page in/out its executable, then my attack won't work. Hey I learned something :). I thought the kernel did memmap its files through the MMU.

Windows does page kernel drivers, and has a system of file locks to protect on disk data (including insisting that the data is on the system volume, not a corruptible network share)

Re: Signing kexec blobs?

Date: 2013-12-04 06:42 pm (UTC)
From: (Anonymous)
Linux kernel does not support paging kernel memory out to disk.

Re: Signing kexec blobs?

Date: 2013-12-05 09:17 am (UTC)
From: (Anonymous)
As I understand it, it's not self modifying.

It's a userspace program that loads it in Purgatory and that same a userspace program that modifies that kernel to change the kernel arguments.

So it's not self modifying, but it is modified.

How much work would it be for the kernel to modify the kernel arguments in it's own signed successor ?

Can the Linux kernel be made smart enough to at least understand it's own kernel arguments ?

Or maybe only allow the same kernel arguments for it's successor to be the same as it's own arguments ?

Date: 2013-12-04 12:27 am (UTC)
From: (Anonymous)
If you've verified a signature on the entire kexec image before kexecing it, which should be entirely plausible to do, then purgatory shouldn't matter: you'd only sign kexec images that don't do naughty things.

Date: 2013-12-04 07:51 pm (UTC)
From: (Anonymous)
It has to be provided by userspace and handed to the kernel, though. If it's handed to the kernel, the kernel can verify it.

Date: 2013-12-04 09:35 am (UTC)
From: (Anonymous)
This appears to me as another nice argument for authenticated/trusted/TPM-based boot over secure/restricted boot.

In authenticated boot, you can test your boot chain a posteriori and decide at the end if it meets your security policy.
In secure boot, you need to restrict the capabilities for reaching insecure system-state a priori and for every boot step regarding your security policy.

This also leads to being able to choose and switch policies in the first case, whilst in the second case you can have only one single policy forever.

Feel free to contact me on that topic if you'd like to discuss it further. Cheers, Andreas Fuchs at Fraunhofer SIT

Date: 2013-12-05 03:26 pm (UTC)
From: (Anonymous)
I went ahead and added support for measuring files to TPM PCRs to Shim so it now measures the next boot loader prior to execution. It's hardwired right now but was easy enough to implement. It helps extend the chain of trust upwards and enable further attestation.

Date: 2013-12-06 02:26 pm (UTC)
From: (Anonymous)
Still cleaning it up before I post the source anywhere. Happy to share with a limited audience though.

Date: 2013-12-12 10:40 am (UTC)
From: (Anonymous)
In case the original author reads this, could you share that code with me ?
@mjg: In case you two got into contact, could you ask him to contact me too ?

Thanks, Andreas Fuchs at SIT Fraunhofer in De

Where is the subverted security?

Date: 2013-12-04 12:28 pm (UTC)
From: (Anonymous)
SecureBoot spec requires that no unsigned code is executed before ExitBootServices() is called.

The difference between before and after call is that EfiBootServicesCode & EfiBootServicesData memory types are unloaded and become available to be freely used by the EfiLoaderCode/EfiLoaderData. I don't see a way to set the memory maps before ExitBootServices, and it seems that BootServices update the memory map and one needs to retrieve latest/current one with GetMemoryMap(). As the key to current MemoryMap is required parameter that needs to match for ExitBootServices code to succeed. After ExitBootServices(), hell breaks loose and one can execute unsigned code. And if the signed binary that does ExitBootServices() is actually somehow rouge or executes unsigned code, its signature can be revoked.

Above seems to rely on a linux kernel (the one that is used as a bootloader) signed by the Trusted key. Is there such a kernel/signature available?

On Ubuntu, no unsigned code is executed before ExitBootServices() call. It is called before kernel is loaded. Why bother with kexec, when one can boot unsigned kernel? =) That was an a design decision, to make sure, for now, that one can easily boot custom / modified kernels.

- xnox

Re: Where is the subverted security?

Date: 2013-12-04 03:17 pm (UTC)
From: (Anonymous)
Not entirely true, grub in Ubuntu will only call ExitBootServices() if the kernel it's loading is unsigned. If the kernel is signed, it's booted with the boot services and will call ExitBootServices() itself once it's done initializing.

Secure Boot is subverting security itself

Date: 2013-12-04 08:06 pm (UTC)
From: (Anonymous)
Secure Boot is subverting security itself, especially when there's a Microsoft key in UEFI.

User should always be able to load arbitrary code into kernel.


Matthew Garrett

About Matthew

Power management, mobile and firmware developer on Linux. Security developer at Nebula. 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