From fa2a3bbdd8bfcc3402c2ad9e941eb3911971e09a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 8 Feb 2019 13:04:41 +0100 Subject: [PATCH] sd-boot: also look for boot loader entries in the XBOOTLDR partition The specification always said so, let's actually implement this. Unfortunately UEFI's own APIs don't allow us to search for partition type GUID, hence we have to implement a minimal GPT parser ourselves. --- src/boot/efi/boot.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++ src/boot/efi/crc32.c | 142 +++++++++++++++++++++++++++++++++++ src/boot/efi/crc32.h | 8 ++ src/boot/efi/meson.build | 2 + 4 files changed, 344 insertions(+) create mode 100644 src/boot/efi/crc32.c create mode 100644 src/boot/efi/crc32.h diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 35c649f..188ce65 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #include +#include #include #include "console.h" +#include "crc32.h" #include "disk.h" #include "graphics.h" #include "linux.h" @@ -1955,6 +1957,193 @@ static VOID config_entry_add_linux( uefi_call_wrapper(linux_dir->Close, 1, linux_dir); } +/* Note that this is in GUID format, i.e. the first 32bit, and the following pair of 16bit are byteswapped. */ +static const UINT8 xbootldr_guid[16] = { + 0xff, 0xc2, 0x13, 0xbc, 0xe6, 0x59, 0x62, 0x42, 0xa3, 0x52, 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72 +}; + +EFI_DEVICE_PATH *path_parent(EFI_DEVICE_PATH *path, EFI_DEVICE_PATH *node) { + EFI_DEVICE_PATH *parent; + UINTN len; + + len = (UINT8*) NextDevicePathNode(node) - (UINT8*) path; + parent = (EFI_DEVICE_PATH*) AllocatePool(len + sizeof(EFI_DEVICE_PATH)); + CopyMem(parent, path, len); + CopyMem((UINT8*) parent + len, EndDevicePath, sizeof(EFI_DEVICE_PATH)); + + return parent; +} + +static VOID config_load_xbootldr( + Config *config, + EFI_HANDLE *device) { + + EFI_DEVICE_PATH *partition_path, *node, *disk_path, *copy; + UINT32 found_partition_number = (UINT32) -1; + UINT64 found_partition_start = (UINT64) -1; + UINT64 found_partition_size = (UINT64) -1; + UINT8 found_partition_signature[16] = {}; + EFI_HANDLE new_device; + EFI_FILE *root_dir; + EFI_STATUS r; + + partition_path = DevicePathFromHandle(device); + if (!partition_path) + return; + + for (node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) { + EFI_HANDLE disk_handle; + EFI_BLOCK_IO *block_io; + EFI_DEVICE_PATH *p; + UINTN nr; + + /* First, Let's look for the SCSI/SATA/USB/… device path node, i.e. one above the media + * devices */ + if (DevicePathType(node) != MESSAGING_DEVICE_PATH) + continue; + + /* Determine the device path one level up */ + disk_path = path_parent(partition_path, node); + p = disk_path; + r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, &p, &disk_handle); + if (EFI_ERROR(r)) + continue; + + r = uefi_call_wrapper(BS->HandleProtocol, 3, disk_handle, &BlockIoProtocol, (VOID **)&block_io); + if (EFI_ERROR(r)) + continue; + + /* 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) + continue; + + /* Try both copies of the GPT header, in case one is corrupted */ + for (nr = 0; nr < 2; nr++) { + _cleanup_freepool_ EFI_PARTITION_ENTRY* entries = NULL; + union { + EFI_PARTITION_TABLE_HEADER gpt_header; + uint8_t space[((sizeof(EFI_PARTITION_TABLE_HEADER) + 511) / 512) * 512]; + } gpt_header_buffer; + UINT64 where; + UINTN i, sz; + UINT32 c; + + if (nr == 0) + where = 1; /* Read the first copy at LBA 1 */ + else + where = block_io->Media->LastBlock; /* Read the second copy at the very last LBA of this block device */ + + /* Read the GPT header */ + r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, where, sizeof(gpt_header_buffer), &gpt_header_buffer); + if (EFI_ERROR(r)) + continue; + + /* Some superficial validation of the GPT header */ + if (CompareMem(&gpt_header_buffer.gpt_header.Header.Signature, "EFI PART", sizeof(gpt_header_buffer.gpt_header.Header.Signature)) != 0) + continue; + + if (gpt_header_buffer.gpt_header.Header.HeaderSize < 92 || gpt_header_buffer.gpt_header.Header.HeaderSize > 512) + continue; + + if (gpt_header_buffer.gpt_header.Header.Revision != 0x00010000U) + continue; + + /* Calculate CRC check */ + c = ~crc32_exclude_offset((UINT32) -1, (const UINT8*) &gpt_header_buffer, gpt_header_buffer.gpt_header.Header.HeaderSize, + OFFSETOF(EFI_PARTITION_TABLE_HEADER, Header.CRC32), sizeof(gpt_header_buffer.gpt_header.Header.CRC32)); + if (c != gpt_header_buffer.gpt_header.Header.CRC32) + continue; + + if (gpt_header_buffer.gpt_header.MyLBA != where) + continue; + + if (gpt_header_buffer.gpt_header.SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY)) + continue; + + if (gpt_header_buffer.gpt_header.NumberOfPartitionEntries <= 0 || gpt_header_buffer.gpt_header.NumberOfPartitionEntries > 1024) + continue; + + /* Now load the GPT entry table */ + sz = ((gpt_header_buffer.gpt_header.SizeOfPartitionEntry * gpt_header_buffer.gpt_header.NumberOfPartitionEntries + 511) / 512) * 512; + entries = AllocatePool(sz); + + r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, gpt_header_buffer.gpt_header.PartitionEntryLBA, sz, entries); + if (EFI_ERROR(r)) + continue; + + /* Calculate CRC of entries array, too */ + c = ~crc32((UINT32) -1, entries, sz); + if (c != gpt_header_buffer.gpt_header.PartitionEntryArrayCRC32) + continue; + + for (i = 0; i < gpt_header_buffer.gpt_header.NumberOfPartitionEntries; i++) { + EFI_PARTITION_ENTRY *entry; + + entry = (EFI_PARTITION_ENTRY*) ((UINT8*) entries + gpt_header_buffer.gpt_header.SizeOfPartitionEntry * i); + + if (CompareMem(&entry->PartitionTypeGUID, xbootldr_guid, 16) == 0) { + UINT64 end; + + /* Let's use memcpy(), in case the structs are not aligned (they really should be though) */ + CopyMem(&found_partition_start, &entry->StartingLBA, sizeof(found_partition_start)); + CopyMem(&end, &entry->EndingLBA, sizeof(end)); + + if (end < found_partition_start) /* Bogus? */ + continue; + + found_partition_size = end - found_partition_start + 1; + CopyMem(found_partition_signature, &entry->UniquePartitionGUID, sizeof(found_partition_signature)); + + found_partition_number = i + 1; + goto found; + } + } + + break; /* This GPT was fully valid, but we didn't find what we are looking for. This + * means there's no reason to check the second copy of the GPT header */ + } + } + + return; /* Not found */ + +found: + copy = DuplicateDevicePath(partition_path); + + /* Patch in the data we found */ + for (node = copy; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) { + HARDDRIVE_DEVICE_PATH *hd; + + if (DevicePathType(node) != MEDIA_DEVICE_PATH) + continue; + + if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP) + continue; + + hd = (HARDDRIVE_DEVICE_PATH*) node; + hd->PartitionNumber = found_partition_number; + hd->PartitionStart = found_partition_start; + hd->PartitionSize = found_partition_size; + CopyMem(hd->Signature, found_partition_signature, sizeof(hd->Signature)); + hd->MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + hd->SignatureType = SIGNATURE_TYPE_GUID; + } + + r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, ©, &new_device); + if (EFI_ERROR(r)) + return; + + root_dir = LibOpenRoot(new_device); + if (!root_dir) + return; + + config_entry_add_linux(config, new_device, root_dir); + config_load_entries(config, new_device, root_dir, NULL); +} + static EFI_STATUS image_start( EFI_HANDLE parent_image, const Config *config, @@ -2147,6 +2336,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { /* scan /loader/entries/\*.conf files */ config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path); + /* Similar, but on any XBOOTLDR partition */ + config_load_xbootldr(&config, loaded_image->DeviceHandle); + /* sort entries after version number */ config_sort_entries(&config); diff --git a/src/boot/efi/crc32.c b/src/boot/efi/crc32.c new file mode 100644 index 0000000..46b9aee --- /dev/null +++ b/src/boot/efi/crc32.c @@ -0,0 +1,142 @@ +/* This is copied from util-linux, which in turn copied in the version from Gary S. Brown */ + +/* + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + * + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1. + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way, + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to high-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly. + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera- + * tions for all combinations of data and CRC register values. + * + * The values must be right-shifted by eight bits by the "updcrc" + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions. + * polynomial $edb88320 + * + */ + +#include "crc32.h" + +static const UINT32 crc32_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +static inline UINT32 crc32_add_char(UINT32 crc, UINT8 c) { + return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); +} + +/* + * This a generic crc32() function, it takes seed as an argument, + * and does __not__ xor at the end. Then individual users can do + * whatever they need. + */ +UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len) { + const UINT8 *p = buf; + UINT32 crc = seed; + + while (len > 0) { + crc = crc32_add_char(crc, *p++); + len--; + } + + return crc; +} + +UINT32 crc32_exclude_offset( + UINT32 seed, + const VOID *buf, + UINTN len, + UINTN exclude_off, + UINTN exclude_len) { + + const UINT8 *p = buf; + UINT32 crc = seed; + UINTN i; + + for (i = 0; i < len; i++) { + UINT8 x = *p++; + + if (i >= exclude_off && i < exclude_off + exclude_len) + x = 0; + + crc = crc32_add_char(crc, x); + } + + return crc; +} diff --git a/src/boot/efi/crc32.h b/src/boot/efi/crc32.h new file mode 100644 index 0000000..64150ee --- /dev/null +++ b/src/boot/efi/crc32.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include +#include + +UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len); +UINT32 crc32_exclude_offset(UINT32 seed, const VOID *buf, UINTN len, UINTN exclude_off, UINTN exclude_len); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 2140151..67b4331 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -2,6 +2,7 @@ efi_headers = files(''' console.h + crc32.h disk.h graphics.h linux.h @@ -24,6 +25,7 @@ systemd_boot_sources = ''' boot.c console.c shim.c + crc32.c '''.split() stub_sources = ''' -- 2.7.4