From: Daan De Meyer Date: Mon, 30 Mar 2026 18:59:09 +0000 (+0000) Subject: boot: use EFI_DISK_IO_PROTOCOL instead of EFI_BLOCK_IO_PROTOCOL for disk reads X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e5c605d4b3e29c42c4838ef7181e4db7bc28542;p=thirdparty%2Fsystemd.git boot: use EFI_DISK_IO_PROTOCOL instead of EFI_BLOCK_IO_PROTOCOL for disk reads 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 --- diff --git a/src/boot/boot.c b/src/boot/boot.c index 904f9bf5894..2492f474b40 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -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; } diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 25f60521acf..60959d4f784 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -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 index 00000000000..d758a8eb289 --- /dev/null +++ b/src/boot/proto/disk-io.h @@ -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); +};