mbox series

[RFC,0/3] efi/x86: add support for generic EFI mixed mode boot

Message ID 20200213145928.7047-1-ardb@kernel.org (mailing list archive)
Headers show
Series efi/x86: add support for generic EFI mixed mode boot | expand

Message

Ard Biesheuvel Feb. 13, 2020, 2:59 p.m. UTC
This series is another part of my effort to reduce the level of knowledge
on the part of the bootloader or firmware of internal per-architecture
details regarding where/how the kernel is loaded and where its initrd and
other context data are passed.

The x86 architecture has a so-called 'EFI handover protocol', which defines
how the bootparams struct should be populated, and how it should be
interpreted to figure out where to load the kernel, and at which offset in
the binary the entrypoint is located. This scheme allows the initrd to be
loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel
via a special entrypoint that manages the state transitions between the
two execution modes.

Due to this, x86 loaders currently do not rely on LoadImage and StartImage,
and therefore, are forced to re-implement things like image authentication
for secure boot and taking the measurements for measured boot in their open
coded clones of these routines.

My previous series on this topic [0] implements a generic way to load the
initrd from any source supported by the loader without relying on something
like device trees or bootparams structures, and so native boot should not
need the EFI handover protocol anymore after those change are merged.

What remains is mixed mode boot, which also needs the EFI handover protocol
regardless of whether an initrd is loaded or not. So let's get rid of that
requirement, and take advantage of the fact that EDK2 based firmware does
support LoadImage() for X64 binaries on IA32 firmware, which means we can
rely on the secure boot and measured boot checks being performed by the
firmware. The only thing we need to put on top is a way to discover the
non-native entrypoint into the binary in a way that does not rely on x86
specific headers and data structures.

So let's introduce a new .compat header in the PE/COFF metadata of the
bzImage, and populate it with a <machine type, entrypoint> tuple, allowing
a generic EFI loader to decide whether the entrypoint supports its native
machine type, and invoke it as an ordinary EFI application entrypoint.
Since we will not be passing a bootparams structure, we need to discover
the base of the image (which contains the setup header) via the loaded
image protocol before we can enter the kernel in 32-bit mode at startup_32()

A loader implementation for OVMF can be found at [1]. Note that this loader
code is fully generic, and could be used without modifications if other
architectures ever emerge that support kernels that can be invoked from a
non-native (but cross-type supported) loader.

