]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: use EFI_DISK_IO_PROTOCOL instead of EFI_BLOCK_IO_PROTOCOL for disk reads
authorDaan De Meyer <daan@amutable.com>
Mon, 30 Mar 2026 18:59:09 +0000 (18:59 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sat, 4 Apr 2026 18:03:44 +0000 (20:03 +0200)
EFI_DISK_IO_PROTOCOL (UEFI spec section 13.7,
https://uefi.org/specs/UEFI/2.10/13_Protocols_Media_Access.html#disk-i-o-protocol)
supports reads at arbitrary byte offsets with no alignment requirements on the
buffer. The UEFI spec mandates that firmware produces this protocol on every
handle that also has EFI_BLOCK_IO_PROTOCOL, so it is always available.

This is a better fit than EFI_BLOCK_IO_PROTOCOL for our GPT parsing and
BitLocker detection because Block I/O requires that both the read offset (LBA)
and the buffer are aligned to the media's IoAlign value. Meeting that
constraint forces us to use xmalloc_aligned_pages() with
PHYSICAL_ADDRESS_TO_POINTER(), page-granularity allocations, and manual size
rounding (ALIGN_TO). Disk I/O handles all of that internally, so callers can
use plain xmalloc() or even stack buffers and read exactly the number of bytes
they need.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/boot/boot.c
src/boot/part-discovery.c
src/boot/proto/disk-io.h [new file with mode: 0644]

index 904f9bf589457cc871dcf6a396a6969263c5a36d..2492f474b405b8f569163625d39db53ac5ba5129 100644 (file)
@@ -20,6 +20,7 @@
 #include "part-discovery.h"
 #include "pe.h"
 #include "proto/block-io.h"
+#include "proto/disk-io.h"
 #include "proto/load-file.h"
 #include "proto/simple-text-io.h"
 #include "random-seed.h"
@@ -2165,22 +2166,19 @@ static EFI_STATUS call_boot_windows_bitlocker(const BootEntry *entry, EFI_FILE *
                 if (err != EFI_SUCCESS || block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096)
                         continue;
 
-                #define BLOCK_IO_BUFFER_SIZE 4096
-                _cleanup_pages_ Pages buf_pages = xmalloc_aligned_pages(
-                        AllocateMaxAddress,
-                        EfiLoaderData,
-                        EFI_SIZE_TO_PAGES(BLOCK_IO_BUFFER_SIZE),
-                        block_io->Media->IoAlign,
-                        /* On 32-bit allocate below 4G boundary as we can't easily access anything above that.
-                         * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere.
-                         * addr= */ UINTPTR_MAX);
-                char *buf = PHYSICAL_ADDRESS_TO_POINTER(buf_pages.addr);
-
-                err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, /* LBA= */ 0, BLOCK_IO_BUFFER_SIZE, buf);
+                EFI_DISK_IO_PROTOCOL *disk_io;
+                err = BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io);
+                if (err != EFI_SUCCESS) {
+                        log_debug_status(err, "Failed to get disk I/O protocol: %m");
+                        continue;
+                }
+
+                char buf[STRLEN("-FVE-FS-")];
+                err = disk_io->ReadDisk(disk_io, block_io->Media->MediaId, /* Offset= */ 3, sizeof(buf), buf);
                 if (err != EFI_SUCCESS)
                         continue;
 
-                if (memcmp(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
+                if (memcmp(buf, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
                         found = true;
                         break;
                 }
index 25f60521acf30b4aa5881a0766ec1c826518f15f..60959d4f78453eb5e2162f24dc2b6faa28c22426 100644 (file)
@@ -4,6 +4,7 @@
 #include "part-discovery.h"
 #include "proto/block-io.h"
 #include "proto/device-path.h"
+#include "proto/disk-io.h"
 #include "util.h"
 
 typedef struct {
@@ -72,80 +73,60 @@ static bool verify_gpt(/* const */ GptHeader *h, EFI_LBA lba_expected) {
 
 static EFI_STATUS try_gpt(
                 const EFI_GUID *type,
-                EFI_BLOCK_IO_PROTOCOL *block_io,
+                EFI_DISK_IO_PROTOCOL *disk_io,
+                uint32_t media_id,
+                uint32_t block_size,
                 EFI_LBA lba,
                 EFI_LBA *ret_backup_lba, /* May be changed even on error! */
                 HARDDRIVE_DEVICE_PATH *ret_hd) {
 
-        EFI_PARTITION_ENTRY *entries;
-        _cleanup_pages_ Pages gpt_pages = {};
-        _cleanup_pages_ Pages entries_pages = {};
-        GptHeader *gpt;
+        GptHeader gpt;
         EFI_STATUS err;
         uint32_t crc32;
         size_t size;
 
-        assert(block_io);
-        assert(block_io->Media);
+        assert(disk_io);
         assert(ret_hd);
 
-        gpt_pages = xmalloc_aligned_pages(
-                AllocateMaxAddress,
-                EfiLoaderData,
-                EFI_SIZE_TO_PAGES(sizeof(GptHeader)),
-                block_io->Media->IoAlign,
-                /* On 32-bit allocate below 4G boundary as we can't easily access anything above that.
-                 * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere.
-                 * addr= */ UINTPTR_MAX);
-        gpt = PHYSICAL_ADDRESS_TO_POINTER(gpt_pages.addr);
-
         /* Read the GPT header */
-        err = block_io->ReadBlocks(
-                        block_io,
-                        block_io->Media->MediaId,
-                        lba,
-                        sizeof(*gpt), gpt);
+        uint64_t offset;
+        if (!MUL_SAFE(&offset, lba, block_size))
+                return EFI_INVALID_PARAMETER;
+
+        err = disk_io->ReadDisk(disk_io, media_id, offset, sizeof(gpt), &gpt);
         if (err != EFI_SUCCESS)
                 return err;
 
         /* Indicate the location of backup LBA even if the rest of the header is corrupt. */
         if (ret_backup_lba)
-                *ret_backup_lba = gpt->AlternateLBA;
+                *ret_backup_lba = gpt.AlternateLBA;
 
-        if (!verify_gpt(gpt, lba))
+        if (!verify_gpt(&gpt, lba))
                 return EFI_NOT_FOUND;
 
         /* Now load the GPT entry table */
-        size = ALIGN_TO((size_t) gpt->SizeOfPartitionEntry * (size_t) gpt->NumberOfPartitionEntries, 512);
+        size = (size_t) gpt.SizeOfPartitionEntry * (size_t) gpt.NumberOfPartitionEntries;
         if (size == SIZE_MAX) /* overflow check */
                 return EFI_OUT_OF_RESOURCES;
-        entries_pages = xmalloc_aligned_pages(
-                AllocateMaxAddress,
-                EfiLoaderData,
-                EFI_SIZE_TO_PAGES(size),
-                block_io->Media->IoAlign,
-                /* On 32-bit allocate below 4G boundary as we can't easily access anything above that.
-                 * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere.
-                 * addr= */ UINTPTR_MAX);
-        entries = PHYSICAL_ADDRESS_TO_POINTER(entries_pages.addr);
-
-        err = block_io->ReadBlocks(
-                        block_io,
-                        block_io->Media->MediaId,
-                        gpt->PartitionEntryLBA,
-                        size, entries);
+
+        _cleanup_free_ void *entries = xmalloc(size);
+
+        if (!MUL_SAFE(&offset, gpt.PartitionEntryLBA, block_size))
+                return EFI_INVALID_PARAMETER;
+
+        err = disk_io->ReadDisk(disk_io, media_id, offset, size, entries);
         if (err != EFI_SUCCESS)
                 return err;
 
         /* Calculate CRC of entries array, too */
         err = BS->CalculateCrc32(entries, size, &crc32);
-        if (err != EFI_SUCCESS || crc32 != gpt->PartitionEntryArrayCRC32)
+        if (err != EFI_SUCCESS || crc32 != gpt.PartitionEntryArrayCRC32)
                 return EFI_CRC_ERROR;
 
         /* Now we can finally look for xbootloader partitions. */
-        for (size_t i = 0; i < gpt->NumberOfPartitionEntries; i++) {
+        for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) {
                 EFI_PARTITION_ENTRY *entry =
-                                (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt->SizeOfPartitionEntry * i);
+                                (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.SizeOfPartitionEntry * i);
 
                 if (!efi_guid_equal(&entry->PartitionTypeGUID, type))
                         continue;
@@ -184,7 +165,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI
         EFI_DEVICE_PATH *partition_path;
         err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &partition_path);
         if (err != EFI_SUCCESS)
-                return err;
+                return log_debug_status(err, "Failed to get device path: %m");
 
         /* Find the (last) partition node itself. */
         EFI_DEVICE_PATH *part_node = NULL;
@@ -196,8 +177,10 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI
                 part_node = node;
         }
 
-        if (!part_node)
+        if (!part_node) {
+                log_debug("No hard drive device path node found.");
                 return EFI_NOT_FOUND;
+        }
 
         /* Chop off the partition part, leaving us with the full path to the disk itself. */
         _cleanup_free_ EFI_DEVICE_PATH *disk_path = NULL;
@@ -207,7 +190,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI
         EFI_BLOCK_IO_PROTOCOL *block_io;
         err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &p, &disk_handle);
         if (err != EFI_SUCCESS)
-                return err;
+                return log_debug_status(err, "Failed to locate disk device: %m");
 
         /* The drivers for other partitions on this drive may not be initialized on fastboot firmware, so we
          * have to ask the firmware to do just that. */
@@ -215,16 +198,22 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI
 
         err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io);
         if (err != EFI_SUCCESS)
-                return err;
+                return log_debug_status(err, "Failed to get block I/O protocol: %m");
 
         /* Filter out some block devices early. (We only care about block devices that aren't
          * partitions themselves — we look for GPT partition tables to parse after all —, and only
          * those which contain a medium and have at least 2 blocks.) */
         if (block_io->Media->LogicalPartition ||
             !block_io->Media->MediaPresent ||
-            block_io->Media->LastBlock <= 1)
+            block_io->Media->LastBlock <= 1 ||
+            block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096)
                 return EFI_NOT_FOUND;
 
+        EFI_DISK_IO_PROTOCOL *disk_io;
+        err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io);
+        if (err != EFI_SUCCESS)
+                return log_debug_status(err, "Failed to get disk I/O protocol: %m");
+
         /* Try several copies of the GPT header, in case one is corrupted */
         EFI_LBA backup_lba = 0;
         for (size_t nr = 0; nr < 3; nr++) {
@@ -243,7 +232,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI
                         continue;
 
                 HARDDRIVE_DEVICE_PATH hd;
-                err = try_gpt(type, block_io, lba,
+                err = try_gpt(type, disk_io, block_io->Media->MediaId, block_io->Media->BlockSize, lba,
                         nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */
                         &hd);
                 if (err != EFI_SUCCESS) {
diff --git a/src/boot/proto/disk-io.h b/src/boot/proto/disk-io.h
new file mode 100644 (file)
index 0000000..d758a8e
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_DISK_IO_PROTOCOL_GUID \
+        GUID_DEF(0xCE345171, 0xBA0B, 0x11d2, 0x8e, 0x4F, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+
+typedef struct EFI_DISK_IO_PROTOCOL EFI_DISK_IO_PROTOCOL;
+struct EFI_DISK_IO_PROTOCOL {
+        uint64_t Revision;
+        EFI_STATUS (EFIAPI *ReadDisk)(
+                        EFI_DISK_IO_PROTOCOL *This,
+                        uint32_t MediaId,
+                        uint64_t Offset,
+                        size_t BufferSize,
+                        void *Buffer);
+        EFI_STATUS (EFIAPI *WriteDisk)(
+                        EFI_DISK_IO_PROTOCOL *This,
+                        uint32_t MediaId,
+                        uint64_t Offset,
+                        size_t BufferSize,
+                        const void *Buffer);
+};