// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2019 The Fuchsia Authors * */ #include #include #include #include #include #ifdef CONFIG_LIBAVB_ATX #include #include #else #include #include #include #if defined(CONFIG_AML_ANTIROLLBACK) || defined(CONFIG_AML_AVB2_ANTIROLLBACK) #include #endif #include #include #include #endif #include #include #include /* By convention, when a rollback index is not used, the value remains zero. */ #define ROLLBACK_INDEX_NOT_USED (0) static uint8_t *preloaded_img_addr; static uint64_t preloaded_img_size; static uint64_t key_versions[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS] = { 0 }; /* 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) { int rc; uint64_t size; int64_t abs_offset; rc = zircon_get_partition_size(partition, &size); if (rc) return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; abs_offset = calc_offset(size, offset); if (abs_offset > size || abs_offset < 0) return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; if ((abs_offset + num_bytes) > size) num_bytes = size - abs_offset; rc = zircon_partition_read(partition, abs_offset, (unsigned char *)buffer, num_bytes); if (rc) return AVB_IO_RESULT_ERROR_IO; *out_num_read = num_bytes; return AVB_IO_RESULT_OK; } #ifdef CONFIG_LIBAVB_ATX static AvbIOResult get_preloaded_partition(AvbOps *ops, const char *partition, size_t num_bytes, uint8_t **out_pointer, size_t *out_num_bytes_preloaded) { *out_pointer = NULL; *out_num_bytes_preloaded = 0; if (!strncmp(partition, ZIRCON_PARTITION_PREFIX, strlen(ZIRCON_PARTITION_PREFIX))) { *out_pointer = preloaded_img_addr; if (num_bytes <= preloaded_img_size) *out_num_bytes_preloaded = num_bytes; else *out_num_bytes_preloaded = preloaded_img_size; } return AVB_IO_RESULT_OK; } #endif static AvbIOResult write_to_partition(AvbOps *ops, const char *partition, int64_t offset, size_t num_bytes, const void *buffer) { int rc; uint64_t size; int64_t abs_offset; rc = zircon_get_partition_size(partition, &size); if (rc) return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; abs_offset = calc_offset(size, offset); if (abs_offset < 0 || (abs_offset + num_bytes > size)) return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; rc = zircon_partition_write(partition, abs_offset, (unsigned char *)buffer, num_bytes); if (rc) return AVB_IO_RESULT_ERROR_IO; return AVB_IO_RESULT_OK; } /* 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) { uint64_t size; int rc = zircon_get_partition_size(partition, &size); if (rc) return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; 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) { int rc = zircon_get_partition_size(partition, out_size_num_bytes); if (rc) return AVB_IO_RESULT_ERROR_IO; return AVB_IO_RESULT_OK; } #ifdef CONFIG_LIBAVB_ATX 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) { if (rollback_index_location < ARRAY_SIZE(key_versions)) { key_versions[rollback_index_location] = key_version; } else { printf("ERROR: rollback index location out of bounds: %lu\n", rollback_index_location); } } 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, }; #else // The flow functions all copied from cmd/amlogic/cmd_avb.c #if CONFIG_AVB2_KPUB_FROM_FIP int compare_avbkey_with_fipkey(const uint8_t *public_key_data, size_t public_key_length); #endif /** * normally, we should read vendor avb public key from a virtual partition * with the name avb_custom_key. Flashing and erasing this partition only * works in the UNLOCKED state. Setting the custom key is done like this: * $ fastboot flash avb_custom_key pkmd.bin * * Erasing the key is done by erasing the virtual partition: * $ fastboot erase avb_custom_key */ static AvbIOResult avb_atx_validate_vbmeta_public_key(AvbOps *ops, const uint8_t *public_key_data, size_t public_key_length, const uint8_t *public_key_metadata, size_t public_key_metadata_length, bool *out_is_trusted) { *out_is_trusted = false; AvbIOResult ret = AVB_IO_RESULT_ERROR_IO; char *keybuf = NULL; char *partition = "misc"; AvbKey_t key; u64 size = 0; #if CONFIG_AVB2_KPUB_FROM_FIP int result = 0; #endif int i = 0; #if CONFIG_AVB2_KPUB_FROM_FIP printf("AVB2 verifying with fip key\n"); result = compare_avbkey_with_fipkey(public_key_data, public_key_length); if (result == -2) { printf("AVB2 verified with fip key failed\n"); *out_is_trusted = false; ret = AVB_IO_RESULT_OK; return ret; } else if (result == -1) { printf("AVB2 cannot find fip key\n"); } else if (result == 0) { printf("AVB2 verified with fip key success\n"); *out_is_trusted = true; ret = AVB_IO_RESULT_OK; return ret; } #endif /* * disable AVB custom key and test key * if device secure boot enabled */ if (!IS_FEAT_BOOT_VERIFY()) { key.size = 0; keybuf = (char *)malloc(AVB_CUSTOM_KEY_LEN_MAX); if (keybuf) { memset(keybuf, 0, AVB_CUSTOM_KEY_LEN_MAX); size = store_logic_cap(partition); if (size != 1) { /* no need workaround for nand. The size is 4K multiple, * and AVB_CUSTOM_KEY_LEN_MAX is 4K. The offset will lay on * 4K boundary. */ if (store_logic_read((const char *)partition, size - AVB_CUSTOM_KEY_LEN_MAX, AVB_CUSTOM_KEY_LEN_MAX, (unsigned char *)keybuf) >= 0) { memcpy(&key, keybuf, sizeof(AvbKey_t)); } } } if (keybuf && (strncmp(keybuf, "AVBK", 4) == 0)) { printf("AVB2 verify with avb_custom_key\n"); if (key.size == public_key_length && !avb_safe_memcmp(public_key_data, keybuf + sizeof(AvbKey_t), public_key_length)) { *out_is_trusted = true; ret = AVB_IO_RESULT_OK; } } else { /** * When the custom key is set * and the device is in the LOCKED state * it will boot images signed with both the built-in key * as well as the custom key */ printf("AVB2 verify with fuchsia default kpub:%d, vbmeta kpub:%ld\n", avb2_kpub_fuchsia_len, public_key_length); if (avb2_kpub_fuchsia_len == public_key_length && !avb_safe_memcmp(public_key_data, avb2_kpub_fuchsia, public_key_length)) { *out_is_trusted = true; ret = AVB_IO_RESULT_OK; } } } else { printf("AVB2 verify with production kpub:%d, vbmeta kpub:%ld\n", avb2_kpub_production_len, public_key_length); if (avb2_kpub_production_len == public_key_length && !avb_safe_memcmp(public_key_data, avb2_kpub_production, public_key_length)) { *out_is_trusted = true; ret = AVB_IO_RESULT_OK; } for (i = 0; i < avb2_kpub_production_len; i++) { if (avb2_kpub_production[i] != 0) break; } if (i == avb2_kpub_production_len) printf("ERROR: DID YOU FORGET TO CHANGE AVB2 KEY FOR SECURE BOOT?"); } if (keybuf) free(keybuf); if (ret != AVB_IO_RESULT_OK) printf("AVB2 key in bootloader does not match with the key in vbmeta\n"); return ret; } static AvbIOResult avb_read_rollback_index(AvbOps *ops, size_t rollback_index_location, uint64_t *out_rollback_index) { AvbIOResult result = AVB_IO_RESULT_OK; #if defined(CONFIG_AML_ANTIROLLBACK) || defined(CONFIG_AML_AVB2_ANTIROLLBACK) uint32_t version = 0; #endif *out_rollback_index = 0; #if defined(CONFIG_AML_ANTIROLLBACK) || defined(CONFIG_AML_AVB2_ANTIROLLBACK) if (is_avb_arb_available()) { if (get_avb_antirollback(rollback_index_location, &version)) { *out_rollback_index = version; } else { printf("failed to read rollback index: %zd\n", rollback_index_location); result = AVB_IO_RESULT_ERROR_NO_SUCH_VALUE; } } #endif return result; } static AvbIOResult avb_write_rollback_index(AvbOps *ops, size_t rollback_index_location, uint64_t rollback_index) { AvbIOResult result = AVB_IO_RESULT_OK; #if defined(CONFIG_AML_ANTIROLLBACK) || defined(CONFIG_AML_AVB2_ANTIROLLBACK) uint32_t version = rollback_index; if (is_avb_arb_available()) { if (!set_avb_antirollback(rollback_index_location, version)) { printf("failed to set rollback index: %zd, version: %u\n", rollback_index_location, version); result = AVB_IO_RESULT_ERROR_NO_SUCH_VALUE; } } #endif return result; } static AvbIOResult read_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked) { AvbIOResult result = AVB_IO_RESULT_OK; LockData_t info = { 0 }; char *lock_s = env_get("lock"); if (!lock_s) return AVB_IO_RESULT_ERROR_IO; #if defined(CONFIG_AML_ANTIROLLBACK) || defined(CONFIG_AML_AVB2_ANTIROLLBACK) uint32_t lock_state = 0; if (is_avb_arb_available()) { if (get_avb_lock_state(&lock_state)) { *out_is_unlocked = !lock_state; if (*out_is_unlocked) lock_s[4] = '0'; else lock_s[4] = '1'; } else { printf("failed to read device lock status from rpmb\n"); result = AVB_IO_RESULT_ERROR_IO; } return result; } #endif memset(&info, 0, sizeof(struct LockData)); info.version_major = (int)(lock_s[0] - '0'); info.version_minor = (int)(lock_s[1] - '0'); info.lock_state = (int)(lock_s[4] - '0'); info.lock_critical_state = (int)(lock_s[5] - '0'); info.lock_bootloader = (int)(lock_s[6] - '0'); if (info.lock_state == 1) *out_is_unlocked = false; else *out_is_unlocked = true; return result; } #endif static AvbOps ops = { #ifdef CONFIG_LIBAVB_ATX .atx_ops = &atx_ops, #endif .read_from_partition = read_from_partition, #ifdef CONFIG_LIBAVB_ATX .get_preloaded_partition = get_preloaded_partition, #else .get_preloaded_partition = NULL, #endif .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, #ifdef CONFIG_LIBAVB_ATX .read_persistent_value = read_persistent_value, .write_persistent_value = write_persistent_value, #else .read_persistent_value = NULL, .write_persistent_value = NULL, #endif }; int zircon_vboot_slot_verify(unsigned char *loadaddr, uint64_t img_size, const char *ab_suffix, bool has_successfully_booted) { preloaded_img_addr = loadaddr; preloaded_img_size = img_size; 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: skipping slot verification.\n"); return 0; } // TODO(http://fxb/44928, improve factory integrity check performance). const char *const requested_partitions[] = { ZIRCON_PARTITION_PREFIX, "factory", NULL }; 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); if (result != AVB_SLOT_VERIFY_RESULT_OK) { fprintf(stderr, "Failed to verify slot: %s, err_code: %s\n", ab_suffix, avb_slot_verify_result_to_string(result)); if (verify_data) avb_slot_verify_data_free(verify_data); 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) { avb_slot_verify_data_free(verify_data); fprintf(stderr, "Failed to write rollback index: %d\n", i); return -1; } } /* Also increase rollback index values for Fuchsia key version locations. */ for (i = 0; i < ARRAY_SIZE(key_versions); i++) { uint64_t rollback_index_value = key_versions[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) { avb_slot_verify_data_free(verify_data); fprintf(stderr, "Failed to write rollback index: %d\n", i); return -1; } } } avb_slot_verify_data_free(verify_data); printf("slot: %s successfully verified.\n", ab_suffix); return 0; } #ifdef CONFIG_LIBAVB_ATX 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; } #endif