blob: 14b5eb0fee429eba71dbb95a30a8771c56f37603 [file] [log] [blame]
/*
* Copyright (c) 2019 The Fuchsia Authors
*/
#include <abr/abr.h>
#include <abr/ops.h>
#include <libavb/libavb.h>
#include <zircon/boot/image.h>
#include <zircon-estelle/partition.h>
#include <zircon-estelle/vboot.h>
#include <zircon-estelle/zircon.h>
#include <common.h>
#include <asm/arch/bl31_apis.h>
#include <asm/arch/secure_apb.h>
#include <linux/ctype.h>
#include <linux/list.h>
#include <zbi/zbi.h>
#include <stdlib.h>
#include <linux/compat.h>
#define errorP(fmt...) \
printf("Err zircon load(%s:%d):", __func__, __LINE__), printf(fmt)
#ifdef DEBUG
#define debugP(fmt...) \
printf("[Dbg zircon load]%s:%d:", __func__, __LINE__), printf(fmt)
#else
#define debugP(fmt...)
#endif
#define CONFIG_AML_SECURE_BOOT_VERSION 4
#define AML_SECURE_HEADER_SIZE 512
#define AVB_DATA_FREE(x) \
do { \
if (x != NULL) { \
avb_slot_verify_data_free(x); \
x = NULL; \
} \
} while (0);
static AvbSlotVerifyData *g_slot_verify_data = NULL;
/* libabr callback implementations to read/write metadata from disk. */
#ifdef CONFIG_ABR_WEAR_LEVELING
#define ABR_PARTITION_NAME "abr-wear-leveling"
#else
#define ABR_PARTITION_NAME "misc"
#endif
static uint8_t abr_read_write_buffer[PAGE_SIZE];
static bool read_abr_metadata(void *context, size_t size, uint8_t *buffer)
{
assert(buffer != NULL);
if (size > sizeof(abr_read_write_buffer)) {
errorP("Unexpected read size for abr metadata, %zu\n", size);
return false;
}
debugP("Read A/B/R metadata from " ABR_PARTITION_NAME " partition\n");
if (zircon_partition_read(ABR_PARTITION_NAME, 0, abr_read_write_buffer,
sizeof(abr_read_write_buffer)) != 0) {
errorP("failed to read A/B/R metadata.\n");
return false;
}
memcpy(buffer, abr_read_write_buffer, size);
return true;
}
static bool write_abr_metadata(void *context, const uint8_t *buffer,
size_t size)
{
assert(buffer != NULL);
if (size > sizeof(abr_read_write_buffer)) {
errorP("Unexpected write size for abr metadata, %zu\n", size);
return false;
}
memcpy(abr_read_write_buffer, buffer, size);
debugP("Write A/B/R metadata to " ABR_PARTITION_NAME " partition\n");
if (zircon_partition_write(ABR_PARTITION_NAME, 0, abr_read_write_buffer,
sizeof(abr_read_write_buffer)) != 0) {
errorP("failed to read A/B/R metadata.\n");
return false;
}
return true;
}
static const AbrOps kAbrOps = { .context = NULL,
.read_abr_metadata = read_abr_metadata,
.write_abr_metadata = write_abr_metadata };
struct property_lookup_user_data {
zbi_header_t *zbi;
size_t capacity;
};
#define ZBI_PROPERTY_PREFIX "zbi"
/* If the given property holds a ZBI container, appends its contents to the ZBI
* container in |lookup_data|. */
static void
process_property(const AvbPropertyDescriptor *prop_desc,
const struct property_lookup_user_data *lookup_data)
{
const char *key =
(const char *)prop_desc + sizeof(AvbPropertyDescriptor);
uint64_t offset;
if (!avb_safe_add(&offset, sizeof(AvbPropertyDescriptor) + 1,
prop_desc->key_num_bytes)) {
fprintf(stderr,
"Overflow while computing offset for property value."
"Skipping this property descriptor.\n");
return;
}
const uint8_t *value = (const uint8_t *)prop_desc + offset;
if (key[prop_desc->key_num_bytes] != 0) {
fprintf(stderr, "No terminating NUL byte in the property key."
"Skipping this property descriptor.\n");
return;
}
if (value[prop_desc->value_num_bytes] != 0) {
fprintf(stderr, "No terminating NUL byte in the property value."
"Skipping this property descriptor.\n");
return;
}
/* Only look at properties whose keys start with the 'zbi' prefix. */
if (strncmp(key, ZBI_PROPERTY_PREFIX, strlen(ZBI_PROPERTY_PREFIX))) {
return;
}
const zbi_header_t *vbmeta_zbi = (zbi_header_t *)value;
printf("Found vbmeta ZBI property '%s' (%llu bytes)\n", key,
prop_desc->value_num_bytes);
const uint64_t zbi_size = sizeof(*vbmeta_zbi) + vbmeta_zbi->length;
if (zbi_size > prop_desc->value_num_bytes) {
fprintf(stderr,
"vbmeta ZBI length exceeds property size (%llu > %llu)\n",
zbi_size, prop_desc->value_num_bytes);
return;
}
zbi_result_t result = zbi_check(vbmeta_zbi, NULL);
if (result != ZBI_RESULT_OK) {
fprintf(stderr, "Mal-formed vbmeta ZBI: error %d\n", result);
return;
}
result =
zbi_extend(lookup_data->zbi, lookup_data->capacity, vbmeta_zbi);
if (result != ZBI_RESULT_OK) {
fprintf(stderr, "Failed to add vbmeta ZBI: error %d\n", result);
return;
}
}
/* Callback for vbmeta property iteration. |user_data| must be a pointer to a
* property_lookup_user_data struct. */
static bool property_lookup_desc_foreach(const AvbDescriptor *header,
void *user_data)
{
if (header->tag != AVB_DESCRIPTOR_TAG_PROPERTY) {
return true;
}
AvbPropertyDescriptor *prop_desc = (AvbPropertyDescriptor *)header;
/* recover original bytes order at the end of the function */
if (!avb_property_descriptor_validate_and_byteswap(prop_desc,
prop_desc)) {
return true;
}
process_property(prop_desc,
(struct property_lookup_user_data *)user_data);
/* return error if byte order recovering failed */
if (!avb_property_descriptor_validate_and_byteswap(prop_desc,
prop_desc)) {
errorP("failed to recover byte order in a property descriptor.\n");
return false;
}
return true;
}
#define CMDLINE_ZVB_SLOT_INFO_SIZE 32
int zircon_vboot_add_extra_zbi_items(zbi_header_t *zbi, size_t capacity)
{
int i;
if (zbi == NULL) {
errorP("Invalid argument: zbi is NULL\n");
return __LINE__;
}
if (g_slot_verify_data == NULL) {
debugP("No properties to load\n");
return 0;
}
if ((g_slot_verify_data->ab_suffix != NULL) &&
strlen(g_slot_verify_data->ab_suffix)) {
char slot_info[CMDLINE_ZVB_SLOT_INFO_SIZE];
snprintf(slot_info, sizeof(slot_info), "zvb.current_slot=%s",
g_slot_verify_data->ab_suffix);
if (append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_CMDLINE, 0,
slot_info, strlen(slot_info) + 1)) {
return -1;
}
}
struct property_lookup_user_data lookup_data = { .zbi = zbi,
.capacity = capacity };
for (i = 0; i < g_slot_verify_data->num_vbmeta_images; ++i) {
AvbVBMetaData *vb = &g_slot_verify_data->vbmeta_images[i];
/* load properties into KV store */
if (!avb_descriptor_foreach(vb->vbmeta_data, vb->vbmeta_size,
property_lookup_desc_foreach,
&lookup_data)) {
AVB_DATA_FREE(g_slot_verify_data);
errorP("Fail to parse VBMETA properties\n");
return __LINE__;
}
}
return 0;
}
int zircon_vboot_img_load(unsigned char *loadaddr, size_t loadsize,
bool force_recovery)
{
uint64_t img_size = 0;
int ret = 0;
unsigned int slot_idx;
AvbSlotVerifyData *out_data = NULL;
const char *const requested_partitions[2] = { "zircon", NULL };
/* clear previous data */
AVB_DATA_FREE(g_slot_verify_data);
do {
AvbSlotVerifyResult verify_result;
bool set_slot_unbootable = false;
bool is_successful = false;
AVB_DATA_FREE(out_data);
/* check recovery mode */
if (force_recovery) {
slot_idx = kAbrSlotIndexR;
} else {
slot_idx =
AbrGetBootSlot(&kAbrOps, true, &is_successful);
assert(slot_idx < 3);
}
verify_result = zircon_vboot_slot_verify(
loadaddr, loadsize, requested_partitions,
AbrGetSlotSuffix(slot_idx),
zircon_is_vboot_enabled() ? AVB_ATX_LOCKED :
AVB_ATX_UNLOCKED,
(is_successful == true) ?
AVB_ATX_SLOT_MARKED_SUCCESSFUL :
AVB_ATX_SLOT_NOT_MARKED_SUCCESSFUL,
&out_data);
debugP("AVB verify status is '%s'\n",
avb_slot_verify_result_to_string(verify_result));
switch (verify_result) {
case AVB_SLOT_VERIFY_RESULT_OK:
ret = 0;
break;
/* non-recoverable error. abort execution. */
case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT:
errorP("Error verifying slot %d with result %s"
" - abort execution.\n",
slot_idx,
avb_slot_verify_result_to_string(verify_result));
AVB_DATA_FREE(out_data);
return __LINE__;
case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
case AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION:
/* Even with AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR
* these mean game over.
*/
set_slot_unbootable = true;
ret = __LINE__;
break;
/* security verification failed */
case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
if (!zircon_is_vboot_enabled()) {
/* Do nothing since we allow this. */
debugP("Allowing slot %d which verified with result "
"%s because vboot disabled\n",
slot_idx,
avb_slot_verify_result_to_string(
verify_result));
ret = 0;
} else {
set_slot_unbootable = true;
ret = __LINE__;
}
break;
/* Do not add a 'default:' case here because of -Wswitch. */
}
if (set_slot_unbootable) {
errorP("Error verifying slot %d with result %s "
"-- setting unbootable\n",
slot_idx,
avb_slot_verify_result_to_string(verify_result));
AbrResult res =
AbrMarkSlotUnbootable(&kAbrOps, slot_idx);
if (res != kAbrResultOk) {
errorP("Fail to set A/B/R metadata.\n");
AVB_DATA_FREE(out_data);
return __LINE__;
}
}
} while ((ret != 0) && (slot_idx != kAbrSlotIndexR));
if (ret != 0) {
errorP("Fail to boot: no valid slots\n");
AVB_DATA_FREE(out_data);
return __LINE__;
}
/* save it for late use */
assert(g_slot_verify_data == NULL);
g_slot_verify_data = out_data;
/*
* because secure boot will use DMA which need disable MMU temp
* here must update the cache, otherwise nand will fail (eMMC is OK)
*/
flush_cache((unsigned long)loadaddr, (unsigned long)img_size);
return 0;
}
int zircon_vboot_preloaded_img_load(unsigned char *loadaddr, size_t loadsize,
unsigned char *zbi, size_t zbi_size,
unsigned char *vbmeta, size_t vbmeta_size)
{
AVB_DATA_FREE(g_slot_verify_data);
bool verified = zircon_vboot_preloaded_img_verify(
zbi, zbi_size, vbmeta, vbmeta_size, &g_slot_verify_data);
if (!verified || zbi_size > loadsize) {
return -1;
}
memcpy(loadaddr, zbi, zbi_size);
return 0;
}
const char *zircon_vboot_get_current_slot(void)
{
int slot = AbrGetBootSlot(&kAbrOps, false, NULL);
return AbrGetSlotSuffix(slot);
}
const char *zircon_vboot_get_slot_last_set_active(void)
{
AbrSlotIndex slot;
AbrResult res = AbrGetSlotLastMarkedActive(&kAbrOps, &slot);
if (res != kAbrResultOk) {
return NULL;
}
const char *ret = AbrGetSlotSuffix(slot);
//&ret[1] skips the first '_' character. i.e. "_a" is returned as "a".
return ret ? &ret[1] : NULL;
}
int zircon_vboot_get_slot_info(int slot_number, AbrSlotInfo *info)
{
AbrResult res = AbrGetSlotInfo(&kAbrOps, slot_number, info);
if (res != kAbrResultOk) {
errorP("Fail to get slot info\n");
return __LINE__;
}
return 0;
}
int zircon_vboot_set_slot_active(int slot_number)
{
AbrResult ret = AbrMarkSlotActive(&kAbrOps, slot_number);
if (ret != kAbrResultOk) {
errorP("Fail to get slot info\n");
return __LINE__;
}
return 0;
}