mbox series

[v2,0/6] efi: implement generic compressed boot support

Message ID 20220809080944.1119654-1-ardb@kernel.org
Headers show
Series efi: implement generic compressed boot support | expand

Message

Ard Biesheuvel Aug. 9, 2022, 8:09 a.m. UTC
Relatively modern architectures such as arm64 or RISC-V don't implement
a self-decompressing kernel, and leave it up to the bootloader to
decompress the compressed image before executing it. For bare metal
boot, this policy makes sense, as a self-decompressing image essentially
duplicates a lot of fiddly preparation work to create a 1:1 mapping and
set up the C runtime, and to discover or infer where DRAM lives from
device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is
called with a 1:1 cached mapping covering all of DRAM already active,
and with a stack, a heap, a memory map and boot services to load and
start images. This means it is rather trivial to implement a
self-decompressing wrapper for EFI boot in a generic manner, and reuse
it across architectures that implement EFI boot.

The only slight downside is that when UEFI secure boot is enabled, the
generic LoadImage/StartImage only allow signed images to be loaded and
started, and we prefer to avoid the need to sign both the inner and
outer PE/COFF images. This series adopts the EFI shim approach, i.e., to
override an internal UEFI/PI protocol that is used by the image loader,
to allow the inner image to be booted after decompression. This has been
tested to work with Tianocore based EFI implementations on arm64, but
u-boot will need some interoperability tweaks as well, ideally just a
protocol that exposes a LoadImage/StartImage combo that the decompresor
can use directly to circumvent the signature check. (Note that EFI apps
have full control over the CPU, page tables, etc. so having code that
circumvents authentication checks is not as crazy as it sounds, given
that the app can do anything it pleases already.)

The code is wired up for arm64 and RISC-V. The latter was build tested
only.

Cc: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
Cc: Matthew Garrett <mjg59@srcf.ucam.org>
Cc: Peter Jones <pjones@redhat.com>
Cc: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Cc: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
Cc: AKASHI Takahiro <takahiro.akashi@linaro.org>
Cc: Palmer Dabbelt <palmer@dabbelt.com>
Cc: Atish Patra <atishp@atishpatra.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Huacai Chen <chenhuacai@loongson.cn>
Cc: Lennart Poettering <lennart@poettering.net>

Ard Biesheuvel (6):
  efi: stub: add some missing boot service prototypes
  efi: stub: split off printk() routines
  efi: stub: move efi_system_table global var into separate object
  efi: stub: implement generic EFI zboot
  arm64: efi: enable generic EFI compressed boot
  riscv: efi: enable generic EFI compressed boot

 arch/arm64/Makefile                            |   5 +
 arch/arm64/boot/Makefile                       |  12 ++
 arch/riscv/Makefile                            |   5 +
 arch/riscv/boot/Makefile                       |  14 ++
 drivers/firmware/efi/Kconfig                   |   9 +
 drivers/firmware/efi/libstub/Makefile          |   7 +-
 drivers/firmware/efi/libstub/Makefile.zboot    |  30 +++
 drivers/firmware/efi/libstub/efi-stub-helper.c | 141 ---------------
 drivers/firmware/efi/libstub/efi-stub.c        |   2 -
 drivers/firmware/efi/libstub/efistub.h         |  12 +-
 drivers/firmware/efi/libstub/printk.c          | 158 ++++++++++++++++
 drivers/firmware/efi/libstub/systable.c        |   8 +
 drivers/firmware/efi/libstub/zboot-header.S    | 144 +++++++++++++++
 drivers/firmware/efi/libstub/zboot.c           | 191 ++++++++++++++++++++
 drivers/firmware/efi/libstub/zboot.lds         |  41 +++++
 include/linux/efi.h                            |   2 +
 16 files changed, 633 insertions(+), 148 deletions(-)
 create mode 100644 drivers/firmware/efi/libstub/Makefile.zboot
 create mode 100644 drivers/firmware/efi/libstub/printk.c
 create mode 100644 drivers/firmware/efi/libstub/systable.c
 create mode 100644 drivers/firmware/efi/libstub/zboot-header.S
 create mode 100644 drivers/firmware/efi/libstub/zboot.c
 create mode 100644 drivers/firmware/efi/libstub/zboot.lds

Comments

