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