| /* | |
| * 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; | |
| } |