blob: b7b53643d462649012eb200100bdca262c4a7ae7 [file] [log] [blame]
// 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);
}