Am 1. Oktober 2020 23:19:08 MESZ schrieb Ard Biesheuvel ardb@kernel.org:
On Thu, 1 Oct 2020 at 22:30, Simon Glass sjg@chromium.org wrote:
Hi Grant,
[who is 'nd'?]
That would be our bot who kindly omits of the obnoxious email footer on outgoing email if cc'ed.
On Wed, 30 Sep 2020 at 09:26, Grant Likely grant.likely@arm.com
wrote:
Hi Simon,
Heinrich provided some great answers on the technical details. I'll
try
not to retrace his answers, but there are a few conceptual model
bits
that I think are worth talking about...
On 28/09/2020 17:51, Simon Glass wrote:
Hi,
I thought perhaps it might be worth starting a thread on this, as despite Grant and Heinrich kinding spending a bit of time talking about this, I am still very much in the dark about how 'embedded'
and
distro/other boot flows are going to come together with EBBR. Of course this would be easier f2f.
I'll assume for both of the cases you describe below that the UEFI
boot
path (bootefi) is used instead of the traditional bootm.
Well in fact it is either bootm (ARM + x86) or zboot (x86) depending on what we are talking about.
Case 1: Firmware loads the kernel to a particular address, selects DT and boots it. The kernel may require EFI boot services, or may not,
but in
the general case the firmware provides them.
Case 2: Firmware loads EFI app and provides EFI boot services to it. How
the
system actually boots is under control of the app.
As far as bootefi is concerned, case 1 & case 2 are identical. The
image
to be run is loaded into memory and bootefi executes the payload.
In
case 1 the payload in Linux's built-in UEFI stub. In case 2 the
payload
is grub.efi, shell.efi, shim.efi, etc.
bootefi differs from bootm in that instead of a bare vmlinuz image
or a
FIT image, the loaded image is a PECOFF executable. U=Boot parses
the
PECOFF headers, loads the sections into memory and jumps to the
entry
point. It also leaves a pointer to an in-memory information table
with
callbacks into firmware functions for accessing basic services
(console,
block device, network, graphics, filesystem, etc).
If the image is Linux, then the stub will do a small amount of
setup
before calling ExitBootServices(), which tells firmware to stop
managing
hardware because the kernel is taking over. (In UEFI terms, this is
call
an "OS loader" image)
If the image is a transient binary like Grub or the UEFI shell, it
can
use firmware facilities to load additional files (e.g., initrd,
kernel,
etc) before jumping into another PE/COFF binary. For example, Grub
will
typically load the kernel into memory, which itself is a PE/COFF
image,
and then jump to its entry point.
Yes. I think this is the source of the pain, since there is really no limit on what these can do and there is no standard flow. Much of the loading happens outside the control of the bootloader, which means that verified boot and testing are harder.
If an image doesn't call ExitBootServices(), and instead exits,
control
returns to firmware as one would expect.
I feel that a lot of the confusion about verified boot, DT
selections,
boot menus, etc. is coming from the introduction of an EFI app
which
has no specification (it can be grub, shim or something else, as
I
understand it). Certainly this is very flexible and future-proof,
but
it is also arbitrarily complex, unpredictable and hard to secure.
Of the items you've listed above, DT selection does indeed need
work,
but the rest is quite well specified. The UEFI spec has very clear specifications on how the boot image is selected via the BOOTxxxx variables and the format of the binaries. Secure Boot is also standardized so that when Secure Boot is turned on, firmware will
only
execute images signed by a recognized key.
It is no more arbitrarily complex than booting an OS kernel. When compared against bootm, both boot flows load an image into memory,
and
both jump to the image starting point. In both cases the OS can do whatever it wants after the firmware jumps into it, and in both
cases if
security is enabled then the binaries must be signed.
I think you are missing my point. With bootm etc. it jumps straight from U-Boot to linux. Of course linux may have an EFI shim, but there is no grub or any other loader 'in the way'.
Where UEFI differs is that it continues to offer
{console,net,block,fs}
services for as long as the image chooses to use them so that early
boot
code doesn't need to carry its own implementations.
Yes. Certainly having a standard way to output console text is useful early in linux for debugging. I'm not too sure about the others though, in the simple case.
Another advantage of EFI boot is that it allows us to avoid putting Linux specific knowledge into the bootloader, which is typically different for every architecture:
- On x86, the location of the initrd and the commandline are passed
via the boot_params struct, and where these may be loaded in memory is different between i686 and x86_64, differs between Linux versions, and is also dependent on whether the kernel is KASLR capable or not. Where the boot_params struct itself may be loaded in memory also varies.
- on ARM and arm64 the location of the initrd and the contents of the
command line are passed via DT - where the initrd may be loaded is arch specific, and the DT needs to be in lowmem (but the size of lowmem depends on your kernel config)
- on ARM, the zImage must be placed within 128 MB of the start of DRAM
- on arm64, the kernel Image must be aligned to 2 MB in memory, unless
it is a KASLR kernel, in which case 64 KB is sufficient
- KASLR seeds are passed in different ways
- RISC-V yay!
- etc etc
We have managed to move all this complex policy into the EFI stub for Linux, which only needs the EFI APIs, which are rigorously specified and documented, and vary very little between architectures. We have defined an arch-agnostic initrd EFI protocol on the Linux side, which the bootloader can expose (and which is already implemented by uboot), and provides a natural hook for attestation/measurement.
As a result, we have defined a generic EFI based platform, where secure boot and measured boot, and perhaps even firmware update can be implemented according to an industry spec, and the resulting code should deviate very little (if at all) between all these architectures.
The architecture specific quirks that I am aware of are:
* State of caches and MMU * Boot hart ID in device tree for RISC-V * Exception level (EL2, S-mode) * Restrictions on the memory map * Calling conventions * PE-COFF file format depending on 32/64 bit
Best regards
Heinrich