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