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 <noreply@anthropic.com>
This commit is contained in:
Daan De Meyer
2026-04-04 20:03:44 +02:00
committed by Daan De Meyer
parent 0245bf7d7e
commit 4e5c605d4b
3 changed files with 71 additions and 60 deletions
+10 -12
View 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);
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;
}
err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, /* LBA= */ 0, BLOCK_IO_BUFFER_SIZE, buf);
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;
}
+37 -48
View 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) {
+24
View File
@@ -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);
};