Heinrich Schuchardt Aug. 9, 2022, 8:38 a.m. UTC | #1
On 8/9/22 10:09, Ard Biesheuvel wrote:
> Relatively modern architectures such as arm64 or RISC-V don't implement
> a self-decompressing kernel, and leave it up to the bootloader to
> decompress the compressed image before executing it. For bare metal
> boot, this policy makes sense, as a self-decompressing image essentially
> duplicates a lot of fiddly preparation work to create a 1:1 mapping and
> set up the C runtime, and to discover or infer where DRAM lives from
> device trees or other firmware tables.
> 
> For EFI boot, the situation is a bit different: the EFI entrypoint is
> called with a 1:1 cached mapping covering all of DRAM already active,
> and with a stack, a heap, a memory map and boot services to load and
> start images. This means it is rather trivial to implement a
> self-decompressing wrapper for EFI boot in a generic manner, and reuse
> it across architectures that implement EFI boot.
> 
> The only slight downside is that when UEFI secure boot is enabled, the
> generic LoadImage/StartImage only allow signed images to be loaded and
> started, and we prefer to avoid the need to sign both the inner and
> outer PE/COFF images. This series adopts the EFI shim approach, i.e., to
> override an internal UEFI/PI protocol that is used by the image loader,
> to allow the inner image to be booted after decompression. This has been

We should avoid requiring anything that is not in the UEFI 
specification. If you have any additional requirements, please, create a 
change request for the UEFI specification.

Overriding the services of the system table is dangerous and should be 
avoided.

There is no need for two UEFI binaries one inside the other and we 
should avoid such overengineering.

Today we append an uncompressed kernel to the EFI stub. The stub 
relocates it, sets up the memory map and calls it entry point.

Just add decompressor code to the EFI stub and instead of appending an 
uncompressed kernel append a compressed one. Then sign a binary 
consisting of the EFI stub and the compressed kernel.

This way you don't need any change to UEFI firmware at all and you don't 
need to override UEFI services.

Another reasonable approach would be to zip the signed UEFI binary (EFI 
stub with uncompressed kernel) and let the UEFI firmware unzip it and 
check the signature of the decompressed UEFI binary. This would not 
require any patch in Linux at all and would be simple to implement in 
U-Boot.

Best regards

Heinrich

