| // Copyright 2020 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. |
| |
| #ifdef ZIRCON_BOOT_CUSTOM_SYSDEPS_HEADER |
| #include <zircon_boot_sysdeps.h> |
| #else |
| #include <assert.h> |
| #include <stddef.h> |
| #include <string.h> |
| #endif |
| |
| #include <lib/abr/abr.h> |
| #include <lib/zbi/zbi.h> |
| #include <lib/zircon_boot/android_boot_image.h> |
| #include <lib/zircon_boot/zircon_boot.h> |
| #include <zircon/hw/gpt.h> |
| |
| #include "utils.h" |
| #include "zircon_ramboot.h" |
| #include "zircon_vboot.h" |
| |
| const char* GetSlotPartitionName(const AbrSlotIndex* slot) { |
| if (slot == NULL) { |
| return GPT_ZIRCON_SLOTLESS_NAME; |
| } else if (*slot == kAbrSlotIndexA) { |
| return GPT_ZIRCON_A_NAME; |
| } else if (*slot == kAbrSlotIndexB) { |
| return GPT_ZIRCON_B_NAME; |
| } else if (*slot == kAbrSlotIndexR) { |
| return GPT_ZIRCON_R_NAME; |
| } |
| return NULL; |
| } |
| |
| static const char* GetFirmwareSlotPartitionName(const AbrSlotIndex slot) { |
| if (slot == kAbrSlotIndexA) { |
| return GPT_BOOTLOADER_A_NAME; |
| } else if (slot == kAbrSlotIndexB) { |
| return GPT_BOOTLOADER_B_NAME; |
| } else if (slot == kAbrSlotIndexR) { |
| return GPT_BOOTLOADER_R_NAME; |
| } |
| return NULL; |
| } |
| |
| bool LoadAbrFirmware(ZirconBootOps* ops, void* dst, size_t size) { |
| AbrSlotIndex slot_index = GetActiveBootSlot(ops); |
| return ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, GetFirmwareSlotPartitionName(slot_index), 0, |
| size, dst, &size); |
| } |
| |
| static bool ReadAbrMetaData(void* context, size_t size, uint8_t* buffer) { |
| ZirconBootOps* ops = (ZirconBootOps*)context; |
| size_t read_size; |
| |
| return ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, GPT_DURABLE_BOOT_NAME, 0, size, buffer, |
| &read_size) && |
| read_size == size; |
| } |
| |
| static bool WriteAbrMetaData(void* context, const uint8_t* buffer, size_t size) { |
| ZirconBootOps* ops = (ZirconBootOps*)context; |
| size_t write_size; |
| |
| return ZIRCON_BOOT_OPS_CALL(ops, write_to_partition, GPT_DURABLE_BOOT_NAME, 0, size, buffer, |
| &write_size) && |
| write_size == size; |
| } |
| |
| AbrOps GetAbrOpsFromZirconBootOps(ZirconBootOps* ops) { |
| AbrOps abr_ops = { |
| .context = ops, .read_abr_metadata = ReadAbrMetaData, .write_abr_metadata = WriteAbrMetaData}; |
| return abr_ops; |
| } |
| |
| static bool IsVerifiedBootOpsImplemented(ZirconBootOps* ops) { |
| return ops->verified_boot_get_partition_size && ops->verified_boot_read_rollback_index && |
| ops->verified_boot_write_rollback_index && ops->verified_boot_read_is_device_locked && |
| ops->verified_boot_read_permanent_attributes && |
| ops->verified_boot_read_permanent_attributes_hash; |
| } |
| |
| size_t ZbiCheckSize(const void* zbi, size_t max_size) { |
| // Copy it locally to avoid any alignment issues. |
| zbi_header_t header; |
| if (max_size && max_size < sizeof(header)) { |
| zircon_boot_dlog("ZBI header exceeds maximum size (%zu)\n", max_size); |
| return 0; |
| } |
| memcpy(&header, zbi, sizeof(header)); |
| |
| // Check a few of the important fields. |
| if (header.type != ZBI_TYPE_CONTAINER || header.extra != ZBI_CONTAINER_MAGIC || |
| header.magic != ZBI_ITEM_MAGIC) { |
| zircon_boot_dlog("Image does not look like a ZBI\n"); |
| return 0; |
| } |
| |
| size_t result = sizeof(header) + header.length; |
| if (result < header.length) { |
| zircon_boot_dlog("ZBI size overflow (%zu)\n", result); |
| return 0; |
| } |
| if (max_size && max_size < result) { |
| zircon_boot_dlog("ZBI exceeds maximum size (%zu > %zu)\n", result, max_size); |
| return 0; |
| } |
| |
| return result; |
| } |
| |
| // Verifies a kernel that has already been loaded into memory. |
| // |
| // @ops: boot callbacks. |
| // @slot: slot to load, or NULL for slotless. |
| // @load_address: pointer to the loaded kernel. |
| // @load_address_size: kernel buffer max capacity. |
| static ZirconBootResult VerifyKernel(ZirconBootOps* ops, const AbrSlotIndex* slot, |
| void* load_address, size_t load_address_size) { |
| // Slotless boot uses empty string for suffix and is always treated as |
| // "successful". This allows us to still provide anti-rollback support so |
| // that the anti-rollback version stays up-to-date with the single slotless |
| // image. There's no purpose trying to actually track slotless image success |
| // because: |
| // 1. there's no image to fall back to on failure anyway, and |
| // 2. there's nowhere to store the success flag since we don't have A/B/R metadata |
| const char* ab_suffix = ""; |
| bool slot_is_marked_successful = true; |
| |
| if (slot != NULL) { |
| ab_suffix = AbrGetSlotSuffix(*slot); |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(ops); |
| AbrSlotInfo slot_info; |
| AbrResult res = AbrGetSlotInfo(&abr_ops, *slot, &slot_info); |
| if (res != kAbrResultOk) { |
| zircon_boot_dlog("Failed to get slot info %d\n", res); |
| return kBootResultErrorSlotVerification; |
| } |
| slot_is_marked_successful = slot_info.is_marked_successful; |
| } |
| |
| if (!ZirconVBootSlotVerify(ops, load_address, load_address_size, ab_suffix, |
| slot_is_marked_successful)) { |
| zircon_boot_dlog("Slot verification failed\n"); |
| return kBootResultErrorSlotVerification; |
| } |
| return kBootResultOK; |
| } |
| |
| // Loads and validates the kernel in the given slot. |
| // |
| // @ops: boot callbacks. |
| // @slot: slot to load, or NULL for slotless. |
| // @boot_flags: boot flags. |
| // @load_address: will be filled with the kernel load address. |
| // @load_address_size: will be filled with the kernel load capacity. |
| static ZirconBootResult LoadKernel(ZirconBootOps* ops, const AbrSlotIndex* slot, |
| ZirconBootFlags boot_flags, void** load_address, |
| size_t* load_address_size) { |
| const char* zircon_part = GetSlotPartitionName(slot); |
| if (zircon_part == NULL) { |
| zircon_boot_dlog("Invalid slot idx %d\n", *slot); |
| return kBootResultErrorInvalidSlotIdx; |
| } |
| zircon_boot_dlog("ABR: loading kernel from %s...\n", zircon_part); |
| |
| if (!ops->get_kernel_load_buffer) { |
| zircon_boot_dlog("Caller must implement get_kernel_load_buffer()\n"); |
| return kBootResultErrorImageTooLarge; |
| } |
| |
| // Allocate space to read a header of any supported kernel type. |
| // |
| // Static allocation to avoid the risk of stack overflow since the Android |
| // header is >1KiB. |
| static union { |
| zircon_boot_android_image_headers android_headers; |
| zbi_header_t zbi_header; |
| } headers; |
| bool zbi_header_loaded = false; |
| |
| // First check if it's an Android boot image, if the user allows it. |
| size_t zbi_offset = 0; |
| size_t zbi_max_size = 0; // 0 means no max size. |
| size_t read_size = 0; |
| if (boot_flags & kZirconBootFlagsAndroidBootImage) { |
| if (!ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, zircon_part, 0, sizeof(headers), &headers, |
| &read_size) || |
| read_size != sizeof(headers)) { |
| zircon_boot_dlog("Failed to load Android boot image header from disk\n"); |
| return kBootResultErrorReadHeader; |
| } |
| |
| if (zircon_boot_get_android_image_kernel_position(&headers.android_headers, &zbi_offset, |
| &zbi_max_size)) { |
| zircon_boot_dlog("Found Android boot image header\n"); |
| } else { |
| zircon_boot_dlog("Kernel does not look like an Android boot image\n"); |
| // In this case, we've loaded the kernel header but it's not Android boot |
| // image, we don't need to load it again to check for ZBI below. |
| zbi_header_loaded = true; |
| } |
| } |
| |
| // If we haven't already loaded what should be the ZBI header, load it now. |
| if (!zbi_header_loaded) { |
| if (!ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, zircon_part, zbi_offset, |
| sizeof(headers.zbi_header), &headers.zbi_header, &read_size) || |
| read_size != sizeof(headers.zbi_header)) { |
| zircon_boot_dlog("Failed to read ZBI header from disk\n"); |
| return kBootResultErrorReadHeader; |
| } |
| } |
| |
| // Figure out how large the ZBI header says the full image is, and make sure |
| // it fits within any previously determined size max. |
| size_t image_size = ZbiCheckSize(&headers.zbi_header, zbi_max_size); |
| if (image_size == 0) { |
| zircon_boot_dlog("Fail to find ZBI header\n"); |
| return kBootResultErrorInvalidZbi; |
| } |
| |
| // Use the size the ZBI header reports, not any Android image kernel size. |
| // Some use cases append ZBI + vbmeta in the Android boot image but we don't |
| // care about vbmeta here. |
| *load_address_size = image_size; |
| *load_address = ops->get_kernel_load_buffer(ops, load_address_size); |
| if (*load_address == NULL) { |
| *load_address_size = 0; |
| zircon_boot_dlog("Cannot get kernel load buffer\n"); |
| return kBootResultErrorImageTooLarge; |
| } |
| |
| if (!ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, zircon_part, zbi_offset, image_size, |
| *load_address, &read_size) || |
| read_size != image_size) { |
| zircon_boot_dlog("Fail to read ZBI image\n"); |
| return kBootResultErrorReadImage; |
| } |
| |
| if (IsVerifiedBootOpsImplemented(ops)) { |
| ZirconBootResult res = VerifyKernel(ops, slot, *load_address, *load_address_size); |
| if (res != kBootResultOK) { |
| return res; |
| } |
| } |
| |
| zircon_boot_dlog("Successfully loaded slot: %s\n", zircon_part); |
| return kBootResultOK; |
| } |
| |
| static bool ReadAbrMetaDataCache(void* context, size_t size, uint8_t* buffer) { |
| assert(size <= sizeof(AbrData)); |
| memcpy(buffer, context, size); |
| return true; |
| } |
| |
| static bool WriteAbrMetaDataCache(void* context, const uint8_t* buffer, size_t size) { |
| assert(size <= sizeof(AbrData)); |
| memcpy(context, buffer, size); |
| return true; |
| } |
| |
| // TODO(b/258467776): We can't use AbrGetBootSlot(.. update_metadata = false, ..) to get the slot to |
| // boot because it doesn't consider one shot recovery. |
| AbrSlotIndex AbrPeekBootSlot(const AbrOps* abr_ops) { |
| // Load abr metadata into memory and simulate storage. |
| uint8_t cache[sizeof(AbrData)]; |
| assert(abr_ops->read_abr_metadata); |
| if (!abr_ops->read_abr_metadata(abr_ops->context, sizeof(cache), cache)) { |
| return kAbrSlotIndexR; |
| } |
| |
| AbrOps cache_ops; |
| memset(&cache_ops, 0, sizeof(AbrOps)); |
| cache_ops.context = cache; |
| cache_ops.read_abr_metadata = ReadAbrMetaDataCache; |
| cache_ops.write_abr_metadata = WriteAbrMetaDataCache; |
| AbrSlotIndex ret = AbrGetBootSlot(&cache_ops, true, NULL); |
| return ret; |
| } |
| |
| // Loads and validates the kernel based on the provided boot mode A/B/R behavior. |
| // |
| // @ops: boot callbacks. |
| // @boot_flags: boot flags. |
| // @load_address: will be filled with the kernel load address. |
| // @load_address_size: will be filled with the kernel load capacity. |
| static ZirconBootResult LoadAbr(ZirconBootOps* ops, uint32_t boot_flags, void** load_address, |
| size_t* load_address_size) { |
| // The code is simpler if we allocate some slot storage and grab the A/B/R ops |
| // here, even though we won't use them in slotless boots. |
| AbrSlotIndex slot_storage; |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(ops); |
| |
| AbrSlotIndex* slot = (boot_flags & kZirconBootFlagsSlotless ? NULL : &slot_storage); |
| do { |
| // If we're doing a slotted boot, find the next slot to attempt. |
| if (slot != NULL) { |
| if (ops->firmware_can_boot_kernel_slot) { |
| // Make sure the firmware can boot the slot we're going to try. We have |
| // use AbrPeekSlot() here because we don't want to modify any data (e.g. |
| // boot attempt counters) since we might have to reboot first to get |
| // into the matching firmware slot. |
| *slot = |
| boot_flags & kZirconBootFlagsForceRecovery ? kAbrSlotIndexR : AbrPeekBootSlot(&abr_ops); |
| bool supported = false; |
| if (!ZIRCON_BOOT_OPS_CALL(ops, firmware_can_boot_kernel_slot, *slot, &supported)) { |
| zircon_boot_dlog("Fail to check slot supported\n"); |
| return kBootResultErrorIsSlotSupprotedByFirmware; |
| } |
| |
| if (!supported) { |
| zircon_boot_dlog( |
| "Target kernel slot %s is not supported by current firmware. Rebooting...\n", |
| AbrGetSlotSuffix(*slot)); |
| ZIRCON_BOOT_OPS_CALL(ops, reboot, boot_flags & kZirconBootFlagsForceRecovery); |
| zircon_boot_dlog("Should not reach here. Reboot handoff failed\n"); |
| return kBootResultRebootReturn; |
| } |
| } |
| |
| if (boot_flags & kZirconBootFlagsForceRecovery) { |
| *slot = kAbrSlotIndexR; |
| } else { |
| // This is the one place we call AbrGetBootSlot() which may modify the |
| // data to update retry counts, mark failed, etc. |
| *slot = AbrGetBootSlot(&abr_ops, true, NULL); |
| } |
| } |
| |
| ZirconBootResult ret = LoadKernel(ops, slot, boot_flags, load_address, load_address_size); |
| if (ret == kBootResultOK) { |
| break; |
| } |
| |
| // Slotless boot failure means nothing else to try; fail out. |
| if (slot == NULL) { |
| zircon_boot_dlog("Failed to load kernel\n"); |
| return ret; |
| } |
| |
| zircon_boot_dlog("Failed to load kernel in slot %d\n", *slot); |
| |
| // We always try R last, if it fails we're also out of things to try. |
| if (*slot == kAbrSlotIndexR) { |
| zircon_boot_dlog("Failed to boot: no valid slots\n"); |
| return kBootResultErrorNoValidSlot; |
| } |
| |
| // Otherwise, update A/B/R metadata to mark this slot unbootable so we |
| // try the next one in the next loop. |
| if (AbrMarkSlotUnbootable(&abr_ops, *slot) != kAbrResultOk) { |
| return kBootResultErrorMarkUnbootable; |
| } |
| } while (1); |
| |
| // If we got here, the kernel is loaded and validated. |
| // Add device-specific ZBI items via user-provided callback. |
| if (ops->add_zbi_items && !ops->add_zbi_items(ops, *load_address, *load_address_size, slot)) { |
| zircon_boot_dlog("Failed to add ZBI items\n"); |
| return kBootResultErrorAppendZbiItems; |
| } |
| |
| return kBootResultOK; |
| } |
| |
| ZirconBootResult LoadAndBoot(ZirconBootOps* ops, uint32_t boot_flags) { |
| if ((boot_flags & kZirconBootFlagsForceRecovery) && (boot_flags & kZirconBootFlagsSlotless)) { |
| zircon_boot_dlog("Cannot give both ForceRecovery and Slotless flags\n"); |
| return kBootResultErrorInvalidArguments; |
| } |
| |
| void* load_address = NULL; |
| size_t load_address_size = 0; |
| ZirconBootResult res = LoadAbr(ops, boot_flags, &load_address, &load_address_size); |
| if (res != kBootResultOK) { |
| return res; |
| } |
| |
| ZIRCON_BOOT_OPS_CALL(ops, boot, load_address, load_address_size); |
| zircon_boot_dlog("Should not reach here. Boot handoff failed\n"); |
| return kBootResultBootReturn; |
| } |
| |
| ZirconBootResult LoadFromRam(ZirconBootOps* ops, const void* image, size_t size, zbi_header_t** zbi, |
| size_t* zbi_capacity) { |
| RambootContext ramboot_context = {}; |
| ZirconBootOps ramboot_ops = {}; |
| ZirconBootResult res = SetupRambootOps(ops, image, size, &ramboot_context, &ramboot_ops); |
| if (res != kBootResultOK) { |
| return res; |
| } |
| |
| void* load_address = NULL; |
| size_t load_address_size = 0; |
| // Maybe counter-intuitively, we do *not* need to pass `kZirconBootFlagsAndroidBootImage` here. |
| // This is because in the RAM-boot case we already had to parse the image in order to extract |
| // any vbmeta data that may have been concatenated, so we've already unpacked the Android image |
| // format. |
| // |
| // If we end up adding support for concatenated ZBI + vbmeta in the load-from-disk flow, we can |
| // use the `kZirconBootFlagsAndroidBootImage` flag here and just pass the RAM image through |
| // directly. |
| res = LoadAbr(&ramboot_ops, kZirconBootFlagsSlotless, &load_address, &load_address_size); |
| if (res != kBootResultOK) { |
| return res; |
| } |
| |
| *zbi = (zbi_header_t*)load_address; |
| *zbi_capacity = load_address_size; |
| return kBootResultOK; |
| } |
| |
| AbrSlotIndex GetActiveBootSlot(ZirconBootOps* ops) { |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(ops); |
| return AbrPeekBootSlot(&abr_ops); |
| } |