| /* |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * * |
| This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * * |
| This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * * |
| You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * * |
| Description: |
| */ |
| |
| #include <common.h> |
| #include <command.h> |
| #include <environment.h> |
| #include <malloc.h> |
| #include <asm/byteorder.h> |
| #include <config.h> |
| #include <asm/arch/io.h> |
| #include <partition_table.h> |
| #include <libavb.h> |
| #include <emmc_partitions.h> |
| #include <amlogic/storage_if.h> |
| #include <asm/arch/bl31_apis.h> |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| #include <anti-rollback.h> |
| #endif |
| #include <amlogic/aml_efuse.h> |
| #include <part.h> |
| #include <ext4fs.h> |
| #include <fat.h> |
| #include <fs.h> |
| |
| |
| #define AVB_USE_TESTKEY |
| #define MAX_DTB_SIZE (AML_DTB_IMG_MAX_SZ + 512) |
| #define DTB_PARTITION_SIZE 258048 |
| #define AVB_NUM_SLOT (4) |
| |
| #define CONFIG_AVB2_KPUB_EMBEDDED |
| #define CONFIG_AVB2_KPUB_DEFAULT |
| |
| #ifdef CONFIG_AVB2_KPUB_VENDOR |
| /** |
| * Use of vendor public key automatically disable default public key |
| */ |
| #undef CONFIG_AVB2_KPUB_DEFAULT |
| #ifdef CONFIG_AVB2_KPUB_VENDOR_MULTIPLE |
| extern const uint8_t *const avb2_kpub_vendor[]; |
| extern const size_t avb2_kpub_vendor_len[]; |
| extern const size_t avb2_kpub_vendor_num; |
| |
| extern const uint8_t *const avb2_kpub_vendor_external[]; |
| extern const size_t avb2_kpub_vendor_external_len[]; |
| extern const size_t avb2_kpub_vendor_external_num; |
| #else |
| extern const uint8_t avb2_kpub_vendor[]; |
| extern const size_t avb2_kpub_vendor_len; |
| #endif /* CONFIG_AVB2_KPUB_VENDOR_MULTIPLE */ |
| #endif /* CONFIG_AVB_KPUB_VENDOR */ |
| |
| #if defined(CONFIG_AVB2_KPUB_DEFAULT) || defined(CONFIG_AVB2_KPUB_DEFAULT_VENDOR) |
| extern const uint8_t avb2_kpub_default[]; |
| extern const size_t avb2_kpub_default_len; |
| #endif /* CONFIG_AVB_KPUB_DEFAULT || CONFIG_AVB2_KPUB_DEFAULT_VENDOR */ |
| |
| AvbOps avb_ops_; |
| |
| 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 = 0; |
| uint64_t part_bytes = 0; |
| |
| if (ops->get_size_of_partition(ops, partition, &part_bytes) != AVB_IO_RESULT_OK) { |
| return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
| } |
| |
| if (offset < 0) { |
| offset = part_bytes + offset; |
| } |
| |
| if (offset < 0 || offset > part_bytes) { |
| return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; |
| } |
| |
| if (num_bytes > part_bytes - offset) { |
| num_bytes = part_bytes - offset; |
| } |
| |
| *out_num_read = 0; |
| |
| bool load_from_mem = false; |
| unsigned long load_mem_addr = NULL; |
| if (!memcmp(partition, "dt", strlen("dt"))) { |
| const char *dtb_mem_addr = getenv("dtb_mem_addr"); |
| if (dtb_mem_addr) { |
| load_from_mem = true; |
| load_mem_addr = simple_strtoul(dtb_mem_addr, NULL, 16); |
| } |
| } else if (!memcmp(partition, "vbmeta", strlen("vbmeta"))) { |
| const char *vbmeta_mem_addr = getenv("vbmeta_mem_addr"); |
| if (getenv_yesno("use_mem_vbmeta") == 1 && vbmeta_mem_addr) { |
| load_from_mem = true; |
| load_mem_addr = simple_strtoul(vbmeta_mem_addr, NULL, 16); |
| } |
| } else { |
| const char *loadpart = getenv("loadpart"); |
| if (loadpart && !memcmp(partition, loadpart, strlen(loadpart))) { |
| const char *loadaddr = getenv("loadaddr"); |
| if (loadaddr) { |
| load_from_mem = true; |
| load_mem_addr = simple_strtoul(loadaddr, NULL, 16); |
| } |
| } |
| } |
| |
| if (load_from_mem) { |
| printf("load %s from mem addr: 0x%lx size: 0x%lx\n", |
| partition, load_mem_addr, num_bytes); |
| memcpy(buffer, (void *)load_mem_addr, num_bytes); |
| *out_num_read = num_bytes; |
| return AVB_IO_RESULT_OK; |
| } |
| |
| if (!memcmp(partition, "dt", strlen("dt"))) { |
| char *dtb_buf = malloc(MAX_DTB_SIZE); |
| |
| if (!dtb_buf) |
| return AVB_IO_RESULT_ERROR_OOM; |
| |
| rc = store_dtb_rw(dtb_buf, MAX_DTB_SIZE, 2); |
| if (rc) { |
| printf("Failed to read dtb\n"); |
| free(dtb_buf); |
| return AVB_IO_RESULT_ERROR_IO; |
| } else { |
| *out_num_read = num_bytes > MAX_DTB_SIZE ? MAX_DTB_SIZE : num_bytes; |
| memcpy(buffer, dtb_buf, *out_num_read); |
| free(dtb_buf); |
| return AVB_IO_RESULT_OK; |
| } |
| } else { |
| printf("load %s partition\n", partition); |
| rc = store_read_ops((unsigned char *)partition, buffer, offset, num_bytes); |
| if (rc) { |
| printf("Failed to read %zdB from part[%s] at offset %lld\n", num_bytes, partition, offset); |
| return AVB_IO_RESULT_ERROR_IO; |
| } |
| *out_num_read = num_bytes; |
| } |
| |
| 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) |
| { |
| int rc = 0; |
| uint64_t part_bytes = 0; |
| |
| if (ops->get_size_of_partition(ops, partition, &part_bytes) != AVB_IO_RESULT_OK) { |
| return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
| } |
| if (part_bytes < offset) |
| return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; |
| |
| if (!memcmp(partition, "dt", strlen("dt"))) { |
| if (offset) |
| return AVB_IO_RESULT_ERROR_IO; |
| rc = store_dtb_rw((void *)buffer, num_bytes, 1); |
| if (rc) { |
| printf("Failed to write dtb\n"); |
| return AVB_IO_RESULT_ERROR_IO; |
| } else { |
| return AVB_IO_RESULT_OK; |
| } |
| } else { |
| rc = store_write_ops((unsigned char *)partition, (unsigned char *)buffer, offset, num_bytes); |
| if (rc) { |
| printf("Failed to write %zdB from part[%s] at offset %lld\n", num_bytes, partition, offset); |
| return AVB_IO_RESULT_ERROR_IO; |
| } |
| } |
| |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static AvbIOResult get_unique_guid_for_partition(AvbOps* ops, const char* partition, |
| char* guid_buf, size_t guid_buf_size) |
| { |
| char *s1; |
| int ret; |
| char part_name[128]; |
| memset(guid_buf, 0, guid_buf_size); |
| run_command("get_valid_slot;", 0); |
| s1 = getenv("active_slot"); |
| printf("active_slot is %s\n", s1); |
| if (!memcmp(partition, "system", strlen("system"))) { |
| if (strcmp(s1, "_a") == 0) { |
| ret = get_partition_num_by_name("system_a"); |
| sprintf(part_name, "/dev/mmcblk0p%d", ret+1); |
| strncpy(guid_buf, part_name, guid_buf_size); |
| } else if (strcmp(s1, "_b") == 0) { |
| ret = get_partition_num_by_name("system_b"); |
| sprintf(part_name, "/dev/mmcblk0p%d", ret+1); |
| strncpy(guid_buf, part_name, guid_buf_size); |
| } else { |
| ret = get_partition_num_by_name("system"); |
| sprintf(part_name, "/dev/mmcblk0p%d", ret+1); |
| strncpy(guid_buf, part_name, guid_buf_size); |
| } |
| } else if (!memcmp(partition, "vbmeta", strlen("vbmeta"))) |
| strncpy(guid_buf, "/dev/block/vbmeta", guid_buf_size); |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static AvbIOResult get_size_of_partition(AvbOps* ops, const char* partition, |
| uint64_t* out_size_num_bytes) |
| { |
| int rc = 0; |
| |
| if (!memcmp(partition, "dt", strlen("dt"))) { |
| *out_size_num_bytes = DTB_PARTITION_SIZE; |
| } else { |
| rc = store_get_partititon_size((unsigned char *)partition, out_size_num_bytes); |
| if (rc) { |
| printf("Failed to get partition[%s] size\n", partition); |
| return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; |
| } |
| *out_size_num_bytes *= 512; |
| } |
| |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static inline bool _validata_key(const uint8_t* key1, size_t key1_len, |
| const uint8_t* key2, size_t key2_len) |
| { |
| return key1_len == key2_len && avb_safe_memcmp(key1, key2, key1_len) == 0; |
| } |
| |
| static AvbIOResult 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; |
| |
| #ifdef CONFIG_AVB2_KPUB_EMBEDDED |
| /** |
| * CONFIG_AVB2_KPUB_DEFAULT and CONFIG_AVB2_KPUB_VENDOR should be |
| * exclusive ideally, however the world is not ideal. |
| * |
| * Instead of forbidding it, just print out a warning to let the user |
| * know this is not something they should be doing unless they really |
| * know what they are doing. |
| */ |
| #if defined(CONFIG_AVB2_KPUB_VENDOR) && defined(CONFIG_AVB2_KPUB_DEFAULT_VENDOR) |
| #pragma message("Both vendor and default AVB2 public keys are enabled") |
| #endif /* CONFIG_AVB2_KPUB_VENDOR && CONFIG_AVB2_KPUB_DEFAULT_VENDOR */ |
| |
| #if defined(CONFIG_AVB2_KPUB_VENDOR) |
| printf("AVB2 verify with vendor kpub\n"); |
| |
| #ifdef CONFIG_AVB2_KPUB_VENDOR_MULTIPLE |
| if (getenv_yesno("use_external_avb_key") == 1) { |
| printf("AVB2 verify with external keys\n"); |
| size_t i; |
| for (i = 0; i < avb2_kpub_vendor_external_num; i++) { |
| if (_validata_key(avb2_kpub_vendor_external[i], |
| avb2_kpub_vendor_external_len[i], |
| public_key_data, public_key_length)) { |
| *out_is_trusted = true; |
| return AVB_IO_RESULT_OK; |
| } |
| } |
| } else { |
| size_t i; |
| for (i = 0; i < avb2_kpub_vendor_num; i++) { |
| if (_validata_key(avb2_kpub_vendor[i], |
| avb2_kpub_vendor_len[i], |
| public_key_data, public_key_length)) { |
| *out_is_trusted = true; |
| return AVB_IO_RESULT_OK; |
| } |
| } |
| } |
| #else |
| if (_validata_key(avb2_kpub_vendor, |
| avb2_kpub_vendor_len, |
| public_key_data, public_key_length)) { |
| *out_is_trusted = true; |
| return AVB_IO_RESULT_OK; |
| } |
| #endif /* CONFIG_AVB2_KPUB_VENDOR_MULTIPLE */ |
| |
| unsigned int isSecure = IS_FEAT_BOOT_VERIFY(); |
| printf("isSecure: %d\n", isSecure); |
| if (isSecure == 0) { |
| |
| /** |
| * Allow re-verify with default AVB2 public key if really want to do. |
| * |
| * Use of this is *NOT* typical and you should really know what you are |
| * doing if want to enable this. |
| */ |
| #ifdef CONFIG_AVB2_KPUB_DEFAULT_VENDOR |
| printf("AVB2 re-verify with default kpub\n"); |
| if (_validata_key(avb2_kpub_default, avb2_kpub_default_len, |
| public_key_data, public_key_length)) { |
| *out_is_trusted = true; |
| return AVB_IO_RESULT_OK; |
| } |
| #endif /* CONFIG_AVB2_KPUB_DEFAULT_VENDOR */ |
| } |
| #elif defined(CONFIG_AVB2_KPUB_DEFAULT) |
| printf("AVB2 verify with default kpub\n"); |
| if (_validata_key(avb2_kpub_default, avb2_kpub_default_len, |
| public_key_data, public_key_length)) { |
| *out_is_trusted = true; |
| return AVB_IO_RESULT_OK; |
| } |
| #else |
| #error "No AVB2 public key defined" |
| #endif /* CONFIG_AVB2_KPUB_VENDOR */ |
| |
| #else /* CONFIG_AVB2_KPUB_EMBEDDED */ |
| unsigned long bl31_addr = get_sharemem_info(GET_SHARE_MEM_INPUT_BASE); |
| memcpy((void *)bl31_addr, public_key_data, public_key_length); |
| flush_cache(bl31_addr, public_key_length); |
| *out_is_trusted = aml_sec_boot_check(AML_D_P_AVB_PUBKEY_VERIFY, |
| bl31_addr, public_key_length, 0); |
| #endif /* CONFIG_AVB2_KPUB_EMBEDDED */ |
| |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static AvbIOResult read_rollback_index(AvbOps* ops, size_t rollback_index_location, |
| uint64_t* out_rollback_index) |
| { |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| uint32_t version; |
| if (get_avb_antirollback(rollback_index_location, &version)) { |
| *out_rollback_index = version; |
| } else { |
| printf("failed to read rollback index: %zd\n", rollback_index_location); |
| return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE; |
| } |
| #else |
| *out_rollback_index = 0; |
| #endif |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static AvbIOResult write_rollback_index(AvbOps* ops, size_t rollback_index_location, |
| uint64_t rollback_index) |
| { |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| uint32_t version = rollback_index; |
| if (set_avb_antirollback(rollback_index_location, version)) { |
| return AVB_IO_RESULT_OK; |
| } else { |
| printf("failed to set rollback index: %zd, version: %u\n", rollback_index_location, version); |
| return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE; |
| } |
| #endif |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static AvbIOResult read_is_device_unlocked(AvbOps* ops, bool* out_is_unlocked) |
| { |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| uint32_t lock_state; |
| LockData_t info; |
| |
| if (get_avb_lock_state(&lock_state)) { |
| *out_is_unlocked = !lock_state; |
| get_lock_data(&info); |
| if (*out_is_unlocked) { |
| info.lock_state = 0; |
| } else { |
| info.lock_state = 1; |
| } |
| save_lock_data(&info); |
| return AVB_IO_RESULT_OK; |
| } else { |
| printf("failed to read device lock status from rpmb\n"); |
| return AVB_IO_RESULT_ERROR_IO; |
| } |
| #else |
| LockData_t info; |
| |
| get_lock_data(&info); |
| |
| if (info.lock_state == 1) |
| *out_is_unlocked = false; |
| else |
| *out_is_unlocked = true; |
| return AVB_IO_RESULT_OK; |
| #endif |
| } |
| |
| static int avb_init(void) |
| { |
| |
| memset(&avb_ops_, 0, sizeof(AvbOps)); |
| avb_ops_.read_from_partition = read_from_partition; |
| avb_ops_.get_preloaded_partition = NULL; |
| avb_ops_.write_to_partition = write_to_partition; |
| avb_ops_.validate_vbmeta_public_key = validate_vbmeta_public_key; |
| avb_ops_.read_rollback_index = read_rollback_index; |
| avb_ops_.write_rollback_index = write_rollback_index; |
| avb_ops_.read_is_device_unlocked = read_is_device_unlocked; |
| avb_ops_.get_unique_guid_for_partition = get_unique_guid_for_partition; |
| avb_ops_.get_size_of_partition = get_size_of_partition; |
| avb_ops_.read_persistent_value = NULL; |
| avb_ops_.write_persistent_value = NULL; |
| |
| //avb_ops_.user_data = NULL; |
| |
| return 0; |
| } |
| |
| int is_device_unlocked(void) |
| { |
| AvbIOResult ret; |
| bool out_is_unlocked; |
| ret = read_is_device_unlocked(&avb_ops_, &out_is_unlocked); |
| if (ret == AVB_IO_RESULT_OK) |
| return out_is_unlocked; |
| else |
| return 0; |
| } |
| |
| int avb_verify(AvbSlotVerifyData** out_data) |
| { |
| /* The last slot must be NULL */ |
| const char *requested_partitions[AVB_NUM_SLOT + 1] = {}; |
| AvbSlotVerifyResult result = AVB_SLOT_VERIFY_RESULT_OK; |
| const char *s1; |
| const char *ab_suffix; |
| |
| run_command("get_valid_slot;", 0); |
| s1 = getenv("active_slot"); |
| printf("active_slot is %s\n", s1); |
| if (strcmp(s1, "normal") == 0) { |
| ab_suffix = ""; |
| } else { |
| ab_suffix = getenv("active_slot"); |
| } |
| printf("ab_suffix is %s\n", ab_suffix); |
| |
| AvbSlotVerifyFlags flags = AVB_SLOT_VERIFY_FLAGS_NONE; |
| const char *upgradestep = NULL; |
| const char *loadpart = NULL; |
| |
| avb_init(); |
| |
| upgradestep = getenv("upgrade_step"); |
| |
| if (is_device_unlocked() || !strcmp(upgradestep, "3")) |
| flags |= AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR; |
| |
| if (!strcmp(ab_suffix, "")) { |
| loadpart = getenv("loadpart"); |
| if (loadpart && !strcmp(loadpart, "recovery")) { |
| requested_partitions[0] = "dt"; |
| requested_partitions[1] = "recovery"; |
| requested_partitions[2] = NULL; |
| } else if (loadpart && !strcmp(loadpart, "boot")) { |
| requested_partitions[0] = "dt"; |
| requested_partitions[1] = "boot"; |
| requested_partitions[2] = NULL; |
| } else { |
| requested_partitions[0] = "dt"; |
| requested_partitions[1] = "boot"; |
| requested_partitions[2] = "recovery"; |
| requested_partitions[3] = NULL; |
| } |
| } else { |
| requested_partitions[0] = "dt"; |
| requested_partitions[1] = "boot"; |
| requested_partitions[2] = NULL; |
| } |
| |
| result = avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix, |
| flags, |
| AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data); |
| |
| if (!strcmp(upgradestep, "3")) |
| result = AVB_SLOT_VERIFY_RESULT_OK; |
| |
| return result; |
| } |
| |
| static int do_avb_verify(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| AvbSlotVerifyResult result = AVB_SLOT_VERIFY_RESULT_OK; |
| AvbSlotVerifyData* out_data = NULL; |
| uint32_t i = 0; |
| |
| result = avb_verify(&out_data); |
| |
| printf("result: %d\n", result); |
| if (result == AVB_SLOT_VERIFY_RESULT_OK) { |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| uint32_t version; |
| uint32_t lock_state; |
| #endif |
| |
| printf("ab_suffix: %s\n", out_data->ab_suffix); |
| printf("vbmeta: name: %s, size: %zd, result: %d\n", |
| out_data->vbmeta_images->partition_name, |
| out_data->vbmeta_images->vbmeta_size, |
| out_data->vbmeta_images->verify_result); |
| printf("num of vbmeta: %zd\n", out_data->num_vbmeta_images); |
| printf("loaded name: %s, size: %zd, preload: %d\n", out_data->loaded_partitions->partition_name, |
| out_data->loaded_partitions->data_size, out_data->loaded_partitions->preloaded); |
| printf("num of loaded: %zd\n", out_data->num_loaded_partitions); |
| printf("cmdline: %s\n", out_data->cmdline); |
| for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) |
| printf("rollback(%d) = %llu\n", i, out_data->rollback_indexes[i]); |
| |
| #ifdef CONFIG_AML_ANTIROLLBACK |
| for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) |
| if (get_avb_antirollback(i, &version)) |
| printf("rpmb rollback(%d) = %u\n", i, version); |
| if (get_avb_lock_state(&lock_state)) |
| printf("rpmb lock state: %u\n", lock_state); |
| #endif |
| } |
| |
| if (out_data != NULL) { |
| avb_slot_verify_data_free(out_data); |
| } |
| |
| return result; |
| } |
| |
| static cmd_tbl_t cmd_avb_sub[] = { |
| U_BOOT_CMD_MKENT(verify, 4, 0, do_avb_verify, "", ""), |
| }; |
| |
| static int do_avb_ops(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| cmd_tbl_t *c; |
| |
| /* Strip off leading 'bmp' command argument */ |
| argc--; |
| argv++; |
| |
| c = find_cmd_tbl(argv[0], &cmd_avb_sub[0], ARRAY_SIZE(cmd_avb_sub)); |
| |
| if (c) { |
| return c->cmd(cmdtp, flag, argc, argv); |
| } else { |
| cmd_usage(cmdtp); |
| return 1; |
| } |
| } |
| |
| |
| U_BOOT_CMD( |
| avb, 2, 0, do_avb_ops, |
| "avb", |
| "\nThis command will trigger related avb operations\n" |
| ); |