> tested to work with Tianocore based EFI implementations on arm64, but
> u-boot will need some interoperability tweaks as well, ideally just a
> protocol that exposes a LoadImage/StartImage combo that the decompresor
> can use directly to circumvent the signature check. (Note that EFI apps
> have full control over the CPU, page tables, etc. so having code that
> circumvents authentication checks is not as crazy as it sounds, given
> that the app can do anything it pleases already.)
> 
> The code is wired up for arm64 and RISC-V. The latter was build tested
> only.
> 
> Cc: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
> Cc: Matthew Garrett <mjg59@srcf.ucam.org>
> Cc: Peter Jones <pjones@redhat.com>
> Cc: Ilias Apalodimas <ilias.apalodimas@linaro.org>
> Cc: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
> Cc: AKASHI Takahiro <takahiro.akashi@linaro.org>
> Cc: Palmer Dabbelt <palmer@dabbelt.com>
> Cc: Atish Patra <atishp@atishpatra.org>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Huacai Chen <chenhuacai@loongson.cn>
> Cc: Lennart Poettering <lennart@poettering.net>
> 
> Ard Biesheuvel (6):
>    efi: stub: add some missing boot service prototypes
>    efi: stub: split off printk() routines
>    efi: stub: move efi_system_table global var into separate object
>    efi: stub: implement generic EFI zboot
>    arm64: efi: enable generic EFI compressed boot
>    riscv: efi: enable generic EFI compressed boot
> 
>   arch/arm64/Makefile                            |   5 +
>   arch/arm64/boot/Makefile                       |  12 ++
>   arch/riscv/Makefile                            |   5 +
>   arch/riscv/boot/Makefile                       |  14 ++
>   drivers/firmware/efi/Kconfig                   |   9 +
>   drivers/firmware/efi/libstub/Makefile          |   7 +-
>   drivers/firmware/efi/libstub/Makefile.zboot    |  30 +++
>   drivers/firmware/efi/libstub/efi-stub-helper.c | 141 ---------------
>   drivers/firmware/efi/libstub/efi-stub.c        |   2 -
>   drivers/firmware/efi/libstub/efistub.h         |  12 +-
>   drivers/firmware/efi/libstub/printk.c          | 158 ++++++++++++++++
>   drivers/firmware/efi/libstub/systable.c        |   8 +
>   drivers/firmware/efi/libstub/zboot-header.S    | 144 +++++++++++++++
>   drivers/firmware/efi/libstub/zboot.c           | 191 ++++++++++++++++++++
>   drivers/firmware/efi/libstub/zboot.lds         |  41 +++++
>   include/linux/efi.h                            |   2 +
>   16 files changed, 633 insertions(+), 148 deletions(-)
>   create mode 100644 drivers/firmware/efi/libstub/Makefile.zboot
>   create mode 100644 drivers/firmware/efi/libstub/printk.c
>   create mode 100644 drivers/firmware/efi/libstub/systable.c
>   create mode 100644 drivers/firmware/efi/libstub/zboot-header.S
>   create mode 100644 drivers/firmware/efi/libstub/zboot.c
>   create mode 100644 drivers/firmware/efi/libstub/zboot.lds
>
Ard Biesheuvel Aug. 9, 2022, 8:46 a.m. UTC | #2
On Tue, 9 Aug 2022 at 10:38, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
> On 8/9/22 10:09, Ard Biesheuvel wrote:
> > Relatively modern architectures such as arm64 or RISC-V don't implement
> > a self-decompressing kernel, and leave it up to the bootloader to
> > decompress the compressed image before executing it. For bare metal
> > boot, this policy makes sense, as a self-decompressing image essentially
> > duplicates a lot of fiddly preparation work to create a 1:1 mapping and
> > set up the C runtime, and to discover or infer where DRAM lives from
> > device trees or other firmware tables.
> >
> > For EFI boot, the situation is a bit different: the EFI entrypoint is
> > called with a 1:1 cached mapping covering all of DRAM already active,
> > and with a stack, a heap, a memory map and boot services to load and
> > start images. This means it is rather trivial to implement a
> > self-decompressing wrapper for EFI boot in a generic manner, and reuse
> > it across architectures that implement EFI boot.
> >
> > The only slight downside is that when UEFI secure boot is enabled, the
> > generic LoadImage/StartImage only allow signed images to be loaded and
> > started, and we prefer to avoid the need to sign both the inner and
> > outer PE/COFF images. This series adopts the EFI shim approach, i.e., to
> > override an internal UEFI/PI protocol that is used by the image loader,
> > to allow the inner image to be booted after decompression. This has been
>
> We should avoid requiring anything that is not in the UEFI
> specification. If you have any additional requirements, please, create a
> change request for the UEFI specification.
>

As I have explained numerous times before, the EFI spec was intended
to be extensible (hence the 'E'). The ACPI, SMBIOS and TCG specs all
augment the EFI specification by defining protocols, GUIDs and other
things that are only relevant in a EFI context, but none of those are
covered by the EFI spec itself.

> Overriding the services of the system table is dangerous and should be
> avoided.
>

Agreed. But this is not what is happening here.

> There is no need for two UEFI binaries one inside the other and we
> should avoid such overengineering.
>

I disagree. Using an EFI app to encapsulate another one is the only
generic way to go about this, as far as I can tell.

> Today we append an uncompressed kernel to the EFI stub. The stub
> relocates it, sets up the memory map and calls it entry point.
>

Not exactly. On arm64 as well as RISC-V, the EFI stub and the kernel
proper are essentially the same executable image.

> Just add decompressor code to the EFI stub and instead of appending an
> uncompressed kernel append a compressed one. Then sign a binary
> consisting of the EFI stub and the compressed kernel.
>

Yes, this would be a cleaner approach, although it would require more
re-engineering of the EFI stub, in particular, it would require
cloning more code, and adding additional build and link steps.

> This way you don't need any change to UEFI firmware at all and you don't
> need to override UEFI services.
>
> Another reasonable approach would be to zip the signed UEFI binary (EFI
> stub with uncompressed kernel) and let the UEFI firmware unzip it and
> check the signature of the decompressed UEFI binary. This would not
> require any patch in Linux at all and would be simple to implement in
> U-Boot.
>

