teknoraver / rpms / systemd

Forked from rpms/systemd 2 months ago
Clone

Blame SOURCES/0150-boot-improve-support-for-qemu.patch

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