| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <diskio.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <zircon/compiler.h> |
| #include <zircon/errors.h> |
| #include <zircon/hw/gpt.h> |
| #include <zircon/types.h> |
| |
| #include <efi/boot-services.h> |
| #include <efi/protocol/block-io.h> |
| #include <efi/protocol/device-path-to-text.h> |
| #include <efi/protocol/device-path.h> |
| #include <efi/protocol/disk-io.h> |
| #include <efi/protocol/loaded-image.h> |
| #include <efi/protocol/simple-file-system.h> |
| |
| #ifdef GIGABOOT_HOST |
| #include <host/stubs.h> |
| #endif |
| |
| #include "osboot.h" |
| #include "utf_conversion.h" |
| |
| // Max number of UTF-16 chars in a GPT partition name. |
| #define GPT_NAME_LEN_U16 (GPT_NAME_LEN / sizeof(uint16_t)) |
| |
| static bool path_node_match(efi_device_path_protocol* a, efi_device_path_protocol* b) { |
| size_t alen = a->Length[0] | (a->Length[1] << 8); |
| size_t blen = b->Length[0] | (b->Length[1] << 8); |
| if (alen != blen) { |
| return false; |
| } |
| if (memcmp(a, b, alen)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static efi_device_path_protocol* path_node_next(efi_device_path_protocol* node) { |
| if (node->Type == DEVICE_PATH_END) { |
| return NULL; |
| } |
| return ((void*)node) + (node->Length[0] | (node->Length[1] << 8)); |
| } |
| |
| static bool path_prefix_match(efi_device_path_protocol* path, efi_device_path_protocol* prefix) { |
| if ((path == NULL) || (prefix == NULL)) { |
| return false; |
| } |
| for (;;) { |
| if (prefix->Type == DEVICE_PATH_END) { |
| return true; |
| } |
| if (!path_node_match(path, prefix)) { |
| return false; |
| } |
| if ((path = path_node_next(path)) == NULL) { |
| return false; |
| } |
| prefix = path_node_next(prefix); |
| } |
| } |
| |
| static void print_path(efi_boot_services* bs, efi_device_path_protocol* path) { |
| efi_device_path_to_text_protocol* ptt; |
| efi_status status = bs->LocateProtocol(&DevicePathToTextProtocol, NULL, (void**)&ptt); |
| if (status != EFI_SUCCESS) { |
| printf("<cannot print path>"); |
| return; |
| } |
| char16_t* txt = ptt->ConvertDevicePathToText(path, false, false); |
| if (txt == NULL) { |
| printf("<cannot print path>"); |
| return; |
| } |
| puts16(txt); |
| printf("\n"); |
| bs->FreePool(txt); |
| } |
| |
| static efi_status disk_read(const disk_t* disk, size_t offset, void* data, size_t length) { |
| if (disk->first > disk->last) { |
| return EFI_VOLUME_CORRUPTED; |
| } |
| |
| uint64_t size = (disk->last - disk->first) * disk->blksz; |
| if ((offset > size) || ((size - offset) < length)) { |
| printf("ERROR: Disk read invalid params. offset:%zu length:%zu disk: [%" PRIu64 " to %" PRIu64 |
| "] size:%" PRIu64 " blksz:%d\n", |
| offset, length, disk->first, disk->last, size, disk->blksz); |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| return disk->io->ReadDisk(disk->io, disk->id, (disk->first * disk->blksz) + offset, length, data); |
| } |
| |
| efi_status disk_write(disk_t* disk, size_t offset, void* data, size_t length) { |
| if (disk->first > disk->last) { |
| return EFI_VOLUME_CORRUPTED; |
| } |
| |
| uint64_t size = (disk->last - disk->first) * disk->blksz; |
| if ((offset > size) || ((size - offset) < length)) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| return disk->io->WriteDisk(disk->io, disk->id, (disk->first * disk->blksz) + offset, length, |
| data); |
| } |
| |
| static void disk_close(disk_t* disk) { |
| disk->bs->CloseProtocol(disk->h, &DiskIoProtocol, disk->img, NULL); |
| } |
| |
| bool is_booting_from_usb(efi_handle img, efi_system_table* sys) { |
| bool result = false; |
| efi_boot_services* bs = sys->BootServices; |
| efi_status status; |
| efi_loaded_image_protocol* li; |
| status = bs->OpenProtocol(img, &LoadedImageProtocol, (void**)&li, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| return -1; |
| } |
| |
| efi_device_path_protocol* imgdevpath; |
| status = bs->OpenProtocol(li->DeviceHandle, &DevicePathProtocol, (void**)&imgdevpath, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| goto fail_open_devpath; |
| } |
| |
| while (imgdevpath != NULL) { |
| if (imgdevpath->Type == DEVICE_PATH_MESSAGING) { |
| switch (imgdevpath->SubType) { |
| case DEVICE_PATH_MESSAGING_USB: |
| case DEVICE_PATH_MESSAGING_USB_LUN: |
| case DEVICE_PATH_MESSAGING_USB_WWID: |
| case DEVICE_PATH_MESSAGING_USB_CLASS: |
| result = true; |
| break; |
| } |
| } |
| |
| imgdevpath = path_node_next(imgdevpath); |
| } |
| |
| bs->CloseProtocol(li->DeviceHandle, &DevicePathProtocol, img, NULL); |
| |
| fail_open_devpath: |
| bs->CloseProtocol(img, &LoadedImageProtocol, img, NULL); |
| |
| return result; |
| } |
| |
| int disk_find_boot(efi_handle img, efi_system_table* sys, bool verbose, disk_t* disk) { |
| bool found = false; |
| efi_boot_services* bs = sys->BootServices; |
| efi_handle* list; |
| size_t count; |
| efi_status status; |
| efi_loaded_image_protocol* li; |
| |
| status = bs->OpenProtocol(img, &LoadedImageProtocol, (void**)&li, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| return -1; |
| } |
| |
| efi_device_path_protocol* imgdevpath; |
| status = bs->OpenProtocol(li->DeviceHandle, &DevicePathProtocol, (void**)&imgdevpath, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| goto fail_open_devpath; |
| } |
| |
| if (verbose) { |
| printf("BootLoader Path: "); |
| print_path(bs, li->FilePath); |
| printf("BootLoader Device: "); |
| print_path(bs, imgdevpath); |
| } |
| |
| status = bs->LocateHandleBuffer(ByProtocol, &BlockIoProtocol, NULL, &count, &list); |
| if (status != EFI_SUCCESS) { |
| printf("find_boot_disk() - no block io devices found\n"); |
| goto fail_get_list; |
| } |
| |
| for (size_t n = 0; n < count; n++) { |
| efi_block_io_protocol* bio; |
| status = bs->OpenProtocol(list[n], &BlockIoProtocol, (void**)&bio, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| continue; |
| } |
| |
| efi_device_path_protocol* path; |
| status = bs->OpenProtocol(list[n], &DevicePathProtocol, (void**)&path, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| bs->CloseProtocol(list[n], &BlockIoProtocol, img, NULL); |
| continue; |
| } |
| |
| bool match = false; |
| |
| // if a non-logical partition, check for match |
| if (!bio->Media->LogicalPartition && bio->Media->MediaPresent) { |
| match = path_prefix_match(imgdevpath, path); |
| } |
| |
| if (verbose) { |
| printf("BlockIO Device: "); |
| print_path(bs, path); |
| printf(" : #%zu, %" PRIu64 "MB%s%s%s%s%s%s\n", n, |
| bio->Media->LastBlock * bio->Media->BlockSize / 1024 / 1024, |
| bio->Media->RemovableMedia ? " Removable" : "", |
| bio->Media->MediaPresent ? " Present" : "", |
| bio->Media->LogicalPartition ? " Logical" : "", bio->Media->ReadOnly ? " RO" : "", |
| bio->Media->WriteCaching ? " WC" : "", match ? " BootDevice" : ""); |
| } |
| |
| if (match && !found) { |
| status = bs->OpenProtocol(list[n], &DiskIoProtocol, (void**)&disk->io, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| printf("find_boot_disk() - cannot get disk io protocol\n"); |
| } else { |
| disk->first = 0; |
| disk->last = bio->Media->LastBlock; |
| disk->id = bio->Media->MediaId; |
| disk->blksz = bio->Media->BlockSize; |
| disk->h = list[n]; |
| disk->img = img; |
| disk->bs = bs; |
| found = true; |
| } |
| } |
| |
| bs->CloseProtocol(list[n], &BlockIoProtocol, img, NULL); |
| bs->CloseProtocol(list[n], &DevicePathProtocol, img, NULL); |
| } |
| |
| bs->FreePool(list); |
| |
| fail_get_list: |
| bs->CloseProtocol(li->DeviceHandle, &DevicePathProtocol, img, NULL); |
| |
| fail_open_devpath: |
| bs->CloseProtocol(img, &LoadedImageProtocol, img, NULL); |
| |
| return found ? 0 : -1; |
| } |
| |
| int disk_find_partition(const disk_t* disk, bool verbose, const uint8_t* type, const uint8_t* guid, |
| const char* name, gpt_entry_t* partition) { |
| // Block 0 is MBR, read from block 1 to get GPT header. |
| gpt_header_t gpt; |
| efi_status status = disk_read(disk, disk->blksz, &gpt, sizeof(gpt)); |
| if (status != EFI_SUCCESS) { |
| return -1; |
| } |
| |
| if (verbose) { |
| printf("gpt: size: %u\n", gpt.size); |
| printf("gpt: current: %" PRIu64 "\n", gpt.current); |
| printf("gpt: backup: %" PRIu64 "\n", gpt.backup); |
| printf("gpt: first: %" PRIu64 "\n", gpt.first); |
| printf("gpt: last: %" PRIu64 "\n", gpt.last); |
| printf("gpt: entries: %" PRIu64 "\n", gpt.entries); |
| printf("gpt: e.count: %u\n", gpt.entries_count); |
| printf("gpt: e.size: %u\n", gpt.entries_size); |
| } |
| |
| // TODO(69527): checksum validation and backup GPT support. |
| if ((gpt.magic != GPT_MAGIC) || (gpt.size != GPT_HEADER_SIZE) || |
| (gpt.entries_size != GPT_ENTRY_SIZE) || (gpt.entries_count > 256)) { |
| printf("gpt - malformed header\n"); |
| return -1; |
| } |
| |
| // If the user gave a name, convert to UTF-16 so we can compare it to |
| // the GPT entry directly with memcmp(). |
| uint16_t name_utf16[GPT_NAME_LEN_U16]; |
| size_t name_utf16_len = sizeof(name_utf16); |
| if (name) { |
| zx_status_t status = |
| utf8_to_utf16((const uint8_t*)name, strlen(name) + 1, name_utf16, &name_utf16_len); |
| if (status != ZX_OK) { |
| printf("gpt - failed to convert name '%s' to UTF-16: %d\n", name, status); |
| return -1; |
| } |
| } |
| |
| // Allocate memory to hold the partition entry table and read it from disk. |
| gpt_entry_t* table; |
| size_t tsize = gpt.entries_count * gpt.entries_size; |
| status = disk->bs->AllocatePool(EfiLoaderData, tsize, (void**)&table); |
| if (status != EFI_SUCCESS) { |
| printf("gpt - allocation failure\n"); |
| return -1; |
| } |
| |
| status = disk_read(disk, disk->blksz * gpt.entries, table, tsize); |
| if (status != EFI_SUCCESS) { |
| disk->bs->FreePool(table); |
| printf("gpt - io error\n"); |
| return -1; |
| } |
| |
| bool found = false; |
| for (unsigned n = 0; n < gpt.entries_count; n++) { |
| if ((table[n].first == 0) || (table[n].last == 0) || (table[n].last < table[n].first)) { |
| // Ignore empty or bogus entries. |
| continue; |
| } |
| |
| if ((!type || memcmp(table[n].type, type, GPT_GUID_LEN) == 0) && |
| (!guid || memcmp(table[n].guid, guid, GPT_GUID_LEN) == 0) && |
| (!name || memcmp(table[n].name, name_utf16, name_utf16_len) == 0)) { |
| // Multi-partition match is an error. |
| if (found) { |
| disk->bs->FreePool(table); |
| return -1; |
| } |
| |
| found = true; |
| memcpy(partition, &table[n], sizeof(*partition)); |
| } |
| |
| if (verbose) { |
| // Convert UTF-16 partition name to UTF-8 for printing. This assumes |
| // the name will actually be basic ASCII and might truncate if not, |
| // but it's fine for debug purposes. |
| char gpt_name[GPT_NAME_LEN / 2] = "<unknown>"; |
| size_t gpt_name_length = sizeof(gpt_name); |
| utf16_to_utf8((const uint16_t*)table[n].name, GPT_NAME_LEN_U16, (uint8_t*)gpt_name, |
| &gpt_name_length); |
| gpt_name[GPT_NAME_LEN / 2 - 1] = '\0'; |
| |
| printf("#%03d %" PRIu64 "..%" PRIu64 " %16s %" PRIx64 "\n", n, table[n].first, table[n].last, |
| gpt_name, table[n].flags); |
| } |
| } |
| disk->bs->FreePool(table); |
| |
| return found ? 0 : -1; |
| } |
| |
| void* image_load_from_disk(efi_handle img, efi_system_table* sys, size_t* _sz, |
| const uint8_t* guid_value, const char* guid_name) { |
| static bool verbose = false; |
| uint8_t sector[512]; |
| efi_boot_services* bs = sys->BootServices; |
| disk_t disk; |
| |
| if (disk_find_boot(img, sys, verbose, &disk) < 0) { |
| printf("Cannot find bootloader disk.\n"); |
| return NULL; |
| } |
| |
| gpt_entry_t partition; |
| if (disk_find_partition(&disk, verbose, guid_value, NULL, NULL, &partition)) { |
| printf("Cannot find %s partition on bootloader disk.\n", guid_name); |
| goto fail0; |
| } |
| const uint64_t partition_offset = partition.first * disk.blksz; |
| |
| efi_status status = disk_read(&disk, partition_offset, sector, 512); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to read disk: %zu\n", status); |
| goto fail0; |
| } |
| |
| size_t sz = image_getsize(sector, 512); |
| if (sz == 0) { |
| printf("%s partition has no valid header\n", guid_name); |
| goto fail0; |
| } |
| |
| size_t pages = (sz + 4095) / 4096; |
| void* image; |
| status = bs->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, (efi_physical_addr*)&image); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate %zu bytes to load %s image\n", sz, guid_name); |
| goto fail0; |
| } |
| |
| status = disk_read(&disk, partition_offset, image, sz); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to read image from %s partition\n", guid_name); |
| goto fail1; |
| } |
| |
| if (identify_image(image, sz) != IMAGE_COMBO) { |
| printf("%s partition has no valid image\n", guid_name); |
| goto fail1; |
| } |
| |
| *_sz = sz; |
| return image; |
| |
| fail1: |
| bs->FreePages((efi_physical_addr)image, pages); |
| fail0: |
| disk_close(&disk); |
| return NULL; |
| } |
| |
| efi_status read_partition(efi_handle img, efi_system_table* sys, const uint8_t* guid_value, |
| const char* guid_name, uint64_t offset, unsigned char* data, |
| size_t size) { |
| static bool verbose = false; |
| disk_t disk; |
| |
| if (disk_find_boot(img, sys, verbose, &disk) < 0) { |
| printf("Cannot find bootloader disk.\n"); |
| return EFI_NOT_FOUND; |
| } |
| |
| gpt_entry_t partition; |
| if (disk_find_partition(&disk, verbose, guid_value, NULL, NULL, &partition)) { |
| printf("Cannot find %s partition on bootloader disk.\n", guid_name); |
| disk_close(&disk); |
| return EFI_NOT_FOUND; |
| } |
| const uint64_t partition_offset = partition.first * disk.blksz; |
| |
| efi_status status = disk_read(&disk, offset + partition_offset, data, size); |
| disk_close(&disk); |
| return status; |
| } |
| |
| efi_status write_partition(efi_handle img, efi_system_table* sys, const uint8_t* guid_value, |
| const char* guid_name, uint64_t offset, const unsigned char* data, |
| size_t size) { |
| static bool verbose = false; |
| disk_t disk; |
| |
| if (disk_find_boot(img, sys, verbose, &disk) < 0) { |
| printf("Cannot find bootloader disk.\n"); |
| return EFI_NOT_FOUND; |
| } |
| |
| gpt_entry_t partition; |
| if (disk_find_partition(&disk, verbose, guid_value, NULL, NULL, &partition)) { |
| printf("Cannot find %s partition on bootloader disk.\n", guid_name); |
| disk_close(&disk); |
| return EFI_NOT_FOUND; |
| } |
| const uint64_t partition_offset = partition.first * disk.blksz; |
| |
| efi_status status = disk_write(&disk, offset + partition_offset, (void*)data, size); |
| disk_close(&disk); |
| return status; |
| } |
| |
| // Mapping from either legacy or new partition naming scheme to the expected |
| // on-disk type GUID. |
| static const struct { |
| const char* legacy_name; |
| const char* name; |
| const uint8_t type_guid[GPT_GUID_LEN]; |
| } partition_map[] = { |
| { |
| .legacy_name = GUID_ZIRCON_A_NAME, |
| .name = GPT_ZIRCON_A_NAME, |
| .type_guid = GUID_ZIRCON_A_VALUE, |
| }, |
| { |
| .legacy_name = GUID_ZIRCON_B_NAME, |
| .name = GPT_ZIRCON_B_NAME, |
| .type_guid = GUID_ZIRCON_B_VALUE, |
| }, |
| { |
| .legacy_name = GUID_ZIRCON_R_NAME, |
| .name = GPT_ZIRCON_R_NAME, |
| .type_guid = GUID_ZIRCON_R_VALUE, |
| }, |
| // Note: even though both vbmeta names are actually the same, still check |
| // both constants here to avoid depending on this always being true. |
| { |
| .legacy_name = GUID_VBMETA_A_NAME, |
| .name = GPT_VBMETA_A_NAME, |
| .type_guid = GUID_VBMETA_A_VALUE, |
| }, |
| { |
| .legacy_name = GUID_VBMETA_B_NAME, |
| .name = GPT_VBMETA_B_NAME, |
| .type_guid = GUID_VBMETA_B_VALUE, |
| }, |
| { |
| .legacy_name = GUID_VBMETA_R_NAME, |
| .name = GPT_VBMETA_R_NAME, |
| .type_guid = GUID_VBMETA_R_VALUE, |
| }, |
| { |
| .legacy_name = GUID_EFI_NAME, |
| // No bootloader_{a,b,r} support, just use standard "bootloader". |
| .name = "bootloader", |
| .type_guid = GUID_EFI_VALUE, |
| }, |
| }; |
| |
| const uint8_t* partition_type_guid(const char* name) { |
| for (size_t i = 0; i < countof(partition_map); ++i) { |
| if (strcmp(partition_map[i].legacy_name, name) == 0 || |
| strcmp(partition_map[i].name, name) == 0) { |
| return partition_map[i].type_guid; |
| } |
| } |
| |
| return NULL; |
| } |