blob: 9248ed5773c79296769777ea10d85d229cd68039 [file] [log] [blame]
/*
* Copyright (c) 2019 The Fuchsia Authors
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <common.h>
#include <asm/arch/efuse.h>
#include <asm/arch/secure_apb.h>
#include <asm/io.h>
#include <libavb/libavb.h>
#include <libavb_atx/libavb_atx.h>
#include <tee/ta_vx_helper.h>
#include <zbi/zbi.h>
#include <zircon_uboot/partition.h>
#include <zircon_uboot/vboot.h>
#include <zircon_uboot/zircon.h>
#define AVB_ATX_NUM_KEY_VERSIONS 2
/* By convention, when a rollback index is not used, the value remains zero. */
#define ROLLBACK_INDEX_NOT_USED (0)
extern AvbIOResult
avb_read_permanent_attributes(AvbAtxOps *atx_ops,
AvbAtxPermanentAttributes *attributes);
extern AvbIOResult
avb_read_permanent_attributes_hash(AvbAtxOps *atx_ops,
uint8_t hash[AVB_SHA256_DIGEST_SIZE]);
typedef struct {
struct {
size_t location;
uint64_t value;
} key_versions[AVB_ATX_NUM_KEY_VERSIONS];
size_t next_key_version_index;
uint8_t *preloaded_img_addr;
uint64_t preloaded_img_size;
} vboot_context_t;
/* If a negative offset is given, computes the unsigned offset. */
static inline int64_t calc_offset(uint64_t size, int64_t offset)
{
if (offset < 0) {
return size + offset;
}
return offset;
}
static AvbIOResult read_from_partition(AvbOps *ops, const char *partition,
int64_t offset, size_t num_bytes,
void *buffer, size_t *out_num_read)
{
int64_t abs_offset;
zircon_partition *part = zircon_get_partition(partition);
if (!part) {
return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
}
abs_offset = calc_offset(part->size, offset);
if ((abs_offset > part->size) || (abs_offset < 0)) {
zircon_free_partition(part);
return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
}
if ((abs_offset + num_bytes) > part->size) {
num_bytes = part->size - abs_offset;
}
if (part->read(part, abs_offset, buffer, num_bytes)) {
zircon_free_partition(part);
return AVB_IO_RESULT_ERROR_IO;
}
*out_num_read = num_bytes;
zircon_free_partition(part);
return AVB_IO_RESULT_OK;
}
static AvbIOResult get_preloaded_partition(AvbOps *ops, const char *partition,
size_t num_bytes,
uint8_t **out_pointer,
size_t *out_num_bytes_preloaded)
{
vboot_context_t *context = (vboot_context_t *)ops->user_data;
*out_pointer = NULL;
*out_num_bytes_preloaded = 0;
if (!strncmp(partition, ZIRCON_PARTITION_PREFIX,
strlen(ZIRCON_PARTITION_PREFIX))) {
*out_pointer = context->preloaded_img_addr;
if (num_bytes <= context->preloaded_img_size) {
*out_num_bytes_preloaded = num_bytes;
} else {
*out_num_bytes_preloaded = context->preloaded_img_size;
}
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult write_to_partition(AvbOps *ops, const char *partition,
int64_t offset, size_t num_bytes,
const void *buffer)
{
// Our usage of libavb should never be writing to a partition - this is only
// used by the (deprecated) libavb_ab extension.
printf("Error: libavb write_to_partition() unimplemented\n");
return AVB_IO_RESULT_ERROR_IO;
}
/* avb_slot_verify uses this call to check that a partition exists.
* Checks for existence but ignores GUID because it's unused. */
static AvbIOResult get_unique_guid_for_partition(AvbOps *ops,
const char *partition,
char *guid_buf,
size_t guid_buf_size)
{
zircon_partition *part = zircon_get_partition(partition);
if (!part) {
return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
}
zircon_free_partition(part);
guid_buf[0] = '\0';
return AVB_IO_RESULT_OK;
}
static AvbIOResult get_size_of_partition(AvbOps *ops, const char *partition,
uint64_t *out_size_num_bytes)
{
zircon_partition *part = zircon_get_partition(partition);
if (!part) {
return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
}
*out_size_num_bytes = part->size;
zircon_free_partition(part);
return AVB_IO_RESULT_OK;
}
static AvbIOResult get_random(AvbAtxOps *atx_ops, size_t num_bytes,
uint8_t *output)
{
if (ta_vx_cprng_draw(output, num_bytes)) {
return AVB_IO_RESULT_ERROR_IO;
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult read_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked)
{
if (ta_vx_is_unlocked(out_is_unlocked)) {
return AVB_IO_RESULT_ERROR_IO;
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult read_persistent_value(AvbOps *ops, const char *name,
size_t buffer_size,
uint8_t *out_buffer,
size_t *out_num_bytes_read)
{
if (ta_vx_read_persistent_value(name, out_buffer, buffer_size,
out_num_bytes_read)) {
// Per contract with avb_ops->read_persistent_value, we should
// return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE when the specified
// value does not exist, is not supported, or is not populated.
//
// In order to relieve the complexity with error propagation
// from the TA, we consider any error as if the error was
// "no such value".
return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult write_persistent_value(AvbOps *ops, const char *name,
size_t value_size,
const uint8_t *value)
{
// Per contract with avb_ops->write_persistent_value, if |value_size|
// is zero, future calls to |read_persisent_value| shall return
// AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. That means we should delete the
// value if |value_size| is zero.
if (value_size == 0 && ta_vx_delete_persistent_value(name)) {
return AVB_IO_RESULT_ERROR_IO;
}
if (value_size > 0 &&
ta_vx_write_persistent_value(name, value, value_size)) {
return AVB_IO_RESULT_ERROR_IO;
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult avb_read_rollback_index(AvbOps *ops,
size_t rollback_index_location,
uint64_t *out_rollback_index)
{
if (ta_vx_read_rollback_index(rollback_index_location,
out_rollback_index)) {
return AVB_IO_RESULT_ERROR_IO;
}
return AVB_IO_RESULT_OK;
}
static AvbIOResult avb_write_rollback_index(AvbOps *ops,
size_t rollback_index_location,
uint64_t rollback_index)
{
if (ta_vx_write_rollback_index(rollback_index_location,
rollback_index)) {
return AVB_IO_RESULT_ERROR_IO;
}
return AVB_IO_RESULT_OK;
}
static void set_key_version(AvbAtxOps *atx_ops, size_t rollback_index_location,
uint64_t key_version)
{
vboot_context_t *context = (vboot_context_t *)atx_ops->ops->user_data;
size_t index = context->next_key_version_index++;
if (index < AVB_ATX_NUM_KEY_VERSIONS) {
context->key_versions[index].location = rollback_index_location;
context->key_versions[index].value = key_version;
} else {
printf("ERROR: set_key_version index out of bounds: %lu\n",
index);
avb_abort();
}
}
static AvbOps ops;
static AvbAtxOps atx_ops = {
.ops = &ops,
.read_permanent_attributes = avb_read_permanent_attributes,
.read_permanent_attributes_hash = avb_read_permanent_attributes_hash,
.set_key_version = set_key_version,
.get_random = get_random,
};
static AvbOps ops = {
.atx_ops = &atx_ops,
.read_from_partition = read_from_partition,
.get_preloaded_partition = get_preloaded_partition,
.write_to_partition = write_to_partition,
.validate_vbmeta_public_key = avb_atx_validate_vbmeta_public_key,
.read_rollback_index = avb_read_rollback_index,
.write_rollback_index = avb_write_rollback_index,
.read_is_device_unlocked = read_is_device_unlocked,
.get_unique_guid_for_partition = get_unique_guid_for_partition,
.get_size_of_partition = get_size_of_partition,
.read_persistent_value = read_persistent_value,
.write_persistent_value = write_persistent_value,
};
struct property_lookup_user_data {
zbi_header_t *zbi;
size_t capacity;
};
static bool property_lookup_desc_foreach(const AvbDescriptor *header,
void *user_data);
static int process_verify_data(AvbOps *ops, AvbSlotVerifyResult result,
AvbSlotVerifyData *verify_data,
vboot_context_t *context, zbi_header_t *zbi,
size_t capacity, bool has_successfully_booted)
{
// Copy zbi items within vbmeta regardless of lock state.
if (zbi && result == AVB_SLOT_VERIFY_RESULT_OK) {
struct property_lookup_user_data lookup_data = {
.zbi = zbi, .capacity = capacity
};
for (int i = 0; i < verify_data->num_vbmeta_images; ++i) {
AvbVBMetaData *vb = &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)) {
fprintf(stderr,
"Fail to parse VBMETA properties\n");
return -1;
}
}
}
bool unlocked;
if (ops->read_is_device_unlocked(ops, &unlocked)) {
fprintf(stderr, "Failed to read lock state.\n");
return -1;
}
if (unlocked) {
printf("Device unlocked: not checking verification result.\n");
return 0;
}
if (result != AVB_SLOT_VERIFY_RESULT_OK) {
fprintf(stderr, "Failed to verify, err_code: %s\n",
avb_slot_verify_result_to_string(result));
return -1;
}
// Increase rollback index values to match the verified slot only if
// it has already successfully booted.
if (has_successfully_booted) {
int i;
for (i = 0; i < ARRAY_SIZE(verify_data->rollback_indexes);
i++) {
uint64_t rollback_index_value =
verify_data->rollback_indexes[i];
if (rollback_index_value == ROLLBACK_INDEX_NOT_USED) {
continue;
}
result = ops->write_rollback_index(
ops, i, rollback_index_value);
if (result != AVB_SLOT_VERIFY_RESULT_OK) {
fprintf(stderr,
"Failed to write rollback index: %d\n",
i);
return -1;
}
}
/* Also increase rollback index values for Fuchsia key version locations.
*/
if (context == NULL) {
fprintf(stderr, "key version context not found\n");
return -1;
}
for (i = 0; i < AVB_ATX_NUM_KEY_VERSIONS; i++) {
result = ops->write_rollback_index(
ops, context->key_versions[i].location,
context->key_versions[i].value);
if (result != AVB_SLOT_VERIFY_RESULT_OK) {
fprintf(stderr,
"Failed to write rollback index: %zu\n",
context->key_versions[i].location);
return -1;
}
}
}
return 0;
}
int zircon_vboot_slot_verify(unsigned char *loadaddr, uint64_t img_size,
const char *ab_suffix,
bool has_successfully_booted, zbi_header_t *zbi,
size_t capacity)
{
vboot_context_t context = { 0 };
context.preloaded_img_addr = loadaddr;
context.preloaded_img_size = img_size;
ops.user_data = (void *)&context;
const char *const requested_partitions[] = {
ZIRCON_PARTITION_PREFIX,
#ifdef CONFIG_FACTORY_BOOT_KVS
CONFIG_FACTORY_BOOT_PARTITION_NAME,
#else
"factory",
#endif
NULL
};
int ret = ta_vx_lock_if_ephemerally_unlocked();
if (ret) {
return ret;
}
AvbSlotVerifyData *verify_data = NULL;
AvbSlotVerifyResult result =
avb_slot_verify(&ops, requested_partitions, ab_suffix,
AVB_SLOT_VERIFY_FLAGS_NONE,
AVB_HASHTREE_ERROR_MODE_EIO, &verify_data);
ret = process_verify_data(&ops, result, verify_data, &context, zbi,
capacity, has_successfully_booted);
if (verify_data) {
avb_slot_verify_data_free(verify_data);
}
return ret;
}
typedef struct {
uint8_t *preloaded_img_addr;
size_t preloaded_img_size;
uint8_t *preloaded_vbmeta_addr;
size_t preloaded_vbmeta_size;
} vboot_ramboot_context_t;
#define VBOOT_RAMBOOT_ZBI_PARTITION "ramboot_zbi"
static AvbIOResult
get_preloaded_partition_ramboot(AvbOps *ops, const char *partition,
size_t num_bytes, uint8_t **out_pointer,
size_t *out_num_bytes_preloaded)
{
vboot_ramboot_context_t *context =
(vboot_ramboot_context_t *)ops->user_data;
// Only support preloaded `ramboot_zbi` partition
if (strcmp(partition, VBOOT_RAMBOOT_ZBI_PARTITION)) {
*out_pointer = NULL;
return AVB_IO_RESULT_OK;
}
*out_num_bytes_preloaded = min(num_bytes, context->preloaded_img_size);
*out_pointer = context->preloaded_img_addr;
return AVB_IO_RESULT_OK;
}
static AvbIOResult read_from_partition_ramboot(AvbOps *ops,
const char *partition,
int64_t offset, size_t num_bytes,
void *buffer,
size_t *out_num_read)
{
// Only read "factory" partition, which is validated by vbmeta
if (!strcmp(partition, "factory")) {
return read_from_partition(ops, "factory", offset, num_bytes,
buffer, out_num_read);
}
if (strcmp(partition, "vbmeta") || offset != 0) {
return AVB_SLOT_VERIFY_RESULT_ERROR_IO;
}
vboot_ramboot_context_t *context =
(vboot_ramboot_context_t *)ops->user_data;
*out_num_read = min(num_bytes, context->preloaded_vbmeta_size);
memcpy(buffer, context->preloaded_vbmeta_addr, *out_num_read);
return AVB_IO_RESULT_OK;
}
/* This is not called, but needs to be non-null. */
static AvbIOResult get_size_of_partition_ramboot(AvbOps *ops,
const char *partition,
uint64_t *out_size_num_bytes)
{
return AVB_SLOT_VERIFY_RESULT_ERROR_IO;
}
/* Assume all partitions exist for ramboot. */
static AvbIOResult get_unique_guid_for_partition_ramboot(AvbOps *ops,
const char *partition,
char *guid_buf,
size_t guid_buf_size)
{
guid_buf[0] = '\0';
return AVB_IO_RESULT_OK;
}
/* RAM-booting does not increment key versions. */
static void set_key_version_ramboot(AvbAtxOps *atx_ops,
size_t rollback_index_location,
uint64_t key_version)
{
}
// ramboot_atx_ops and ramboot_ops have all disk write functionality removed.
static AvbAtxOps ramboot_atx_ops = {
.ops = &ops,
.read_permanent_attributes = avb_read_permanent_attributes,
.read_permanent_attributes_hash = avb_read_permanent_attributes_hash,
.set_key_version = set_key_version_ramboot,
.get_random = get_random,
};
static AvbOps ramboot_ops = {
.atx_ops = &ramboot_atx_ops,
.read_from_partition = read_from_partition_ramboot,
.get_preloaded_partition = get_preloaded_partition_ramboot,
.validate_vbmeta_public_key = avb_atx_validate_vbmeta_public_key,
.read_rollback_index = avb_read_rollback_index,
.read_is_device_unlocked = read_is_device_unlocked,
.get_unique_guid_for_partition = get_unique_guid_for_partition_ramboot,
.get_size_of_partition = get_size_of_partition_ramboot,
.read_persistent_value = read_persistent_value,
// This is required to initialize factory digest if the rpmb is empty.
.write_persistent_value = write_persistent_value,
};
int zircon_vboot_preloaded_img_verify(zbi_header_t *zbi, size_t zbi_size,
size_t capacity, unsigned char *vbmeta,
size_t vbmeta_size)
{
bool unlocked;
if (ops.read_is_device_unlocked(&ops, &unlocked)) {
fprintf(stderr, "Failed to read lock state.\n");
return -1;
}
if (unlocked && vbmeta_size == 0) {
return 0;
}
const char *const requested_partitions[] = {
VBOOT_RAMBOOT_ZBI_PARTITION, "factory", NULL
};
vboot_ramboot_context_t context = { 0 };
context.preloaded_img_addr = (uint8_t *)zbi;
context.preloaded_img_size = zbi_size;
context.preloaded_vbmeta_addr = vbmeta;
context.preloaded_vbmeta_size = vbmeta_size;
ramboot_ops.user_data = (void *)&context;
int ret = ta_vx_lock_if_ephemerally_unlocked();
if (ret) {
return ret;
}
AvbSlotVerifyData *verify_data = NULL;
AvbSlotVerifyResult result =
avb_slot_verify(&ramboot_ops, requested_partitions, "",
AVB_SLOT_VERIFY_FLAGS_NONE,
AVB_HASHTREE_ERROR_MODE_EIO, &verify_data);
ret = process_verify_data(&ramboot_ops, result, verify_data, NULL,
zbi, capacity, false);
if (verify_data) {
avb_slot_verify_data_free(verify_data);
}
return ret;
}
int zircon_vboot_generate_unlock_challenge(
AvbAtxUnlockChallenge *out_unlock_challenge)
{
AvbIOResult ret = avb_atx_generate_unlock_challenge(
&atx_ops, out_unlock_challenge);
if (ret != AVB_IO_RESULT_OK) {
fprintf(stderr, "Failed to generate unlock challenge\n");
return -1;
}
return 0;
}
int zircon_vboot_validate_unlock_credential(
AvbAtxUnlockCredential *unlock_credential, bool *out_is_trusted)
{
AvbIOResult ret = avb_atx_validate_unlock_credential(
&atx_ops, unlock_credential, out_is_trusted);
if (ret != AVB_IO_RESULT_OK) {
fprintf(stderr, "Failed to validate unlock challenge\n");
return -1;
}
return 0;
}
int zircon_vboot_is_unlocked(bool *unlocked)
{
AvbIOResult ret = ops.read_is_device_unlocked(&ops, unlocked);
if (ret != AVB_IO_RESULT_OK) {
fprintf(stderr, "Failed to get unlock status\n");
return -1;
}
return 0;
}
#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;
}
/* recover original bytes order at the end of the function */
AvbPropertyDescriptor *prop_desc = (AvbPropertyDescriptor *)header;
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)) {
fprintf(stderr,
"failed to recover byte order in a property descriptor.\n");
return false;
}
return true;
}