[0] https://lore.kernel.org/linux-arm-kernel/20200206140352.6300-1-ardb@kernel.org/
[1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic

Cc: lersek@redhat.com
Cc: leif@nuviainc.com
Cc: pjones@redhat.com
Cc: mjg59@google.com
Cc: agraf@csgraf.de
Cc: daniel.kiper@oracle.com
Cc: hdegoede@redhat.com
Cc: nivedita@alum.mit.edu
Cc: mbrown@fensystems.co.uk
Cc: mingo@kernel.org

Ard Biesheuvel (3):
  efi/x86: drop redundant .bss section
  efi/x86: add true mixed mode entry point into .compat section
  efi/x86: implement mixed mode boot without the handover protocol

 arch/x86/boot/Makefile             |  2 +-
 arch/x86/boot/compressed/head_64.S | 61 +++++++++++++++++-
 arch/x86/boot/header.S             | 23 ++++---
 arch/x86/boot/tools/build.c        | 67 +++++++++++++-------
 4 files changed, 115 insertions(+), 38 deletions(-)

Comments

Arvind Sankar Feb. 13, 2020, 5:53 p.m. UTC | #1
On Thu, Feb 13, 2020 at 03:59:25PM +0100, Ard Biesheuvel wrote:
> This series is another part of my effort to reduce the level of knowledge
> on the part of the bootloader or firmware of internal per-architecture
> details regarding where/how the kernel is loaded and where its initrd and
> other context data are passed.
> 
> The x86 architecture has a so-called 'EFI handover protocol', which defines
> how the bootparams struct should be populated, and how it should be
> interpreted to figure out where to load the kernel, and at which offset in
> the binary the entrypoint is located. This scheme allows the initrd to be
> loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel
> via a special entrypoint that manages the state transitions between the
> two execution modes.
> 
> Due to this, x86 loaders currently do not rely on LoadImage and StartImage,
> and therefore, are forced to re-implement things like image authentication
> for secure boot and taking the measurements for measured boot in their open
> coded clones of these routines.
> 
> My previous series on this topic [0] implements a generic way to load the
> initrd from any source supported by the loader without relying on something
> like device trees or bootparams structures, and so native boot should not
> need the EFI handover protocol anymore after those change are merged.
> 
> What remains is mixed mode boot, which also needs the EFI handover protocol
> regardless of whether an initrd is loaded or not. So let's get rid of that
> requirement, and take advantage of the fact that EDK2 based firmware does
> support LoadImage() for X64 binaries on IA32 firmware, which means we can
> rely on the secure boot and measured boot checks being performed by the
> firmware. The only thing we need to put on top is a way to discover the
> non-native entrypoint into the binary in a way that does not rely on x86
> specific headers and data structures.
> 
> So let's introduce a new .compat header in the PE/COFF metadata of the
> bzImage, and populate it with a <machine type, entrypoint> tuple, allowing
> a generic EFI loader to decide whether the entrypoint supports its native
> machine type, and invoke it as an ordinary EFI application entrypoint.
> Since we will not be passing a bootparams structure, we need to discover
> the base of the image (which contains the setup header) via the loaded
> image protocol before we can enter the kernel in 32-bit mode at startup_32()
> 
> A loader implementation for OVMF can be found at [1]. Note that this loader
> code is fully generic, and could be used without modifications if other
> architectures ever emerge that support kernels that can be invoked from a
> non-native (but cross-type supported) loader.
> 
> [0] https://lore.kernel.org/linux-arm-kernel/20200206140352.6300-1-ardb@kernel.org/
> [1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic
> 

As an alternative to the new section, how about having a CONFIG option
to emit the 64-bit kernel with a 32-bit PE header instead, which would
point to efi32_pe_entry? In that case it could be directly loaded by
existing firmware already. You could even have a tool that can mangle an
existing bzImage's header from 64-bit to 32-bit, say using the newly
added kernel_info structure to record the existence and location of
efi32_pe_entry.

Also, the PE header can live anywhere inside the image, right? Is there
any reason to struggle to shoehorn it into the "boot sector"?
Ard Biesheuvel Feb. 13, 2020, 5:55 p.m. UTC | #2
On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
>
> On Thu, Feb 13, 2020 at 03:59:25PM +0100, Ard Biesheuvel wrote:
> > This series is another part of my effort to reduce the level of knowledge
> > on the part of the bootloader or firmware of internal per-architecture
> > details regarding where/how the kernel is loaded and where its initrd and
> > other context data are passed.
> >
> > The x86 architecture has a so-called 'EFI handover protocol', which defines
> > how the bootparams struct should be populated, and how it should be
> > interpreted to figure out where to load the kernel, and at which offset in
> > the binary the entrypoint is located. This scheme allows the initrd to be
> > loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel
> > via a special entrypoint that manages the state transitions between the
> > two execution modes.
> >
> > Due to this, x86 loaders currently do not rely on LoadImage and StartImage,
> > and therefore, are forced to re-implement things like image authentication
> > for secure boot and taking the measurements for measured boot in their open
> > coded clones of these routines.
> >
> > My previous series on this topic [0] implements a generic way to load the
> > initrd from any source supported by the loader without relying on something
> > like device trees or bootparams structures, and so native boot should not
> > need the EFI handover protocol anymore after those change are merged.
> >
> > What remains is mixed mode boot, which also needs the EFI handover protocol
> > regardless of whether an initrd is loaded or not. So let's get rid of that
> > requirement, and take advantage of the fact that EDK2 based firmware does
> > support LoadImage() for X64 binaries on IA32 firmware, which means we can
> > rely on the secure boot and measured boot checks being performed by the
> > firmware. The only thing we need to put on top is a way to discover the
> > non-native entrypoint into the binary in a way that does not rely on x86
> > specific headers and data structures.
> >
> > So let's introduce a new .compat header in the PE/COFF metadata of the
> > bzImage, and populate it with a <machine type, entrypoint> tuple, allowing
> > a generic EFI loader to decide whether the entrypoint supports its native
> > machine type, and invoke it as an ordinary EFI application entrypoint.
> > Since we will not be passing a bootparams structure, we need to discover
> > the base of the image (which contains the setup header) via the loaded
> > image protocol before we can enter the kernel in 32-bit mode at startup_32()
> >
> > A loader implementation for OVMF can be found at [1]. Note that this loader
> > code is fully generic, and could be used without modifications if other
> > architectures ever emerge that support kernels that can be invoked from a
> > non-native (but cross-type supported) loader.
> >
> > [0] https://lore.kernel.org/linux-arm-kernel/20200206140352.6300-1-ardb@kernel.org/
> > [1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic
> >
>
> As an alternative to the new section, how about having a CONFIG option
> to emit the 64-bit kernel with a 32-bit PE header instead, which would
> point to efi32_pe_entry? In that case it could be directly loaded by
> existing firmware already. You could even have a tool that can mangle an
> existing bzImage's header from 64-bit to 32-bit, say using the newly
> added kernel_info structure to record the existence and location of
> efi32_pe_entry.
>

That wouldn't work with, say, signed distro kernels.

> Also, the PE header can live anywhere inside the image, right? Is there
> any reason to struggle to shoehorn it into the "boot sector"?

It cannot. It must live outside a region described by the section headers.
Arvind Sankar Feb. 13, 2020, 6:47 p.m. UTC | #3
On Thu, Feb 13, 2020 at 05:55:44PM +0000, Ard Biesheuvel wrote:
> On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > As an alternative to the new section, how about having a CONFIG option
> > to emit the 64-bit kernel with a 32-bit PE header instead, which would
> > point to efi32_pe_entry? In that case it could be directly loaded by
> > existing firmware already. You could even have a tool that can mangle an
> > existing bzImage's header from 64-bit to 32-bit, say using the newly
> > added kernel_info structure to record the existence and location of
> > efi32_pe_entry.
> >
> 
> That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed
images, one 32-bit and one 64-bit, which are identical except for the
header. At install time, the installer chooses based on the system's
firmware bit-ness.

> 
> > Also, the PE header can live anywhere inside the image, right? Is there
> > any reason to struggle to shoehorn it into the "boot sector"?
> 
> It cannot. It must live outside a region described by the section headers.

It could still be inserted after .setup, or at the very end of the file, no?
Ard Biesheuvel Feb. 13, 2020, 10:36 p.m. UTC | #4
On Thu, 13 Feb 2020 at 19:47, Arvind Sankar <nivedita@alum.mit.edu> wrote:
>
> On Thu, Feb 13, 2020 at 05:55:44PM +0000, Ard Biesheuvel wrote:
> > On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > > As an alternative to the new section, how about having a CONFIG option
> > > to emit the 64-bit kernel with a 32-bit PE header instead, which would
> > > point to efi32_pe_entry? In that case it could be directly loaded by
> > > existing firmware already. You could even have a tool that can mangle an
> > > existing bzImage's header from 64-bit to 32-bit, say using the newly
> > > added kernel_info structure to record the existence and location of
> > > efi32_pe_entry.
> > >
> >
> > That wouldn't work with, say, signed distro kernels.
>
> No, the idea would be that the distro would distribute two signed
> images, one 32-bit and one 64-bit, which are identical except for the
> header. At install time, the installer chooses based on the system's
> firmware bit-ness.
>

I guess it would be possible, but then we'd need two different images
while today, we can run the same image on both kinds of firmwares. The
only thing I am trying to do is remove all the quirky bootparams stuff
from the loader so that we can switch to LoadImage

> >
> > > Also, the PE header can live anywhere inside the image, right? Is there
> > > any reason to struggle to shoehorn it into the "boot sector"?
> >
> > It cannot. It must live outside a region described by the section headers.
>
> It could still be inserted after .setup, or at the very end of the file, no?

The PE/COFF spec mentions that the COFF header needs to follow the
signature. Also, the SizeOfHeaders field would become somewhat
ambiguous if the header is split up like that.
Arvind Sankar Feb. 14, 2020, 12:10 a.m. UTC | #5
On Thu, Feb 13, 2020 at 10:36:14PM +0000, Ard Biesheuvel wrote:
> On Thu, 13 Feb 2020 at 19:47, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> >
> > On Thu, Feb 13, 2020 at 05:55:44PM +0000, Ard Biesheuvel wrote:
> > > On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > > > As an alternative to the new section, how about having a CONFIG option
> > > > to emit the 64-bit kernel with a 32-bit PE header instead, which would
> > > > point to efi32_pe_entry? In that case it could be directly loaded by
> > > > existing firmware already. You could even have a tool that can mangle an
> > > > existing bzImage's header from 64-bit to 32-bit, say using the newly
> > > > added kernel_info structure to record the existence and location of
> > > > efi32_pe_entry.
> > > >
> > >
> > > That wouldn't work with, say, signed distro kernels.
> >
> > No, the idea would be that the distro would distribute two signed
> > images, one 32-bit and one 64-bit, which are identical except for the
> > header. At install time, the installer chooses based on the system's
> > firmware bit-ness.
> >
> 
> I guess it would be possible, but then we'd need two different images
> while today, we can run the same image on both kinds of firmwares. The
> only thing I am trying to do is remove all the quirky bootparams stuff
> from the loader so that we can switch to LoadImage

Yeah, but doing that will allow you to boot directly from firmware on
existing machines, and only one image needs to be chosen at install
time, so it just adds a few MiB to the package. I guess most people will
still use a boot manager or loader that can be easily enhanced to use
LoadImage and the new section, but it would be nice to have the option
to avoid that.

> 
> > >
> > > > Also, the PE header can live anywhere inside the image, right? Is there
> > > > any reason to struggle to shoehorn it into the "boot sector"?
> > >
> > > It cannot. It must live outside a region described by the section headers.
> >
> > It could still be inserted after .setup, or at the very end of the file, no?
> 
> The PE/COFF spec mentions that the COFF header needs to follow the
> signature. Also, the SizeOfHeaders field would become somewhat
> ambiguous if the header is split up like that.


Ah, the definition of SizeOfHeaders doesn't make much sense if the
headers weren't contiguous with the MS-DOS stub. I guess they just
wanted the MS-DOS stub to potentially vary in size, but still want the
header to immediately follow it, drat.
Arvind Sankar Feb. 14, 2020, 12:12 a.m. UTC | #6
On Thu, Feb 13, 2020 at 07:10:49PM -0500, Arvind Sankar wrote:
> On Thu, Feb 13, 2020 at 10:36:14PM +0000, Ard Biesheuvel wrote:
> > On Thu, 13 Feb 2020 at 19:47, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > >
> > > On Thu, Feb 13, 2020 at 05:55:44PM +0000, Ard Biesheuvel wrote:
> > > > On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > > > > As an alternative to the new section, how about having a CONFIG option
> > > > > to emit the 64-bit kernel with a 32-bit PE header instead, which would
> > > > > point to efi32_pe_entry? In that case it could be directly loaded by
> > > > > existing firmware already. You could even have a tool that can mangle an
> > > > > existing bzImage's header from 64-bit to 32-bit, say using the newly
> > > > > added kernel_info structure to record the existence and location of
> > > > > efi32_pe_entry.
> > > > >
> > > >
> > > > That wouldn't work with, say, signed distro kernels.
> > >
> > > No, the idea would be that the distro would distribute two signed
> > > images, one 32-bit and one 64-bit, which are identical except for the
> > > header. At install time, the installer chooses based on the system's
> > > firmware bit-ness.
> > >
> > 
> > I guess it would be possible, but then we'd need two different images
> > while today, we can run the same image on both kinds of firmwares. The
> > only thing I am trying to do is remove all the quirky bootparams stuff
> > from the loader so that we can switch to LoadImage
> 
> Yeah, but doing that will allow you to boot directly from firmware on
> existing machines, and only one image needs to be chosen at install
> time, so it just adds a few MiB to the package. I guess most people will
> still use a boot manager or loader that can be easily enhanced to use
> LoadImage and the new section, but it would be nice to have the option
> to avoid that.

Also not quite today, right? You still need this patchset and the
modifications to bootloaders to get away with one image.
Ard Biesheuvel Feb. 14, 2020, 12:21 a.m. UTC | #7
On Fri, 14 Feb 2020 at 01:13, Arvind Sankar <nivedita@alum.mit.edu> wrote:
>
> On Thu, Feb 13, 2020 at 07:10:49PM -0500, Arvind Sankar wrote:
> > On Thu, Feb 13, 2020 at 10:36:14PM +0000, Ard Biesheuvel wrote:
> > > On Thu, 13 Feb 2020 at 19:47, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > > >
> > > > On Thu, Feb 13, 2020 at 05:55:44PM +0000, Ard Biesheuvel wrote:
> > > > > On Thu, 13 Feb 2020 at 18:53, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> > > > > > As an alternative to the new section, how about having a CONFIG option
> > > > > > to emit the 64-bit kernel with a 32-bit PE header instead, which would
> > > > > > point to efi32_pe_entry? In that case it could be directly loaded by
> > > > > > existing firmware already. You could even have a tool that can mangle an
> > > > > > existing bzImage's header from 64-bit to 32-bit, say using the newly
> > > > > > added kernel_info structure to record the existence and location of
> > > > > > efi32_pe_entry.
> > > > > >
> > > > >
> > > > > That wouldn't work with, say, signed distro kernels.
> > > >
> > > > No, the idea would be that the distro would distribute two signed
> > > > images, one 32-bit and one 64-bit, which are identical except for the
> > > > header. At install time, the installer chooses based on the system's
> > > > firmware bit-ness.
> > > >
> > >
> > > I guess it would be possible, but then we'd need two different images
> > > while today, we can run the same image on both kinds of firmwares. The
> > > only thing I am trying to do is remove all the quirky bootparams stuff
> > > from the loader so that we can switch to LoadImage
> >
> > Yeah, but doing that will allow you to boot directly from firmware on
> > existing machines, and only one image needs to be chosen at install
> > time, so it just adds a few MiB to the package. I guess most people will
> > still use a boot manager or loader that can be easily enhanced to use
> > LoadImage and the new section, but it would be nice to have the option
> > to avoid that.


I see the value of having a 64-bit image that can boot natively on
32-bit firmware, but I am not expecting any buy in from the distros
for this scheme.


>
> Also not quite today, right? You still need this patchset and the
> modifications to bootloaders to get away with one image.

Sure. But we already have mixed mode support today that doesn't
require this, so it's going to be a difficult sell to switch to a new
scheme that requires infrastructure to distribute different kernels,
and logic to choose between the two.

A generic EFI bootloader/firmware will need to implement the initrd
loadfile2 protocol as well, so some Linux specific features will need
to be implemented anyway. This series is intended to ensure that mixed
mode doesn't get left behind, even though very few people use it
today.
Arvind Sankar Feb. 14, 2020, 12:38 a.m. UTC | #8
On Fri, Feb 14, 2020 at 12:21:30AM +0000, Ard Biesheuvel wrote:
> 
> 
> I see the value of having a 64-bit image that can boot natively on
> 32-bit firmware, but I am not expecting any buy in from the distros
> for this scheme.
> 

Ok.