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:
committed by
Daan De Meyer
parent
0245bf7d7e
commit
4e5c605d4b
+10
-12
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user