boot: fix buffer alignment when doing block I/O (#40465)

UEFI Block I/O Protocol has `Media->IoAlign` field dictating the minimum
alignment for I/O buffer. It's quite surprising this has been lingering
here unnoticed for years, seems like most UEFI implementations have
small or no alignment requirements. U-Boot is not the case here, and
requires at least 512 byte alignment, hence attempt to read GPT
partition table fail and in effect systemd-boot can not find XBOOTLDR
partition.

These patches allow to boot from XBOOTLDR partition on U-Boot - tested
with latest systemd revision and U-Boot master
(`8de6e8f8a076d2c9b6d38d8563db135c167077ec`) on x64 and ARM32, of which
both are failing without the patch.

Also fixes Bitlocker probing logic, which is the only other place where
raw block I/O is used, however this is untested.
This commit is contained in:
Yu Watanabe
2026-02-16 09:10:01 +09:00
committed by GitHub
4 changed files with 96 additions and 13 deletions

View File

@@ -2064,8 +2064,18 @@ 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;
char buf[4096];
err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, 0, sizeof(buf), buf);
#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);
if (err != EFI_SUCCESS)
continue;

View File

@@ -77,52 +77,73 @@ static EFI_STATUS try_gpt(
EFI_LBA *ret_backup_lba, /* May be changed even on error! */
HARDDRIVE_DEVICE_PATH *ret_hd) {
_cleanup_free_ EFI_PARTITION_ENTRY *entries = NULL;
GptHeader gpt;
EFI_PARTITION_ENTRY *entries;
_cleanup_pages_ Pages gpt_pages = {};
_cleanup_pages_ Pages entries_pages = {};
GptHeader *gpt;
EFI_STATUS err;
uint32_t crc32;
size_t size;
assert(block_io);
assert(block_io->Media);
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);
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);
entries = xmalloc(size);
size = ALIGN_TO((size_t) gpt->SizeOfPartitionEntry * (size_t) gpt->NumberOfPartitionEntries, 512);
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,
gpt->PartitionEntryLBA,
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;

View File

@@ -517,6 +517,51 @@ void *xmalloc(size_t size) {
return p;
}
Pages xmalloc_aligned_pages(
EFI_ALLOCATE_TYPE type,
EFI_MEMORY_TYPE memory_type,
size_t n_pages,
size_t alignment,
EFI_PHYSICAL_ADDRESS addr) {
EFI_PHYSICAL_ADDRESS aligned = addr;
/* Allow to pass block_io->Media->IoAlign to this function directly.
* alignment <= 1 means no alignment is required, in that case just
* allocate pages directly.
*/
if (alignment <= 1)
alignment = EFI_PAGE_SIZE;
assert(ISPOWEROF2(alignment));
if (alignment <= EFI_PAGE_SIZE) {
assert_se(BS->AllocatePages(type, memory_type, n_pages, &aligned) == EFI_SUCCESS);
return (Pages) {
.addr = aligned,
.n_pages = n_pages,
};
}
size_t total_pages = n_pages + EFI_SIZE_TO_PAGES(alignment);
assert_se(BS->AllocatePages(type, memory_type, total_pages, &addr) == EFI_SUCCESS);
aligned = ALIGN_TO(addr, alignment);
size_t unaligned_pages = EFI_SIZE_TO_PAGES(aligned - addr);
if (unaligned_pages > 0)
assert_se(BS->FreePages(addr, unaligned_pages) == EFI_SUCCESS);
addr = aligned + n_pages * EFI_PAGE_SIZE;
unaligned_pages = total_pages - n_pages - unaligned_pages;
if (unaligned_pages > 0)
assert_se(BS->FreePages(addr, unaligned_pages) == EFI_SUCCESS);
return (Pages) {
.addr = aligned,
.n_pages = n_pages,
};
}
bool free_and_xstrdup16(char16_t **p, const char16_t *s) {
char16_t *t;

View File

@@ -96,6 +96,13 @@ static inline Pages xmalloc_pages(
};
}
Pages xmalloc_aligned_pages(
EFI_ALLOCATE_TYPE type,
EFI_MEMORY_TYPE memory_type,
size_t n_pages,
size_t alignment,
EFI_PHYSICAL_ADDRESS addr);
static inline Pages xmalloc_initrd_pages(size_t n_pages) {
/* The original native x86 boot protocol of the Linux kernel was not 64bit safe, hence we try to
* allocate memory for the initrds below the 4G boundary on x86, since we don't know early enough