blob: 3700962b03f26e42f88bc928ad93f4ca9ac4586b [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/zbi/zbi.h>
#include <lib/zircon_boot/zircon_boot.h>
#include <zircon/hw/gpt.h>
#include "utils.h"
#include "zircon_vboot.h"
const char* GetSlotPartitionName(AbrSlotIndex slot) {
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 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;
}
static ZirconBootResult VerifyKernel(ZirconBootOps* zb_ops, void* load_address,
size_t load_address_size, AbrSlotIndex slot) {
const char* ab_suffix = AbrGetSlotSuffix(slot);
AbrOps abr_ops = GetAbrOpsFromZirconBootOps(zb_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;
}
if (!ZirconVBootSlotVerify(zb_ops, load_address, load_address_size, ab_suffix,
slot_info.is_marked_successful)) {
zircon_boot_dlog("Slot verification failed\n");
return kBootResultErrorSlotVerification;
}
return kBootResultOK;
}
static ZirconBootResult LoadKernel(void* load_address, size_t load_address_size, AbrSlotIndex slot,
ZirconBootOps* ops) {
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 slot %s...\n", zircon_part);
zbi_header_t zbi_hdr __attribute__((aligned(ZBI_ALIGNMENT)));
size_t read_size;
// This library only deals with zircon image and assume that it always starts from 0 offset.
if (!ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, zircon_part, 0, sizeof(zbi_hdr), &zbi_hdr,
&read_size) ||
read_size != sizeof(zbi_hdr)) {
zircon_boot_dlog("Failed to read header from slot\n");
return kBootResultErrorReadHeader;
}
if (zbi_hdr.type != ZBI_TYPE_CONTAINER || zbi_hdr.extra != ZBI_CONTAINER_MAGIC ||
zbi_hdr.magic != ZBI_ITEM_MAGIC) {
zircon_boot_dlog("Fail to find ZBI header\n");
return kBootResultErrorZbiHeaderNotFound;
}
uint64_t image_size = zbi_hdr.length + sizeof(zbi_header_t);
if (image_size > load_address_size) {
zircon_boot_dlog("Image is too large to load (%lu > %zu)\n", image_size, load_address_size);
return kBootResultErrorImageTooLarge;
}
if (!ZIRCON_BOOT_OPS_CALL(ops, read_from_partition, zircon_part, 0, image_size, load_address,
&read_size) ||
read_size != image_size) {
zircon_boot_dlog("Fail to read image from slot\n");
return kBootResultErrorReadImage;
}
if (IsVerifiedBootOpsImplemented(ops)) {
ZirconBootResult res = VerifyKernel(ops, load_address, load_address_size, slot);
if (res != kBootResultOK) {
return res;
}
}
zircon_boot_dlog("Successfully loaded slot: %s\n", zircon_part);
return kBootResultOK;
}
static ZirconBootResult LoadImageOsAbr(ZirconBootOps* ops, void* load_address,
size_t load_address_size, ForceRecovery force_recovery,
AbrSlotIndex* out_slot) {
ZirconBootResult ret;
AbrSlotIndex cur_slot;
AbrOps abr_ops = GetAbrOpsFromZirconBootOps(ops);
do {
/* check recovery mode */
cur_slot =
force_recovery == kForceRecoveryOn ? kAbrSlotIndexR : AbrGetBootSlot(&abr_ops, true, NULL);
ret = LoadKernel(load_address, load_address_size, cur_slot, ops);
if (ret != kBootResultOK) {
zircon_boot_dlog("ABR: failed to load slot %d\n", cur_slot);
if (cur_slot != kAbrSlotIndexR && AbrMarkSlotUnbootable(&abr_ops, cur_slot) != kAbrResultOk) {
return kBootResultErrorMarkUnbootable;
}
}
} while ((ret != kBootResultOK) && (cur_slot != kAbrSlotIndexR));
if (ret != kBootResultOK) {
zircon_boot_dlog("Fail to boot: no valid slots\n");
return kBootResultErrorNoValidSlot;
}
*out_slot = cur_slot;
return kBootResultOK;
}
static ZirconBootResult LoadImageFirmwareAbr(ZirconBootOps* ops, void* load_address,
size_t load_address_size, ForceRecovery force_recovery,
AbrSlotIndex* out_slot) {
// Only boot to matching firmware and OS slot.
AbrSlotIndex firmware_slot;
if (!ZIRCON_BOOT_OPS_CALL(ops, get_firmware_slot, &firmware_slot)) {
zircon_boot_dlog("Fail to get firmware slot\n");
return kBootResultErrorGetFirmwareSlot;
}
AbrOps abr_ops = GetAbrOpsFromZirconBootOps(ops);
AbrSlotIndex target_slot =
force_recovery == kForceRecoveryOn ? kAbrSlotIndexR : AbrGetBootSlot(&abr_ops, false, NULL);
if (firmware_slot != target_slot) {
zircon_boot_dlog(
"Device is in firmware slot %s. But metadata/force-recovery suggests "
"slot %s. Refusing to continue.\n",
AbrGetSlotSuffix(firmware_slot), AbrGetSlotSuffix(target_slot));
return kBootResultErrorMismatchedFirmwareSlot;
}
*out_slot = target_slot;
if (target_slot != kAbrSlotIndexR) {
zircon_boot_dlog("updating metadata\n");
// set |update_metadat| to true to decrement retry counter if applicable.
AbrGetBootSlot(&abr_ops, true, NULL);
}
ZirconBootResult ret = LoadKernel(load_address, load_address_size, target_slot, ops);
if (ret != kBootResultOK) {
zircon_boot_dlog("ABR: failed to load slot %d\n", target_slot);
if (target_slot != kAbrSlotIndexR &&
AbrMarkSlotUnbootable(&abr_ops, target_slot) != kAbrResultOk) {
return kBootResultErrorMarkUnbootable;
}
return kBootResultErrorSlotFail;
}
return kBootResultOK;
}
ZirconBootResult LoadAndBoot(ZirconBootOps* ops, void* load_address, size_t load_address_size,
ForceRecovery force_recovery) {
AbrSlotIndex slot;
ZirconBootResult res;
if (ops->get_firmware_slot) {
res = LoadImageFirmwareAbr(ops, load_address, load_address_size, force_recovery, &slot);
if (res == kBootResultErrorSlotFail && slot == kAbrSlotIndexR) {
// Recovery failure and not because of slot mismatch. Simply return. Up to the device to
// decide what to do.
return res;
} else if (res == kBootResultErrorMismatchedFirmwareSlot || res == kBootResultErrorSlotFail) {
ZIRCON_BOOT_OPS_CALL(ops, reboot, force_recovery);
zircon_boot_dlog("Should not reach here. Reboot handoff failed\n");
return kBootResultRebootReturn;
}
} else {
res = LoadImageOsAbr(ops, load_address, load_address_size, force_recovery, &slot);
}
if (res != kBootResultOK) {
return res;
}
// Adds device-specific ZBI items provided by device through |add_zbi_items| method
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;
}
ZIRCON_BOOT_OPS_CALL(ops, boot, load_address, load_address_size, slot);
zircon_boot_dlog("Should not reach here. Boot handoff failed\n");
return kBootResultBootReturn;
}