This is how it works today. One problem with this is that the image
needs to be decompressed in order to sign it, or verify its signature.
In general, having compression at the outside like this is fiddly
because it is no longer a PE/COFF image, and the EFI spec only reasons
about PE/COFF images as executable images. So we'd need to change the
UEFI spec or the PE/COFF spec.
Heinrich Schuchardt Aug. 9, 2022, 9:03 a.m. UTC | #3
On 8/9/22 10:46, Ard Biesheuvel wrote:
> On Tue, 9 Aug 2022 at 10:38, Heinrich Schuchardt
> <heinrich.schuchardt@canonical.com> wrote:
>>
>> On 8/9/22 10:09, Ard Biesheuvel wrote:
>>> Relatively modern architectures such as arm64 or RISC-V don't implement
>>> a self-decompressing kernel, and leave it up to the bootloader to
>>> decompress the compressed image before executing it. For bare metal
>>> boot, this policy makes sense, as a self-decompressing image essentially
>>> duplicates a lot of fiddly preparation work to create a 1:1 mapping and
>>> set up the C runtime, and to discover or infer where DRAM lives from
>>> device trees or other firmware tables.
>>>
>>> For EFI boot, the situation is a bit different: the EFI entrypoint is
>>> called with a 1:1 cached mapping covering all of DRAM already active,
>>> and with a stack, a heap, a memory map and boot services to load and
>>> start images. This means it is rather trivial to implement a
>>> self-decompressing wrapper for EFI boot in a generic manner, and reuse
>>> it across architectures that implement EFI boot.
>>>
>>> The only slight downside is that when UEFI secure boot is enabled, the
>>> generic LoadImage/StartImage only allow signed images to be loaded and
>>> started, and we prefer to avoid the need to sign both the inner and
>>> outer PE/COFF images. This series adopts the EFI shim approach, i.e., to
>>> override an internal UEFI/PI protocol that is used by the image loader,
>>> to allow the inner image to be booted after decompression. This has been
>>
>> We should avoid requiring anything that is not in the UEFI
>> specification. If you have any additional requirements, please, create a
>> change request for the UEFI specification.
>>
> 
> As I have explained numerous times before, the EFI spec was intended
> to be extensible (hence the 'E'). The ACPI, SMBIOS and TCG specs all
> augment the EFI specification by defining protocols, GUIDs and other
> things that are only relevant in a EFI context, but none of those are
> covered by the EFI spec itself.
> 
>> Overriding the services of the system table is dangerous and should be
>> avoided.
>>
> 
> Agreed. But this is not what is happening here.
> 
>> There is no need for two UEFI binaries one inside the other and we
>> should avoid such overengineering.
>>
> 
> I disagree. Using an EFI app to encapsulate another one is the only
> generic way to go about this, as far as I can tell.

Please, elaborate why the inner compressed binary needs to be UEFI to 
boot into Linux while currently we don't need a an uncompressed inner 
UEFI binary.

> 
>> Today we append an uncompressed kernel to the EFI stub. The stub
>> relocates it, sets up the memory map and calls it entry point.
>>
> 
> Not exactly. On arm64 as well as RISC-V, the EFI stub and the kernel
> proper are essentially the same executable image.

Currently on ARM and RISC-V you have a file with two entry points:

* EFI stub
* legacy entry point

The EFI stub calls the legacy entry point. In the EFI case some part of 
the EFI stub lives on at runtime. The same pointers passed to the legacy 
entry point could also be passed to a decompressed legacy kernel.

The EFI stub and the kernel should be completely separate binaries. Then 
you just need the cp command to join them.

> 
>> Just add decompressor code to the EFI stub and instead of appending an
>> uncompressed kernel append a compressed one. Then sign a binary
>> consisting of the EFI stub and the compressed kernel.
>>
> 
> Yes, this would be a cleaner approach, although it would require more
> re-engineering of the EFI stub, in particular, it would require
> cloning more code, and adding additional build and link steps.

If this is the cleanest approach, we should go for it.

Best regards

Heinrich

> 
>> This way you don't need any change to UEFI firmware at all and you don't
>> need to override UEFI services.
>>
>> Another reasonable approach would be to zip the signed UEFI binary (EFI
>> stub with uncompressed kernel) and let the UEFI firmware unzip it and
>> check the signature of the decompressed UEFI binary. This would not
>> require any patch in Linux at all and would be simple to implement in
>> U-Boot.
>>
> 
> This is how it works today. One problem with this is that the image
> needs to be decompressed in order to sign it, or verify its signature.
> In general, having compression at the outside like this is fiddly
> because it is no longer a PE/COFF image, and the EFI spec only reasons
> about PE/COFF images as executable images. So we'd need to change the
> UEFI spec or the PE/COFF spec.