diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2022-09-09 13:12:04 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2022-11-16 12:52:58 +0100 |
commit | 8fec4f95be7a323410f9853b6773c810ba6c7152 (patch) | |
tree | 15458fe3c17e74c6a4d499b07fa82cf4a35e7c05 /src/boot | |
parent | Merge pull request #24555 from medhefgo/bootctl (diff) | |
download | systemd-8fec4f95be7a323410f9853b6773c810ba6c7152.tar.xz systemd-8fec4f95be7a323410f9853b6773c810ba6c7152.zip |
boot: improve support for qemu
systemd-boot expects being loaded from ESP and is quite unhappy in case
the loaded image device path is something else. When running on qemu
this can easily happen though. Case one is direct kernel boot, i.e.
loading via 'qemu -kernel systemd-bootx64.efi'. Case two is sd-boot
being added to the ovmf firmware image and being loaded from there.
This patch detects both cases and goes inspect all file systems known to
the firmware, trying to find the ESP. When present the
VMMBootOrderNNNN variables are used to inspect the file systems in the
given order.
Diffstat (limited to 'src/boot')
-rw-r--r-- | src/boot/efi/boot.c | 10 | ||||
-rw-r--r-- | src/boot/efi/meson.build | 1 | ||||
-rw-r--r-- | src/boot/efi/vmm.c | 130 | ||||
-rw-r--r-- | src/boot/efi/vmm.h | 8 |
4 files changed, 148 insertions, 1 deletions
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 84f4cc11a3..76023b14ca 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -16,6 +16,7 @@ #include "linux.h" #include "measure.h" #include "pe.h" +#include "vmm.h" #include "random-seed.h" #include "secure-boot.h" #include "shim.h" @@ -2646,6 +2647,13 @@ static void config_load_all_entries( config_default_entry_select(config); } +static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE **ret_dir) { + if (is_direct_boot(loaded_image->DeviceHandle)) + return vmm_open(&loaded_image->DeviceHandle, ret_dir); + else + return open_volume(loaded_image->DeviceHandle, ret_dir); +} + EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { EFI_LOADED_IMAGE_PROTOCOL *loaded_image; _cleanup_(file_closep) EFI_FILE *root_dir = NULL; @@ -2682,7 +2690,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { export_variables(loaded_image, loaded_image_path, init_usec); - err = open_volume(loaded_image->DeviceHandle, &root_dir); + err = discover_root_dir(loaded_image, &root_dir); if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Unable to open root directory: %r", err); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 395386d3ed..0de43993a4 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -389,6 +389,7 @@ systemd_boot_sources = files( 'boot.c', 'drivers.c', 'random-seed.c', + 'vmm.c', 'shim.c', 'xbootldr.c', ) diff --git a/src/boot/efi/vmm.c b/src/boot/efi/vmm.c new file mode 100644 index 0000000000..b1bfd778fc --- /dev/null +++ b/src/boot/efi/vmm.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <efi.h> +#include <efilib.h> +#include <stdbool.h> + +#include "drivers.h" +#include "efi-string.h" +#include "string-util-fundamental.h" +#include "util.h" + +#define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \ + { 0x1428f772, 0xb64a, 0x441e, {0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 }} + +#define VMM_BOOT_ORDER_GUID \ + { 0x668f4529, 0x63d0, 0x4bb5, {0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a }} + +/* detect direct boot */ +bool is_direct_boot(EFI_HANDLE device) { + EFI_STATUS err; + VENDOR_DEVICE_PATH *dp; + + err = BS->HandleProtocol(device, &DevicePathProtocol, (void **) &dp); + if (err != EFI_SUCCESS) + return false; + + /* 'qemu -kernel systemd-bootx64.efi' */ + if (dp->Header.Type == MEDIA_DEVICE_PATH && + dp->Header.SubType == MEDIA_VENDOR_DP && + memcmp(&dp->Guid, &(EFI_GUID)QEMU_KERNEL_LOADER_FS_MEDIA_GUID, sizeof(EFI_GUID)) == 0) + return true; + + /* loaded from firmware volume (sd-boot added to ovmf) */ + if (dp->Header.Type == MEDIA_DEVICE_PATH && + dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP) + return true; + + return false; +} + +static bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) { + if (!start) + return true; + if (!dp) + return false; + for (;;) { + if (IsDevicePathEnd(start)) + return true; + if (IsDevicePathEnd(dp)) + return false; + size_t l1 = DevicePathNodeLength(start); + size_t l2 = DevicePathNodeLength(dp); + if (l1 != l2) + return false; + if (memcmp(dp, start, l1) != 0) + return false; + start = NextDevicePathNode(start); + dp = NextDevicePathNode(dp); + } +} + +/* + * Try find ESP when not loaded from ESP + * + * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are + * present they are used to inspect the filesystems in the specified order. When nothing was found or the + * variables are not present the function will do one final search pass over all filesystems. + * + * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu + * command line) in VMMBootOrderNNNN. The variables contain a device path. + * + * Example qemu command line: + * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1 + * + * Resulting variable: + * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0) + */ +EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles; + EFI_STATUS err, dp_err; + + assert(ret_vmm_dev); + assert(ret_vmm_dir); + + /* find all file system handles */ + err = BS->LocateHandleBuffer(ByProtocol, &FileSystemProtocol, NULL, &n_handles, &handles); + if (err != EFI_SUCCESS) + return err; + + for (size_t order = 0;; order++) { + _cleanup_free_ EFI_DEVICE_PATH *dp = NULL; + char16_t order_str[STRLEN("VMMBootOrder") + 4 + 1]; + + SPrint(order_str, sizeof(order_str), u"VMMBootOrder%04x", order); + dp_err = efivar_get_raw(&(EFI_GUID)VMM_BOOT_ORDER_GUID, order_str, (char**)&dp, NULL); + + for (size_t i = 0; i < n_handles; i++) { + _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL; + EFI_DEVICE_PATH *fs; + + err = BS->HandleProtocol(handles[i], &DevicePathProtocol, (void **) &fs); + if (err != EFI_SUCCESS) + return err; + + /* check against VMMBootOrderNNNN (if set) */ + if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp)) + continue; + + err = open_volume(handles[i], &root_dir); + if (err != EFI_SUCCESS) + continue; + + /* simple ESP check */ + err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI", + EFI_FILE_MODE_READ, + EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY); + if (err != EFI_SUCCESS) + continue; + + *ret_vmm_dev = handles[i]; + *ret_vmm_dir = TAKE_PTR(root_dir); + return EFI_SUCCESS; + } + + if (dp_err != EFI_SUCCESS) + return EFI_NOT_FOUND; + } + assert_not_reached(); +} diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h new file mode 100644 index 0000000000..7bac1a324a --- /dev/null +++ b/src/boot/efi/vmm.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <efi.h> +#include <efilib.h> + +bool is_direct_boot(EFI_HANDLE device); +EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir); |