1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-11 05:17:44 +03:00

stub: Use LoadImage/StartImage to start the kernel

This is the proper way to start any EFI binary. The fact this even ever
worked was because the kernel does not have any PE relocations.

The only downside is that the embedded kernel image has to be signed and
trusted by the firmware under secure boot. A future commit will try to
deal with that.
This commit is contained in:
Jan Janssen 2022-09-21 11:07:53 +02:00
parent bfc075fa6b
commit a529d8182e
2 changed files with 58 additions and 111 deletions

View File

@ -16,80 +16,41 @@
#include "pe.h"
#include "util.h"
static EFI_LOADED_IMAGE_PROTOCOL *loaded_image_free(EFI_LOADED_IMAGE_PROTOCOL *img) {
if (!img)
return NULL;
mfree(img->LoadOptions);
return mfree(img);
}
#define STUB_PAYLOAD_GUID \
{ 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } }
static EFI_STATUS loaded_image_register(
const char *cmdline, UINTN cmdline_len,
const void *linux_buffer, UINTN linux_length,
EFI_HANDLE *ret_image) {
EFI_LOADED_IMAGE_PROTOCOL *loaded_image = NULL;
EFI_STATUS err;
assert(cmdline || cmdline_len > 0);
assert(linux_buffer && linux_length > 0);
EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) {
assert(parent);
assert(source);
assert(ret_image);
/* create and install new LoadedImage Protocol */
loaded_image = xnew(EFI_LOADED_IMAGE_PROTOCOL, 1);
*loaded_image = (EFI_LOADED_IMAGE_PROTOCOL) {
.ImageBase = (void *) linux_buffer,
.ImageSize = linux_length
/* We could pass a NULL device path, but it's nicer to provide something. */
struct {
VENDOR_DEVICE_PATH payload;
EFI_DEVICE_PATH end;
} _packed_ payload_device_path = {
.payload = {
.Header = {
.Type = MEDIA_DEVICE_PATH,
.SubType = MEDIA_VENDOR_DP,
.Length = { sizeof(payload_device_path.payload), 0 },
},
.Guid = STUB_PAYLOAD_GUID,
},
.end = {
.Type = END_DEVICE_PATH_TYPE,
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
.Length = { sizeof(payload_device_path.end), 0 },
},
};
/* if a cmdline is set convert it to UCS2 */
if (cmdline) {
loaded_image->LoadOptions = xstra_to_str(cmdline);
loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
}
/* install a new LoadedImage protocol. ret_handle is a new image handle */
err = BS->InstallMultipleProtocolInterfaces(
ret_image,
&LoadedImageProtocol, loaded_image,
NULL);
if (err != EFI_SUCCESS)
loaded_image = loaded_image_free(loaded_image);
return err;
}
static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) {
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
EFI_STATUS err;
if (!loaded_image_handle)
return EFI_SUCCESS;
/* get the LoadedImage protocol that we allocated earlier */
err = BS->OpenProtocol(
loaded_image_handle, &LoadedImageProtocol, (void **) &loaded_image,
NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
return err;
/* close the handle */
(void) BS->CloseProtocol(loaded_image_handle, &LoadedImageProtocol, NULL, NULL);
err = BS->UninstallMultipleProtocolInterfaces(
loaded_image_handle,
&LoadedImageProtocol, loaded_image,
NULL);
if (err != EFI_SUCCESS)
return err;
loaded_image_handle = NULL;
loaded_image = loaded_image_free(loaded_image);
return EFI_SUCCESS;
}
static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) {
(void) loaded_image_unregister(*loaded_image_handle);
*loaded_image_handle = NULL;
return BS->LoadImage(
/*BootPolicy=*/false,
parent,
&payload_device_path.payload.Header,
(void *) source,
len,
ret_image);
}
EFI_STATUS linux_exec(
@ -98,11 +59,7 @@ EFI_STATUS linux_exec(
const void *linux_buffer, UINTN linux_length,
const void *initrd_buffer, UINTN initrd_length) {
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
_cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL;
uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address;
EFI_IMAGE_ENTRY_POINT kernel_entry;
void *new_buffer;
uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address = 0;
EFI_STATUS err;
assert(parent);
@ -126,46 +83,36 @@ EFI_STATUS linux_exec(
initrd_length);
#endif
if (err != EFI_SUCCESS)
return err;
/* sanity check */
assert(kernel_size_of_image >= linux_length);
return log_error_status_stall(err, u"Bad kernel image: %r", err);
/* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
Code, (static) Data and Headers.
Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
*/
/* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
_cleanup_pages_ Pages kernel = xmalloc_pages(
AllocateAnyPages,
EfiLoaderCode,
EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment),
0);
new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
if (!new_buffer) /* Silence gcc 11.2.0, assert(new_buffer) doesn't work. */
return EFI_OUT_OF_RESOURCES;
memcpy(new_buffer, linux_buffer, linux_length);
/* zero out rest of memory (probably not needed, but BSS section should be 0) */
memset((uint8_t *)new_buffer + linux_length, 0, kernel_size_of_image - linux_length);
/* get the entry point inside the relocated kernel */
kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *)new_buffer + kernel_entry_address);
/* register a LoadedImage Protocol in order to pass on the commandline */
err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle);
_cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL;
err = load_image(parent, linux_buffer, linux_length, &kernel_image);
if (err != EFI_SUCCESS)
return err;
return log_error_status_stall(err, u"Error loading kernel image: %r", err);
/* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err);
if (cmdline) {
loaded_image->LoadOptions = xstra_to_str(cmdline);
loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
}
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
if (err != EFI_SUCCESS)
return err;
return log_error_status_stall(err, u"Error registering initrd: %r", err);
/* call the kernel */
return kernel_entry(loaded_image_handle, ST);
err = BS->StartImage(kernel_image, NULL, NULL);
/* Try calling the kernel compat entry point if one exists. */
if (err == EFI_UNSUPPORTED && kernel_entry_address > 0) {
EFI_IMAGE_ENTRY_POINT compat_entry =
(EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + kernel_entry_address);
err = compat_entry(kernel_image, ST);
}
return log_error_status_stall(err, u"Error starting kernel image: %r", err);
}

View File

@ -378,5 +378,5 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
graphics_mode(false);
return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err);
return err;
}