| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <efi.h> |
| #include <efigpt.h> |
| #include <efilib.h> |
| |
| #include "part-discovery.h" |
| #include "util.h" |
| |
| union GptHeaderBuffer { |
| EFI_PARTITION_TABLE_HEADER gpt_header; |
| uint8_t space[CONST_ALIGN_TO(sizeof(EFI_PARTITION_TABLE_HEADER), 512)]; |
| }; |
| |
| static EFI_DEVICE_PATH *path_replace_hd( |
| const EFI_DEVICE_PATH *path, |
| const EFI_DEVICE_PATH *node, |
| const HARDDRIVE_DEVICE_PATH *new_node) { |
| |
| /* Create a new device path as a copy of path, while chopping off the remainder starting at the given |
| * node. If new_node is provided, it is appended at the end of the new path. */ |
| |
| assert(path); |
| assert(node); |
| |
| size_t len = (uint8_t *) node - (uint8_t *) path, new_node_len = 0; |
| if (new_node) |
| new_node_len = DevicePathNodeLength(&new_node->Header); |
| |
| EFI_DEVICE_PATH *ret = xmalloc(len + new_node_len + END_DEVICE_PATH_LENGTH); |
| EFI_DEVICE_PATH *end = mempcpy(ret, path, len); |
| |
| if (new_node) |
| end = mempcpy(end, new_node, new_node_len); |
| |
| SetDevicePathEndNode(end); |
| return ret; |
| } |
| |
| static bool verify_gpt(union GptHeaderBuffer *gpt_header_buffer, EFI_LBA lba_expected) { |
| EFI_PARTITION_TABLE_HEADER *h; |
| uint32_t crc32, crc32_saved; |
| EFI_STATUS err; |
| |
| assert(gpt_header_buffer); |
| |
| h = &gpt_header_buffer->gpt_header; |
| |
| /* Some superficial validation of the GPT header */ |
| if (memcmp(&h->Header.Signature, "EFI PART", sizeof(h->Header.Signature)) != 0) |
| return false; |
| |
| if (h->Header.HeaderSize < 92 || h->Header.HeaderSize > 512) |
| return false; |
| |
| if (h->Header.Revision != 0x00010000U) |
| return false; |
| |
| /* Calculate CRC check */ |
| crc32_saved = h->Header.CRC32; |
| h->Header.CRC32 = 0; |
| err = BS->CalculateCrc32(gpt_header_buffer, h->Header.HeaderSize, &crc32); |
| h->Header.CRC32 = crc32_saved; |
| if (err != EFI_SUCCESS || crc32 != crc32_saved) |
| return false; |
| |
| if (h->MyLBA != lba_expected) |
| return false; |
| |
| if ((h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0) |
| return false; |
| |
| if (h->NumberOfPartitionEntries <= 0 || h->NumberOfPartitionEntries > 1024) |
| return false; |
| |
| /* overflow check */ |
| if (h->SizeOfPartitionEntry > SIZE_MAX / h->NumberOfPartitionEntries) |
| return false; |
| |
| return true; |
| } |
| |
| static EFI_STATUS try_gpt( |
| const EFI_GUID *type, |
| EFI_BLOCK_IO_PROTOCOL *block_io, |
| EFI_LBA lba, |
| EFI_LBA *ret_backup_lba, /* May be changed even on error! */ |
| HARDDRIVE_DEVICE_PATH *ret_hd) { |
| |
| _cleanup_free_ EFI_PARTITION_ENTRY *entries = NULL; |
| union GptHeaderBuffer gpt; |
| EFI_STATUS err; |
| uint32_t crc32; |
| size_t size; |
| |
| assert(block_io); |
| assert(ret_hd); |
| |
| /* Read the GPT header */ |
| err = block_io->ReadBlocks( |
| block_io, |
| block_io->Media->MediaId, |
| lba, |
| 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.gpt_header.AlternateLBA; |
| |
| if (!verify_gpt(&gpt, lba)) |
| return EFI_NOT_FOUND; |
| |
| /* Now load the GPT entry table */ |
| size = ALIGN_TO((size_t) gpt.gpt_header.SizeOfPartitionEntry * (size_t) gpt.gpt_header.NumberOfPartitionEntries, 512); |
| entries = xmalloc(size); |
| |
| err = block_io->ReadBlocks( |
| block_io, |
| block_io->Media->MediaId, |
| gpt.gpt_header.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.gpt_header.PartitionEntryArrayCRC32) |
| return EFI_CRC_ERROR; |
| |
| /* Now we can finally look for xbootloader partitions. */ |
| for (size_t i = 0; i < gpt.gpt_header.NumberOfPartitionEntries; i++) { |
| EFI_PARTITION_ENTRY *entry = |
| (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.gpt_header.SizeOfPartitionEntry * i); |
| |
| if (!efi_guid_equal(&entry->PartitionTypeGUID, type)) |
| continue; |
| |
| if (entry->EndingLBA < entry->StartingLBA) /* Bogus? */ |
| continue; |
| |
| *ret_hd = (HARDDRIVE_DEVICE_PATH) { |
| .Header = { |
| .Type = MEDIA_DEVICE_PATH, |
| .SubType = MEDIA_HARDDRIVE_DP, |
| }, |
| .PartitionNumber = i + 1, |
| .PartitionStart = entry->StartingLBA, |
| .PartitionSize = entry->EndingLBA - entry->StartingLBA + 1, |
| .MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER, |
| .SignatureType = SIGNATURE_TYPE_GUID, |
| }; |
| memcpy(ret_hd->Signature, &entry->UniquePartitionGUID, sizeof(ret_hd->Signature)); |
| |
| /* HARDDRIVE_DEVICE_PATH has padding, which at least OVMF does not like. */ |
| SetDevicePathNodeLength( |
| &ret_hd->Header, |
| offsetof(HARDDRIVE_DEVICE_PATH, SignatureType) + sizeof(ret_hd->SignatureType)); |
| |
| return EFI_SUCCESS; |
| } |
| |
| /* 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 EFI_NOT_FOUND; |
| } |
| |
| static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVICE_PATH **ret_device_path) { |
| EFI_STATUS err; |
| |
| assert(device); |
| assert(ret_device_path); |
| |
| 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; |
| |
| /* Find the (last) partition node itself. */ |
| EFI_DEVICE_PATH *part_node = NULL; |
| for (EFI_DEVICE_PATH *node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) { |
| if (DevicePathType(node) != MEDIA_DEVICE_PATH) |
| continue; |
| |
| if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP) |
| continue; |
| |
| part_node = node; |
| } |
| |
| if (!part_node) |
| 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; |
| EFI_DEVICE_PATH *p = disk_path = path_replace_hd(partition_path, part_node, NULL); |
| |
| EFI_HANDLE disk_handle; |
| 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; |
| |
| /* 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. */ |
| (void) BS->ConnectController(disk_handle, NULL, NULL, true); |
| |
| err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); |
| if (err != EFI_SUCCESS) |
| return err; |
| |
| /* 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) |
| return EFI_NOT_FOUND; |
| |
| /* 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++) { |
| EFI_LBA lba; |
| |
| /* Read the first copy at LBA 1 and then try the backup GPT header pointed |
| * to by the first header if that one was corrupted. As a last resort, |
| * try the very last LBA of this block device. */ |
| if (nr == 0) |
| lba = 1; |
| else if (nr == 1 && backup_lba != 0) |
| lba = backup_lba; |
| else if (nr == 2 && backup_lba != block_io->Media->LastBlock) |
| lba = block_io->Media->LastBlock; |
| else |
| continue; |
| |
| HARDDRIVE_DEVICE_PATH hd; |
| err = try_gpt(type, block_io, lba, |
| nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */ |
| &hd); |
| if (err != EFI_SUCCESS) { |
| /* GPT was valid but no XBOOT loader partition found. */ |
| if (err == EFI_NOT_FOUND) |
| break; |
| /* Bad GPT, try next one. */ |
| continue; |
| } |
| |
| /* Patch in the data we found */ |
| *ret_device_path = path_replace_hd(partition_path, part_node, &hd); |
| return EFI_SUCCESS; |
| } |
| |
| /* No xbootloader partition found */ |
| return EFI_NOT_FOUND; |
| } |
| |
| EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE *ret_device, |
| EFI_FILE **ret_root_dir) { |
| _cleanup_free_ EFI_DEVICE_PATH *partition_path = NULL; |
| EFI_HANDLE new_device; |
| EFI_FILE *root_dir; |
| EFI_STATUS err; |
| |
| assert(type); |
| assert(device); |
| assert(ret_root_dir); |
| |
| err = find_device(type, device, &partition_path); |
| if (err != EFI_SUCCESS) |
| return err; |
| |
| EFI_DEVICE_PATH *dp = partition_path; |
| err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &dp, &new_device); |
| if (err != EFI_SUCCESS) |
| return err; |
| |
| err = open_volume(new_device, &root_dir); |
| if (err != EFI_SUCCESS) |
| return err; |
| |
| if (ret_device) |
| *ret_device = new_device; |
| *ret_root_dir = root_dir; |
| return EFI_SUCCESS; |
| } |