diff options
author | Luo Ji <ji.luo@nxp.com> | 2018-05-15 17:53:04 +0800 |
---|---|---|
committer | Ji Luo <ji.luo@nxp.com> | 2018-08-20 21:25:44 +0800 |
commit | 449f9048e6f7870385b4fbecb18c846478a8305f (patch) | |
tree | cf50612f7882e11514875e042f3d860585e51033 /lib | |
parent | fed4a26ead8b38a4e03ca192876016246fe3e601 (diff) |
[iot] Update libavb in u-boot
This commit did:
1. Sync AVB lib with external/avb, head of commit is:
commit 6d5326a945c2d17d5d0e7718d5cb97663c3b33a2
Author: Neal Ostrem <nealo@google.com>
Date: Tue Apr 24 13:09:45 2018 -0700
Merge fix/changes required after merge from AOSP ToT.
Change library name to one used by AT.
Test: Built successfully and unit tests pass.
Change-Id: I5e5fc9a6010d96cfecfc6faf0858ba930cba65a0
2. Change product id in ATX to be full zeros to sync with
external/avb.
3. Fix build errors and implement ops fsl_set_key_version.
4. Move most nxp modified code to lib/avb/fsl/.
Test: build and boot successfully for imx7d_pico and imx8m_phanbell.
Change-Id: I199a035fe8267b10955299a4b745458d40a2e754
Signed-off-by: Luo Ji <ji.luo@nxp.com>
Diffstat (limited to 'lib')
32 files changed, 1787 insertions, 800 deletions
diff --git a/lib/avb/Makefile b/lib/avb/Makefile index ea0fd22d38..bb96ad14da 100644 --- a/lib/avb/Makefile +++ b/lib/avb/Makefile @@ -1,7 +1,7 @@ -subdir-ccflags-y += -D_FILE_OFFSET_BITS=64 \ +subdir-ccflags-y += -I./lib/avb \ + -D_FILE_OFFSET_BITS=64 \ -D_POSIX_C_SOURCE=199309L \ -Wa,--noexecstack \ - -Werror \ -Wall \ -Wextra \ -Wformat=2 \ diff --git a/lib/avb/fsl/Makefile b/lib/avb/fsl/Makefile index 6218fab0f5..990d62fe26 100644 --- a/lib/avb/fsl/Makefile +++ b/lib/avb/fsl/Makefile @@ -1,5 +1,7 @@ -ccflags-$(CONFIG_AVB_DEBUG) += -DAVB_DEBUG +ccflags-y += -Werror obj-y += fsl_avb.o obj-y += fsl_avbkey.o obj-y += fsl_bootctl.o +obj-y += fsl_avb_ab_flow.o +obj-y += fsl_avb_sysdeps_uboot.o obj-y += utils.o diff --git a/lib/avb/fsl/fsl_atx_attributes.h b/lib/avb/fsl/fsl_atx_attributes.h index 8f4fc48469..14592e02d3 100644 --- a/lib/avb/fsl/fsl_atx_attributes.h +++ b/lib/avb/fsl/fsl_atx_attributes.h @@ -11,8 +11,8 @@ /* This product_id is generated from * extern/avb/test/data/atx_product_id.bin */ unsigned char fsl_atx_product_id[] = { - 0x3f,0x38,0x9c,0xcb,0xbe,0x56,0xcc,0x3d, - 0x0b,0xd0,0xbb,0x35,0x01,0x85,0xa7,0xd2 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; /* This product_root_public_key is generated form * extern/avb/test/data/testkey_atx_prk.pem */ diff --git a/lib/avb/fsl/fsl_avb_ab_flow.c b/lib/avb/fsl/fsl_avb_ab_flow.c new file mode 100644 index 0000000000..d82525670e --- /dev/null +++ b/lib/avb/fsl/fsl_avb_ab_flow.c @@ -0,0 +1,398 @@ +/* + * Copyright 2018 NXP + */ + +#include <common.h> +#include <fsl_avb.h> + +static const char* slot_suffixes[2] = {"_a", "_b"}; + +/* This is a copy of slot_set_unbootable() form + * lib/avb/libavb_ab/avb_ab_flow.c. + */ +static void fsl_slot_set_unbootable(AvbABSlotData* slot) { + slot->priority = 0; + slot->tries_remaining = 0; + slot->successful_boot = 0; +} + +/* Ensure all unbootable and/or illegal states are marked as the + * canonical 'unbootable' state, e.g. priority=0, tries_remaining=0, + * and successful_boot=0. This is a copy of fsl_slot_normalize from + * lib/avb/libavb_ab/avb_ab_flow.c. + */ +static void fsl_slot_normalize(AvbABSlotData* slot) { + if (slot->priority > 0) { + if ((slot->tries_remaining == 0) && (!slot->successful_boot)) { + /* We've exhausted all tries -> unbootable. */ + fsl_slot_set_unbootable(slot); + } + if ((slot->tries_remaining > 0) && (slot->successful_boot)) { + /* Illegal state - avb_ab_mark_slot_successful() will clear + * tries_remaining when setting successful_boot. + */ + fsl_slot_set_unbootable(slot); + } + } else { + fsl_slot_set_unbootable(slot); + } +} + +/* Writes A/B metadata to disk only if it has changed - returns + * AVB_IO_RESULT_OK on success, error code otherwise. This is a + * copy of save_metadata_if_changed form lib/avb/libavb_ab/avb_ab_flow.c. + */ +static AvbIOResult fsl_save_metadata_if_changed(AvbABOps* ab_ops, + AvbABData* ab_data, + AvbABData* ab_data_orig) { + if (avb_safe_memcmp(ab_data, ab_data_orig, sizeof(AvbABData)) != 0) { + avb_debug("Writing A/B metadata to disk.\n"); + return ab_ops->write_ab_metadata(ab_ops, ab_data); + } + return AVB_IO_RESULT_OK; +} + +/* This is a copy of slot_is_bootable() from + * lib/avb/libavb_ab/avb_ab_flow.c. + */ +static bool fsl_slot_is_bootable(AvbABSlotData* slot) { + return (slot->priority > 0) && + (slot->successful_boot || (slot->tries_remaining > 0)); +} + +/* Helper function to load metadata - returns AVB_IO_RESULT_OK on + * success, error code otherwise. This is a copy of load_metadata() + * from /lib/avb/libavb_ab/avb_ab_flow.c. + */ +static AvbIOResult fsl_load_metadata(AvbABOps* ab_ops, + AvbABData* ab_data, + AvbABData* ab_data_orig) { + AvbIOResult io_ret; + + io_ret = ab_ops->read_ab_metadata(ab_ops, ab_data); + if (io_ret != AVB_IO_RESULT_OK) { + avb_error("I/O error while loading A/B metadata.\n"); + return io_ret; + } + *ab_data_orig = *ab_data; + + /* Ensure data is normalized, e.g. illegal states will be marked as + * unbootable and all unbootable states are represented with + * (priority=0, tries_remaining=0, successful_boot=0). + */ + fsl_slot_normalize(&ab_data->slots[0]); + fsl_slot_normalize(&ab_data->slots[1]); + return AVB_IO_RESULT_OK; +} + +/* For legacy i.mx6/7, we won't enable A/B due to the limitation of + * storage capacity, but we still want to verify boot/recovery with + * AVB. */ +AvbABFlowResult avb_single_flow(AvbABOps* ab_ops, + const char* const* requested_partitions, + AvbSlotVerifyFlags flags, + AvbHashtreeErrorMode hashtree_error_mode, + AvbSlotVerifyData** out_data) { + AvbOps* ops = ab_ops->ops; + AvbSlotVerifyData* slot_data = NULL; + AvbSlotVerifyData* data = NULL; + AvbABFlowResult ret; + bool saw_and_allowed_verification_error = false; + + /* Validate boot/recovery. */ + AvbSlotVerifyResult verify_result; + + verify_result = avb_slot_verify(ops, + requested_partitions, + "", + flags, + hashtree_error_mode, + &slot_data); + switch (verify_result) { + case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + goto out; + + case AVB_SLOT_VERIFY_RESULT_ERROR_IO: + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + goto out; + + case AVB_SLOT_VERIFY_RESULT_OK: + break; + + 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. + */ + ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; + goto out; + + /* explicit fallthrough. */ + 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 (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR) { + /* Do nothing since we allow this. */ + avb_debugv("Allowing slot ", + slot_suffixes[n], + " which verified " + "with result ", + avb_slot_verify_result_to_string(verify_result), + " because " + "AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " + "is set.\n", + NULL); + saw_and_allowed_verification_error = true; + } else { + ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; + goto out; + } + break; + + case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT: + ret = AVB_AB_FLOW_RESULT_ERROR_INVALID_ARGUMENT; + goto out; + /* Do not add a 'default:' case here because of -Wswitch. */ + } + + avb_assert(slot_data != NULL); + data = slot_data; + slot_data = NULL; + if (saw_and_allowed_verification_error) { + avb_assert(flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR); + ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR; + } else { + ret = AVB_AB_FLOW_RESULT_OK; + } + +out: + if (slot_data != NULL) { + avb_slot_verify_data_free(slot_data); + } + + if (out_data != NULL) { + *out_data = data; + } else { + if (data != NULL) { + avb_slot_verify_data_free(data); + } + } + + return ret; +} + +AvbABFlowResult avb_ab_flow_fast(AvbABOps* ab_ops, + const char* const* requested_partitions, + AvbSlotVerifyFlags flags, + AvbHashtreeErrorMode hashtree_error_mode, + AvbSlotVerifyData** out_data) { + AvbOps* ops = ab_ops->ops; + AvbSlotVerifyData* slot_data[2] = {NULL, NULL}; + AvbSlotVerifyData* data = NULL; + AvbABFlowResult ret; + AvbABData ab_data, ab_data_orig; + size_t slot_index_to_boot, n; + AvbIOResult io_ret; + bool saw_and_allowed_verification_error = false; + size_t target_slot; + AvbSlotVerifyResult verify_result; + bool set_slot_unbootable = false; + + io_ret = fsl_load_metadata(ab_ops, &ab_data, &ab_data_orig); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + goto out; + } else if (io_ret != AVB_IO_RESULT_OK) { + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + goto out; + } + + slot_index_to_boot = 2; // Means not 0 or 1 + target_slot = + (ab_data.slots[1].priority > ab_data.slots[0].priority) ? 1 : 0; + + for (n = 0; n < 2; n++) { + if (!fsl_slot_is_bootable(&ab_data.slots[target_slot])) { + target_slot = (target_slot == 1 ? 0 : 1); + continue; + } + verify_result = avb_slot_verify(ops, + requested_partitions, + slot_suffixes[target_slot], + flags, + hashtree_error_mode, + &slot_data[target_slot]); + switch (verify_result) { + case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + goto out; + + case AVB_SLOT_VERIFY_RESULT_ERROR_IO: + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + goto out; + + case AVB_SLOT_VERIFY_RESULT_OK: + slot_index_to_boot = target_slot; + n = 2; + break; + + 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; + break; + + /* explicit fallthrough. */ + 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 (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR) { + /* Do nothing since we allow this. */ + avb_debugv("Allowing slot ", + slot_suffixes[target_slot], + " which verified " + "with result ", + avb_slot_verify_result_to_string(verify_result), + " because " + "AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " + "is set.\n", + NULL); + saw_and_allowed_verification_error = + true; + slot_index_to_boot = target_slot; + n = 2; + } else { + set_slot_unbootable = true; + } + break; + + case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT: + ret = AVB_AB_FLOW_RESULT_ERROR_INVALID_ARGUMENT; + goto out; + /* Do not add a 'default:' case here because + * of -Wswitch. + */ + } + + if (set_slot_unbootable) { + avb_errorv("Error verifying slot ", + slot_suffixes[target_slot], + " with result ", + avb_slot_verify_result_to_string(verify_result), + " - setting unbootable.\n", + NULL); + fsl_slot_set_unbootable(&ab_data.slots[target_slot]); + set_slot_unbootable = false; + } + /* switch to another slot */ + target_slot = (target_slot == 1 ? 0 : 1); + } + + if (slot_index_to_boot == 2) { + /* No bootable slots! */ + avb_error("No bootable slots found.\n"); + ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; + goto out; + } + + /* Update stored rollback index such that the stored rollback index + * is the largest value supporting all currently bootable slots. Do + * this for every rollback index location. + */ + for (n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) { + uint64_t rollback_index_value = 0; + + if ((slot_data[0] != NULL) && (slot_data[1] != NULL)) { + uint64_t a_rollback_index = + slot_data[0]->rollback_indexes[n]; + uint64_t b_rollback_index = + slot_data[1]->rollback_indexes[n]; + rollback_index_value = + (a_rollback_index < b_rollback_index ? + a_rollback_index : b_rollback_index); + } else if (slot_data[0] != NULL) { + rollback_index_value = + slot_data[0]->rollback_indexes[n]; + } else if (slot_data[1] != NULL) { + rollback_index_value = + slot_data[1]->rollback_indexes[n]; + } + + if (rollback_index_value != 0) { + uint64_t current_rollback_index_value; + io_ret = ops->read_rollback_index( + ops, n, ¤t_rollback_index_value); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + goto out; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_error("Error getting rollback index for slot.\n"); + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + goto out; + } + if (current_rollback_index_value != rollback_index_value) { + io_ret = ops->write_rollback_index( + ops, n, rollback_index_value); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + goto out; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_error("Error setting stored rollback index.\n"); + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + goto out; + } + } + } + } + + /* Finally, select this slot. */ + avb_assert(slot_data[slot_index_to_boot] != NULL); + data = slot_data[slot_index_to_boot]; + slot_data[slot_index_to_boot] = NULL; + if (saw_and_allowed_verification_error) { + avb_assert( + flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR); + ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR; + } else { + ret = AVB_AB_FLOW_RESULT_OK; + } + + /* ... and decrement tries remaining, if applicable. */ + if (!ab_data.slots[slot_index_to_boot].successful_boot && + (ab_data.slots[slot_index_to_boot].tries_remaining > 0)) { + ab_data.slots[slot_index_to_boot].tries_remaining -= 1; + } + +out: + io_ret = fsl_save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); + if (io_ret != AVB_IO_RESULT_OK) { + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + ret = AVB_AB_FLOW_RESULT_ERROR_OOM; + } else { + ret = AVB_AB_FLOW_RESULT_ERROR_IO; + } + if (data != NULL) { + avb_slot_verify_data_free(data); + data = NULL; + } + } + + for (n = 0; n < 2; n++) { + if (slot_data[n] != NULL) { + avb_slot_verify_data_free(slot_data[n]); + } + } + + if (out_data != NULL) { + *out_data = data; + } else { + if (data != NULL) { + avb_slot_verify_data_free(data); + } + } + + return ret; +} diff --git a/lib/avb/libavb/avb_sysdeps_uboot.c b/lib/avb/fsl/fsl_avb_sysdeps_uboot.c index be5c1d2640..5fcb69f75e 100644 --- a/lib/avb/libavb/avb_sysdeps_uboot.c +++ b/lib/avb/fsl/fsl_avb_sysdeps_uboot.c @@ -26,7 +26,7 @@ #include <stdlib.h> #include <linux/string.h> -#include "avb_sysdeps.h" +#include "../libavb/libavb.h" int avb_memcmp(const void* src1, const void* src2, size_t n) { return memcmp(src1, src2, n); @@ -62,3 +62,9 @@ void avb_printv(const char* message, ...) { void* avb_malloc_(size_t size) { return malloc(size); } void avb_free(void* ptr) { free(ptr); } + +uint32_t avb_div_by_10(uint64_t* dividend) { + uint32_t rem = (uint32_t)(*dividend % 10); + *dividend /= 10; + return rem; +} diff --git a/lib/avb/fsl/fsl_avbkey.c b/lib/avb/fsl/fsl_avbkey.c index f365bf8222..81e6549c5c 100644 --- a/lib/avb/fsl/fsl_avbkey.c +++ b/lib/avb/fsl/fsl_avbkey.c @@ -1108,7 +1108,8 @@ AvbIOResult fsl_write_rollback_index_rpmb(AvbOps* ops, size_t rollback_index_slo *plain_idx = rollback_index; /* write rollback_index keyblob */ - if (rpmb_write(mmc_dev, (uint8_t *)plain_idx, rbk->len, rbk->offset) != 0) { + if (rpmb_write(mmc_dev, (uint8_t *)plain_idx, rbk->len, rbk->offset) != + 0) { ERR("write rollback index error\n"); ret = AVB_IO_RESULT_ERROR_IO; goto fail; @@ -1131,8 +1132,9 @@ AvbIOResult fsl_read_permanent_attributes( /* use hard code permanent attributes due to limited fuse and RPMB */ attributes->version = fsl_version; memcpy(attributes->product_root_public_key, fsl_product_root_public_key, - sizeof(fsl_product_root_public_key)); - memcpy(attributes->product_id, fsl_atx_product_id, sizeof(fsl_atx_product_id)); + sizeof(fsl_product_root_public_key)); + memcpy(attributes->product_id, fsl_atx_product_id, + sizeof(fsl_atx_product_id)); return AVB_IO_RESULT_OK; } @@ -1148,8 +1150,9 @@ AvbIOResult fsl_read_permanent_attributes_hash( /* read first 112 bits of sha256(permanent attributes) from fuse */ if (fsl_fuse_read(sha256_hash_fuse, ATX_FUSE_BANK_NUM, - PERMANENT_ATTRIBUTE_HASH_OFFSET)) { - printf("ERROR - read permanent attributes hash from fuse error\n"); + PERMANENT_ATTRIBUTE_HASH_OFFSET)) { + printf("ERROR - read permanent attributes hash from " + "fuse error\n"); return AVB_IO_RESULT_ERROR_IO; } /* only take the lower 2 bytes of last bank */ @@ -1168,4 +1171,54 @@ AvbIOResult fsl_read_permanent_attributes_hash( memcpy(hash, sha256_hash_buf, AVB_SHA256_DIGEST_SIZE); return AVB_IO_RESULT_OK; } + +/* Provides the key version of a key used during verification. This may be + * useful for managing the minimum key version. + */ +void fsl_set_key_version(AvbAtxOps* atx_ops, + size_t rollback_index_location, + uint64_t key_version) { + kblb_hdr_t hdr; + kblb_tag_t *rbk; + uint64_t *plain_idx = NULL; + struct mmc *mmc_dev; + static const uint32_t kTypeMask = 0xF000; + + DEBUGAVB("[rpmb] write to rollback slot: (%zu, %" PRIu64 ")\n", + rollback_index_location, key_version); + + assert(atx_ops != NULL); + + if ((mmc_dev = get_mmc()) == NULL) { + ERR("err get mmc device\n"); + } + /* read the kblb header */ + if (rpmb_read(mmc_dev, (uint8_t *)&hdr, sizeof(hdr), 0) != 0) { + ERR("read RPMB error\n"); + } + + if (memcmp(hdr.magic, AVB_KBLB_MAGIC, AVB_KBLB_MAGIC_LEN) != 0) { + ERR("magic not match\n"); + } + + /* rollback index for Android Things key versions */ + rbk = &hdr.atx_rbk_tags[rollback_index_location & ~kTypeMask]; + + plain_idx = malloc(rbk->len); + if (plain_idx == NULL) + printf("\nError! allocate memory fail!\n"); + memset(plain_idx, 0, rbk->len); + *plain_idx = key_version; + + /* write rollback_index keyblob */ + if (rpmb_write(mmc_dev, (uint8_t *)plain_idx, rbk->len, rbk->offset) != + 0) { + ERR("write rollback index error\n"); + goto fail; + } +fail: + if (plain_idx != NULL) + free(plain_idx); +} + #endif diff --git a/lib/avb/libavb/Makefile b/lib/avb/libavb/Makefile index 2badd6013f..f09c0d4bc6 100644 --- a/lib/avb/libavb/Makefile +++ b/lib/avb/libavb/Makefile @@ -14,4 +14,4 @@ obj-y += avb_descriptor.o \ avb_hashtree_descriptor.o \ avb_sha256.o \ avb_util.o \ - avb_sysdeps_uboot.o + avb_cmdline.o diff --git a/lib/avb/libavb/avb_cmdline.c b/lib/avb/libavb/avb_cmdline.c new file mode 100644 index 0000000000..3f4b99ace9 --- /dev/null +++ b/lib/avb/libavb/avb_cmdline.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "avb_cmdline.h" +#include "avb_sha.h" +#include "avb_util.h" +#include "avb_version.h" + +#define NUM_GUIDS 3 + +/* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with + * values. Returns NULL on OOM, otherwise the cmdline with values + * replaced. + */ +char* avb_sub_cmdline(AvbOps* ops, + const char* cmdline, + const char* ab_suffix, + bool using_boot_for_vbmeta, + const AvbCmdlineSubstList* additional_substitutions) { + const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"}; + const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)", + "$(ANDROID_BOOT_PARTUUID)", + "$(ANDROID_VBMETA_PARTUUID)"}; + char* ret = NULL; + AvbIOResult io_ret; + size_t n; + + /* Special-case for when the top-level vbmeta struct is in the boot + * partition. + */ + if (using_boot_for_vbmeta) { + part_name_str[2] = "boot"; + } + + /* Replace unique partition GUIDs */ + for (n = 0; n < NUM_GUIDS; n++) { + char part_name[AVB_PART_NAME_MAX_SIZE]; + char guid_buf[37]; + + if (!avb_str_concat(part_name, + sizeof part_name, + part_name_str[n], + avb_strlen(part_name_str[n]), + ab_suffix, + avb_strlen(ab_suffix))) { + avb_error("Partition name and suffix does not fit.\n"); + goto fail; + } + + io_ret = ops->get_unique_guid_for_partition( + ops, part_name, guid_buf, sizeof guid_buf); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + goto fail; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_error("Error getting unique GUID for partition.\n"); + goto fail; + } + + if (ret == NULL) { + ret = avb_replace(cmdline, replace_str[n], guid_buf); + } else { + char* new_ret = avb_replace(ret, replace_str[n], guid_buf); + avb_free(ret); + ret = new_ret; + } + if (ret == NULL) { + goto fail; + } + } + + avb_assert(ret != NULL); + + /* Replace any additional substitutions. */ + if (additional_substitutions != NULL) { + for (n = 0; n < additional_substitutions->size; ++n) { + char* new_ret = avb_replace(ret, + additional_substitutions->tokens[n], + additional_substitutions->values[n]); + avb_free(ret); + ret = new_ret; + if (ret == NULL) { + goto fail; + } + } + } + + return ret; + +fail: + if (ret != NULL) { + avb_free(ret); + } + return NULL; +} + +static int cmdline_append_option(AvbSlotVerifyData* slot_data, + const char* key, + const char* value) { + size_t offset, key_len, value_len; + char* new_cmdline; + + key_len = avb_strlen(key); + value_len = avb_strlen(value); + + offset = 0; + if (slot_data->cmdline != NULL) { + offset = avb_strlen(slot_data->cmdline); + if (offset > 0) { + offset += 1; + } + } + + new_cmdline = avb_calloc(offset + key_len + value_len + 2); + if (new_cmdline == NULL) { + return 0; + } + if (offset > 0) { + avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1); + new_cmdline[offset - 1] = ' '; + } + avb_memcpy(new_cmdline + offset, key, key_len); + new_cmdline[offset + key_len] = '='; + avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len); + if (slot_data->cmdline != NULL) { + avb_free(slot_data->cmdline); + } + slot_data->cmdline = new_cmdline; + + return 1; +} + +#define AVB_MAX_DIGITS_UINT64 32 + +/* Writes |value| to |digits| in base 10 followed by a NUL byte. + * Returns number of characters written excluding the NUL byte. + */ +static size_t uint64_to_base10(uint64_t value, + char digits[AVB_MAX_DIGITS_UINT64]) { + char rev_digits[AVB_MAX_DIGITS_UINT64]; + size_t n, num_digits; + + for (num_digits = 0; num_digits < AVB_MAX_DIGITS_UINT64 - 1;) { + rev_digits[num_digits++] = avb_div_by_10(&value) + '0'; + if (value == 0) { + break; + } + } + + for (n = 0; n < num_digits; n++) { + digits[n] = rev_digits[num_digits - 1 - n]; + } + digits[n] = '\0'; + return n; +} + +static int cmdline_append_version(AvbSlotVerifyData* slot_data, + const char* key, + uint64_t major_version, + uint64_t minor_version) { + char major_digits[AVB_MAX_DIGITS_UINT64]; + char minor_digits[AVB_MAX_DIGITS_UINT64]; + char combined[AVB_MAX_DIGITS_UINT64 * 2 + 1]; + size_t num_major_digits, num_minor_digits; + + num_major_digits = uint64_to_base10(major_version, major_digits); + num_minor_digits = uint64_to_base10(minor_version, minor_digits); + avb_memcpy(combined, major_digits, num_major_digits); + combined[num_major_digits] = '.'; + avb_memcpy(combined + num_major_digits + 1, minor_digits, num_minor_digits); + combined[num_major_digits + 1 + num_minor_digits] = '\0'; + + return cmdline_append_option(slot_data, key, combined); +} + +static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data, + const char* key, + uint64_t value) { + char digits[AVB_MAX_DIGITS_UINT64]; + uint64_to_base10(value, digits); + return cmdline_append_option(slot_data, key, digits); +} + +static int cmdline_append_hex(AvbSlotVerifyData* slot_data, + const char* key, + const uint8_t* data, + size_t data_len) { + int ret; + char* hex_data = avb_bin2hex(data, data_len); + if (hex_data == NULL) { + return 0; + } + ret = cmdline_append_option(slot_data, key, hex_data); + avb_free(hex_data); + return ret; +} + +AvbSlotVerifyResult avb_append_options( + AvbOps* ops, + AvbSlotVerifyData* slot_data, + AvbVBMetaImageHeader* toplevel_vbmeta, + AvbAlgorithmType algorithm_type, + AvbHashtreeErrorMode hashtree_error_mode) { + AvbSlotVerifyResult ret; + const char* verity_mode; + bool is_device_unlocked; + AvbIOResult io_ret; + + /* Add androidboot.vbmeta.device option. */ + if (!cmdline_append_option(slot_data, + "androidboot.vbmeta.device", + "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + + /* Add androidboot.vbmeta.avb_version option. */ + if (!cmdline_append_version(slot_data, + "androidboot.vbmeta.avb_version", + AVB_VERSION_MAJOR, + AVB_VERSION_MINOR)) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + + /* Set androidboot.avb.device_state to "locked" or "unlocked". */ + io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_error("Error getting device state.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; + goto out; + } + if (!cmdline_append_option(slot_data, + "androidboot.vbmeta.device_state", + is_device_unlocked ? "unlocked" : "locked")) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + + /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash + * function as is used to sign vbmeta. + */ + switch (algorithm_type) { + /* Explicit fallthrough. */ + case AVB_ALGORITHM_TYPE_NONE: + case AVB_ALGORITHM_TYPE_SHA256_RSA2048: + case AVB_ALGORITHM_TYPE_SHA256_RSA4096: + case AVB_ALGORITHM_TYPE_SHA256_RSA8192: { + size_t n, total_size = 0; + uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE]; + avb_slot_verify_data_calculate_vbmeta_digest( + slot_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest); + for (n = 0; n < slot_data->num_vbmeta_images; n++) { + total_size += slot_data->vbmeta_images[n].vbmeta_size; + } + if (!cmdline_append_option( + slot_data, "androidboot.vbmeta.hash_alg", "sha256") || + !cmdline_append_uint64_base10( + slot_data, "androidboot.vbmeta.size", total_size) || + !cmdline_append_hex(slot_data, + "androidboot.vbmeta.digest", + vbmeta_digest, + AVB_SHA256_DIGEST_SIZE)) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + } break; + /* Explicit fallthrough. */ + case AVB_ALGORITHM_TYPE_SHA512_RSA2048: + case AVB_ALGORITHM_TYPE_SHA512_RSA4096: + case AVB_ALGORITHM_TYPE_SHA512_RSA8192: { + size_t n, total_size = 0; + uint8_t vbmeta_digest[AVB_SHA512_DIGEST_SIZE]; + avb_slot_verify_data_calculate_vbmeta_digest( + slot_data, AVB_DIGEST_TYPE_SHA512, vbmeta_digest); + for (n = 0; n < slot_data->num_vbmeta_images; n++) { + total_size += slot_data->vbmeta_images[n].vbmeta_size; + } + if (!cmdline_append_option( + slot_data, "androidboot.vbmeta.hash_alg", "sha512") || + !cmdline_append_uint64_base10( + slot_data, "androidboot.vbmeta.size", total_size) || + !cmdline_append_hex(slot_data, + "androidboot.vbmeta.digest", + vbmeta_digest, + AVB_SHA512_DIGEST_SIZE)) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + } break; + case _AVB_ALGORITHM_NUM_TYPES: + avb_assert_not_reached(); + break; + } + + /* Set androidboot.veritymode and androidboot.vbmeta.invalidate_on_error */ + if (toplevel_vbmeta->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) { + verity_mode = "disabled"; + } else { + const char* dm_verity_mode; + char* new_ret; + + switch (hashtree_error_mode) { + case AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE: + if (!cmdline_append_option( + slot_data, "androidboot.vbmeta.invalidate_on_error", "yes")) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + verity_mode = "enforcing"; + dm_verity_mode = "restart_on_corruption"; + break; + case AVB_HASHTREE_ERROR_MODE_RESTART: + verity_mode = "enforcing"; + dm_verity_mode = "restart_on_corruption"; + break; + case AVB_HASHTREE_ERROR_MODE_EIO: + verity_mode = "eio"; + /* For now there's no option to specify the EIO mode. So + * just use 'ignore_zero_blocks' since that's already set + * and dm-verity-target.c supports specifying this multiple + * times. + */ + dm_verity_mode = "ignore_zero_blocks"; + break; + case AVB_HASHTREE_ERROR_MODE_LOGGING: + verity_mode = "logging"; + dm_verity_mode = "ignore_corruption"; + break; + } + new_ret = avb_replace( + slot_data->cmdline, "$(ANDROID_VERITY_MODE)", dm_verity_mode); + avb_free(slot_data->cmdline); + slot_data->cmdline = new_ret; + if (slot_data->cmdline == NULL) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + } + if (!cmdline_append_option( + slot_data, "androidboot.veritymode", verity_mode)) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto out; + } + + ret = AVB_SLOT_VERIFY_RESULT_OK; + +out: + + return ret; +} + +AvbCmdlineSubstList* avb_new_cmdline_subst_list() { + return (AvbCmdlineSubstList*)avb_calloc(sizeof(AvbCmdlineSubstList)); +} + +void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst) { + size_t i; + for (i = 0; i < cmdline_subst->size; ++i) { + avb_free(cmdline_subst->tokens[i]); + avb_free(cmdline_subst->values[i]); + } + cmdline_subst->size = 0; + avb_free(cmdline_subst); +} + +AvbSlotVerifyResult avb_add_root_digest_substitution( + const char* part_name, + const uint8_t* digest, + size_t digest_size, + AvbCmdlineSubstList* out_cmdline_subst) { + const char* kDigestSubPrefix = "$(AVB_"; + const char* kDigestSubSuffix = "_ROOT_DIGEST)"; + size_t part_name_len = avb_strlen(part_name); + size_t list_index = out_cmdline_subst->size; + + avb_assert(part_name_len < AVB_PART_NAME_MAX_SIZE); + avb_assert(digest_size <= AVB_SHA512_DIGEST_SIZE); + if (part_name_len >= AVB_PART_NAME_MAX_SIZE || + digest_size > AVB_SHA512_DIGEST_SIZE) { + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } + + if (out_cmdline_subst->size >= AVB_MAX_NUM_CMDLINE_SUBST) { + /* The list is full. Currently dynamic growth of this list is not supported. + */ + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } + + /* Construct the token to replace in the command line based on the partition + * name. For partition 'foo', this will be '$(AVB_FOO_ROOT_DIGEST)'. + */ + out_cmdline_subst->tokens[list_index] = + avb_strdupv(kDigestSubPrefix, part_name, kDigestSubSuffix, NULL); + if (out_cmdline_subst->tokens[list_index] == NULL) { + goto fail; + } + avb_uppercase(out_cmdline_subst->tokens[list_index]); + + /* The digest value is hex encoded when inserted in the command line. */ + out_cmdline_subst->values[list_index] = avb_bin2hex(digest, digest_size); + if (out_cmdline_subst->values[list_index] == NULL) { + goto fail; + } + + out_cmdline_subst->size++; + return AVB_SLOT_VERIFY_RESULT_OK; + +fail: + if (out_cmdline_subst->tokens[list_index]) { + avb_free(out_cmdline_subst->tokens[list_index]); + } + if (out_cmdline_subst->values[list_index]) { + avb_free(out_cmdline_subst->values[list_index]); + } + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; +} diff --git a/lib/avb/libavb/avb_cmdline.h b/lib/avb/libavb/avb_cmdline.h new file mode 100644 index 0000000000..996535d088 --- /dev/null +++ b/lib/avb/libavb/avb_cmdline.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef AVB_INSIDE_LIBAVB_H +#error "You can't include avb_sha.h in the public header libavb.h." +#endif + +#ifndef AVB_COMPILATION +#error "Never include this file, it may only be used from internal avb code." +#endif + +#ifndef AVB_CMDLINE_H_ +#define AVB_CMDLINE_H_ + +#include "avb_ops.h" +#include "avb_slot_verify.h" + +/* Maximum allow length (in bytes) of a partition name, including + * ab_suffix. + */ +#define AVB_PART_NAME_MAX_SIZE 32 + +#define AVB_MAX_NUM_CMDLINE_SUBST 10 + +/* Holds information about command-line substitutions. */ +typedef struct AvbCmdlineSubstList { + size_t size; + char* tokens[AVB_MAX_NUM_CMDLINE_SUBST]; + char* values[AVB_MAX_NUM_CMDLINE_SUBST]; +} AvbCmdlineSubstList; + +/* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with + * values. Returns NULL on OOM, otherwise the cmdline with values + * replaced. + */ +char* avb_sub_cmdline(AvbOps* ops, + const char* cmdline, + const char* ab_suffix, + bool using_boot_for_vbmeta, + const AvbCmdlineSubstList* additional_substitutions); + +AvbSlotVerifyResult avb_append_options( + AvbOps* ops, + AvbSlotVerifyData* slot_data, + AvbVBMetaImageHeader* toplevel_vbmeta, + AvbAlgorithmType algorithm_type, + AvbHashtreeErrorMode hashtree_error_mode); + +/* Allocates and initializes a new command line substitution list. Free with + * |avb_free_cmdline_subst_list|. + */ +AvbCmdlineSubstList* avb_new_cmdline_subst_list(void); + +/* Use this instead of |avb_free| to deallocate a AvbCmdlineSubstList. */ +void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst); + +/* Adds a hashtree root digest to be substituted in $(AVB_*_ROOT_DIGEST) + * variables. The partition name differentiates the variable. For example, if + * |part_name| is "foo" then $(AVB_FOO_ROOT_DIGEST) will be substituted with the + * hex encoding of the digest. The substitution will be added to + * |out_cmdline_subst|. Returns AVB_SLOT_VERIFY_RESULT_OK on success. + */ +AvbSlotVerifyResult avb_add_root_digest_substitution( + const char* part_name, + const uint8_t* digest, + size_t digest_size, + AvbCmdlineSubstList* out_cmdline_subst); + +#endif diff --git a/lib/avb/libavb/avb_crypto.c b/lib/avb/libavb/avb_crypto.c index a428443ae3..a99ff80d65 100644 --- a/lib/avb/libavb/avb_crypto.c +++ b/lib/avb/libavb/avb_crypto.c @@ -355,8 +355,7 @@ static AvbAlgorithmData algorithm_data[_AVB_ALGORITHM_NUM_TYPES] = { }; const AvbAlgorithmData* avb_get_algorithm_data(AvbAlgorithmType algorithm) { - if (algorithm >= AVB_ALGORITHM_TYPE_NONE && - algorithm < _AVB_ALGORITHM_NUM_TYPES) { + if ((size_t)algorithm < _AVB_ALGORITHM_NUM_TYPES) { return &algorithm_data[algorithm]; } return NULL; diff --git a/lib/avb/libavb/avb_crypto.h b/lib/avb/libavb/avb_crypto.h index 7e8d7e21ca..30c1e4b989 100644 --- a/lib/avb/libavb/avb_crypto.h +++ b/lib/avb/libavb/avb_crypto.h @@ -44,12 +44,21 @@ extern "C" { /* Size of a RSA-8192 signature. */ #define AVB_RSA8192_NUM_BYTES 1024 +/* Size in bytes of a SHA-1 digest. */ +#define AVB_SHA1_DIGEST_SIZE 20 + /* Size in bytes of a SHA-256 digest. */ #define AVB_SHA256_DIGEST_SIZE 32 /* Size in bytes of a SHA-512 digest. */ #define AVB_SHA512_DIGEST_SIZE 64 +/* Possible digest types supported by libavb routines. */ +typedef enum { + AVB_DIGEST_TYPE_SHA256, + AVB_DIGEST_TYPE_SHA512, +} AvbDigestType; + /* Algorithms that can be used in the vbmeta image for * verification. An algorithm consists of a hash type and a signature * type. diff --git a/lib/avb/libavb/avb_hash_descriptor.c b/lib/avb/libavb/avb_hash_descriptor.c index 2e427de939..3a6b8c8809 100644 --- a/lib/avb/libavb/avb_hash_descriptor.c +++ b/lib/avb/libavb/avb_hash_descriptor.c @@ -44,6 +44,7 @@ bool avb_hash_descriptor_validate_and_byteswap(const AvbHashDescriptor* src, dest->partition_name_len = avb_be32toh(dest->partition_name_len); dest->salt_len = avb_be32toh(dest->salt_len); dest->digest_len = avb_be32toh(dest->digest_len); + dest->flags = avb_be32toh(dest->flags); /* Check that partition_name, salt, and digest are fully contained. */ expected_size = sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor); diff --git a/lib/avb/libavb/avb_hash_descriptor.h b/lib/avb/libavb/avb_hash_descriptor.h index 2668118474..9ee8997123 100644 --- a/lib/avb/libavb/avb_hash_descriptor.h +++ b/lib/avb/libavb/avb_hash_descriptor.h @@ -35,6 +35,16 @@ extern "C" { #endif +/* Flags for hash descriptors. + * + * AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB: Do not apply the default A/B + * partition logic to this partition. This is intentionally a negative boolean + * because A/B should be both the default and most used in practice. + */ +typedef enum { + AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0), +} AvbHashDescriptorFlags; + /* A descriptor containing information about hash for an image. * * This descriptor is typically used for boot partitions to verify the @@ -46,6 +56,10 @@ extern "C" { * * The |reserved| field is for future expansion and must be set to NUL * bytes. + * + * Changes in v1.1: + * - flags field is added which supports AVB_HASH_DESCRIPTOR_FLAGS_USE_AB + * - digest_len may be zero, which indicates the use of a persistent digest */ typedef struct AvbHashDescriptor { AvbDescriptor parent_descriptor; @@ -54,7 +68,8 @@ typedef struct AvbHashDescriptor { uint32_t partition_name_len; uint32_t salt_len; uint32_t digest_len; - uint8_t reserved[64]; + uint32_t flags; + uint8_t reserved[60]; } AVB_ATTR_PACKED AvbHashDescriptor; /* Copies |src| to |dest| and validates, byte-swapping fields in the diff --git a/lib/avb/libavb/avb_hashtree_descriptor.c b/lib/avb/libavb/avb_hashtree_descriptor.c index b961e47cc6..0822458f84 100644 --- a/lib/avb/libavb/avb_hashtree_descriptor.c +++ b/lib/avb/libavb/avb_hashtree_descriptor.c @@ -52,6 +52,7 @@ bool avb_hashtree_descriptor_validate_and_byteswap( dest->partition_name_len = avb_be32toh(dest->partition_name_len); dest->salt_len = avb_be32toh(dest->salt_len); dest->root_digest_len = avb_be32toh(dest->root_digest_len); + dest->flags = avb_be32toh(dest->flags); /* Check that partition_name, salt, and root_digest are fully contained. */ expected_size = sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor); diff --git a/lib/avb/libavb/avb_hashtree_descriptor.h b/lib/avb/libavb/avb_hashtree_descriptor.h index a5aafbf058..d0f7e2c288 100644 --- a/lib/avb/libavb/avb_hashtree_descriptor.h +++ b/lib/avb/libavb/avb_hashtree_descriptor.h @@ -35,6 +35,16 @@ extern "C" { #endif +/* Flags for hashtree descriptors. + * + * AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB: Do not apply the default A/B + * partition logic to this partition. This is intentionally a negative boolean + * because A/B should be both the default and most used in practice. + */ +typedef enum { + AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0), +} AvbHashtreeDescriptorFlags; + /* A descriptor containing information about a dm-verity hashtree. * * Hash-trees are used to verify large partitions typically containing @@ -48,6 +58,10 @@ extern "C" { * * The |reserved| field is for future expansion and must be set to NUL * bytes. + * + * Changes in v1.1: + * - flags field is added which supports AVB_HASHTREE_DESCRIPTOR_FLAGS_USE_AB + * - digest_len may be zero, which indicates the use of a persistent digest */ typedef struct AvbHashtreeDescriptor { AvbDescriptor parent_descriptor; @@ -64,7 +78,8 @@ typedef struct AvbHashtreeDescriptor { uint32_t partition_name_len; uint32_t salt_len; uint32_t root_digest_len; - uint8_t reserved[64]; + uint32_t flags; + uint8_t reserved[60]; } AVB_ATTR_PACKED AvbHashtreeDescriptor; /* Copies |src| to |dest| and validates, byte-swapping fields in the diff --git a/lib/avb/libavb/avb_ops.h b/lib/avb/libavb/avb_ops.h index de36b599c0..77f7ec3c12 100644 --- a/lib/avb/libavb/avb_ops.h +++ b/lib/avb/libavb/avb_ops.h @@ -35,6 +35,9 @@ extern "C" { #endif +/* Well-known names of named persistent values. */ +#define AVB_NPV_PERSISTENT_DIGEST_PREFIX "avb.persistent_digest." + /* Return codes used for I/O operations. * * AVB_IO_RESULT_OK is returned if the requested operation was @@ -51,13 +54,25 @@ extern "C" { * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION is returned if the * range of bytes requested to be read or written is outside the range * of the partition. + * + * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE is returned if a named persistent value + * does not exist. + * + * AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE is returned if a named persistent + * value size is not supported or does not match the expected size. + * + * AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE is returned if a buffer is too small + * for the requested operation. */ typedef enum { AVB_IO_RESULT_OK, AVB_IO_RESULT_ERROR_OOM, AVB_IO_RESULT_ERROR_IO, AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, - AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION + AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION, + AVB_IO_RESULT_ERROR_NO_SUCH_VALUE, + AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE, + AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE, } AvbIOResult; struct AvbOps; @@ -117,6 +132,27 @@ struct AvbOps { void* buffer, size_t* out_num_read); + /* Gets the starting pointer of a partition that is pre-loaded in memory, and + * save it to |out_pointer|. The preloaded partition is expected to be + * |num_bytes|, where the actual preloaded byte count is returned in + * |out_num_bytes_preloaded|. |out_num_bytes_preloaded| must be no larger than + * |num_bytes|. + * + * This provides an alternative way to access a partition that is preloaded + * into memory without a full memory copy. When this function pointer is not + * set (has value NULL), or when the |out_pointer| is set to NULL as a result, + * |read_from_partition| will be used as the fallback. This function is mainly + * used for accessing the entire partition content to calculate its hash. + * + * Preloaded partition data must outlive the lifespan of the + * |AvbSlotVerifyData| structure that |avb_slot_verify| outputs. + */ + AvbIOResult (*get_preloaded_partition)(AvbOps* ops, + const char* partition, + size_t num_bytes, + uint8_t** out_pointer, + size_t* out_num_bytes_preloaded); + /* Writes |num_bytes| from |bffer| at offset |offset| to partition * with name |partition| (NUL-terminated UTF-8 string). If |offset| * is negative, its absolute value should be interpreted as the @@ -219,6 +255,53 @@ struct AvbOps { AvbIOResult (*get_size_of_partition)(AvbOps* ops, const char* partition, uint64_t* out_size_num_bytes); + + /* Reads a persistent value corresponding to the given |name|. The value is + * returned in |out_buffer| which must point to |buffer_size| bytes. On + * success |out_num_bytes_read| contains the number of bytes read into + * |out_buffer|. If AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE is returned, + * |out_num_bytes_read| contains the number of bytes that would have been read + * which can be used to allocate a buffer. + * + * The |buffer_size| may be zero and the |out_buffer| may be NULL, but if + * |out_buffer| is NULL then |buffer_size| *must* be zero. + * + * Returns AVB_IO_RESULT_OK on success, otherwise an error code. + * + * If the value does not exist, is not supported, or is not populated, returns + * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. If |buffer_size| is smaller than the + * size of the stored value, returns AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE. + * + * This operation is currently only used to support persistent digests. If a + * device does not use persistent digests this function pointer can be set to + * NULL. + */ + AvbIOResult (*read_persistent_value)(AvbOps* ops, + const char* name, + size_t buffer_size, + uint8_t* out_buffer, + size_t* out_num_bytes_read); + + /* Writes a persistent value corresponding to the given |name|. The value is + * supplied in |value| which must point to |value_size| bytes. Any existing + * value with the same name is overwritten. If |value_size| is zero, future + * calls to |read_persistent_value| will return + * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. + * + * Returns AVB_IO_RESULT_OK on success, otherwise an error code. + * + * If the value |name| is not supported, returns + * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. If the |value_size| is not supported, + * returns AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE. + * + * This operation is currently only used to support persistent digests. If a + * device does not use persistent digests this function pointer can be set to + * NULL. + */ + AvbIOResult (*write_persistent_value)(AvbOps* ops, + const char* name, + size_t value_size, + const uint8_t* value); }; #ifdef __cplusplus diff --git a/lib/avb/libavb/avb_slot_verify.c b/lib/avb/libavb/avb_slot_verify.c index ba95351b07..64e2666643 100644 --- a/lib/avb/libavb/avb_slot_verify.c +++ b/lib/avb/libavb/avb_slot_verify.c @@ -24,19 +24,16 @@ #include "avb_slot_verify.h" #include "avb_chain_partition_descriptor.h" +#include "avb_cmdline.h" #include "avb_footer.h" #include "avb_hash_descriptor.h" +#include "avb_hashtree_descriptor.h" #include "avb_kernel_cmdline_descriptor.h" #include "avb_sha.h" #include "avb_util.h" #include "avb_vbmeta_image.h" #include "avb_version.h" -/* Maximum allow length (in bytes) of a partition name, including - * ab_suffix. - */ -#define PART_NAME_MAX_SIZE 32 - /* Maximum number of partitions that can be loaded with avb_slot_verify(). */ #define MAX_NUMBER_OF_LOADED_PARTITIONS 32 @@ -69,6 +66,114 @@ static inline bool result_should_continue(AvbSlotVerifyResult result) { return false; } +static AvbSlotVerifyResult load_full_partition(AvbOps* ops, + const char* part_name, + uint64_t image_size, + uint8_t** out_image_buf, + bool* out_image_preloaded) { + size_t part_num_read; + AvbIOResult io_ret; + + /* Make sure that we do not overwrite existing data. */ + avb_assert(*out_image_buf == NULL); + avb_assert(!*out_image_preloaded); + + /* We are going to implicitly cast image_size from uint64_t to size_t in the + * following code, so we need to make sure that the cast is safe. */ + if (image_size != (size_t)(image_size)) { + avb_errorv(part_name, ": Partition size too large to load.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } + + /* Try use a preloaded one. */ + if (ops->get_preloaded_partition != NULL) { + io_ret = ops->get_preloaded_partition( + ops, part_name, image_size, out_image_buf, &part_num_read); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_errorv(part_name, ": Error loading data from partition.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_IO; + } + + if (*out_image_buf != NULL) { + if (part_num_read != image_size) { + avb_errorv(part_name, ": Read incorrect number of bytes.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_IO; + } + *out_image_preloaded = true; + } + } + + /* Allocate and copy the partition. */ + if (!*out_image_preloaded) { + *out_image_buf = avb_malloc(image_size); + if (*out_image_buf == NULL) { + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + } + + io_ret = ops->read_from_partition(ops, + part_name, + 0 /* offset */, + image_size, + *out_image_buf, + &part_num_read); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_errorv(part_name, ": Error loading data from partition.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_IO; + } + if (part_num_read != image_size) { + avb_errorv(part_name, ": Read incorrect number of bytes.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_IO; + } + } + + return AVB_SLOT_VERIFY_RESULT_OK; +} + +static AvbSlotVerifyResult read_persistent_digest(AvbOps* ops, + const char* part_name, + size_t expected_digest_size, + uint8_t* out_digest) { + char* persistent_value_name = NULL; + AvbIOResult io_ret = AVB_IO_RESULT_OK; + size_t stored_digest_size = 0; + + if (ops->read_persistent_value == NULL) { + avb_errorv(part_name, ": Persistent values are not implemented.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } + persistent_value_name = + avb_strdupv(AVB_NPV_PERSISTENT_DIGEST_PREFIX, part_name, NULL); + if (persistent_value_name == NULL) { + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + } + io_ret = ops->read_persistent_value(ops, + persistent_value_name, + expected_digest_size, + out_digest, + &stored_digest_size); + avb_free(persistent_value_name); + if (io_ret == AVB_IO_RESULT_ERROR_OOM) { + return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + } else if (io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_VALUE) { + avb_errorv(part_name, ": Persistent digest does not exist.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } else if (io_ret == AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE || + io_ret == AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE || + expected_digest_size != stored_digest_size) { + avb_errorv( + part_name, ": Persistent digest is not of expected size.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + } else if (io_ret != AVB_IO_RESULT_OK) { + avb_errorv(part_name, ": Error reading persistent digest.\n", NULL); + return AVB_SLOT_VERIFY_RESULT_ERROR_IO; + } + return AVB_SLOT_VERIFY_RESULT_OK; +} + static AvbSlotVerifyResult load_and_verify_hash_partition( AvbOps* ops, const char* const* requested_partitions, @@ -80,15 +185,18 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( const uint8_t* desc_partition_name = NULL; const uint8_t* desc_salt; const uint8_t* desc_digest; - char part_name[PART_NAME_MAX_SIZE]; + char part_name[AVB_PART_NAME_MAX_SIZE]; AvbSlotVerifyResult ret; AvbIOResult io_ret; uint8_t* image_buf = NULL; - size_t part_num_read; + bool image_preloaded = false; uint8_t* digest; size_t digest_len; const char* found; uint64_t image_size; + size_t expected_digest_len = 0; + uint8_t expected_digest_buf[AVB_SHA512_DIGEST_SIZE]; + const uint8_t* expected_digest = NULL; if (!avb_hash_descriptor_validate_and_byteswap( (const AvbHashDescriptor*)descriptor, &hash_desc)) { @@ -118,15 +226,35 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( goto out; } - if (!avb_str_concat(part_name, - sizeof part_name, - (const char*)desc_partition_name, - hash_desc.partition_name_len, - ab_suffix, - avb_strlen(ab_suffix))) { - avb_error("Partition name and suffix does not fit.\n"); + if ((hash_desc.flags & AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB) != 0) { + /* No ab_suffix, just copy the partition name as is. */ + if (hash_desc.partition_name_len >= AVB_PART_NAME_MAX_SIZE) { + avb_error("Partition name does not fit.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + avb_memcpy(part_name, desc_partition_name, hash_desc.partition_name_len); + part_name[hash_desc.partition_name_len] = '\0'; + } else if (hash_desc.digest_len == 0 && avb_strlen(ab_suffix) != 0) { + /* No ab_suffix allowed for partitions without a digest in the descriptor + * because these partitions hold data unique to this device and are not + * updated using an A/B scheme. + */ + avb_error("Cannot use A/B with a persistent digest.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; + } else { + /* Add ab_suffix to the partition name. */ + if (!avb_str_concat(part_name, + sizeof part_name, + (const char*)desc_partition_name, + hash_desc.partition_name_len, + ab_suffix, + avb_strlen(ab_suffix))) { + avb_error("Partition name and suffix does not fit.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } } /* If we're allowing verification errors then hash_desc.image_size @@ -159,25 +287,9 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( } } - image_buf = avb_malloc(image_size); - if (image_buf == NULL) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - io_ret = ops->read_from_partition( - ops, part_name, 0 /* offset */, image_size, image_buf, &part_num_read); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error loading data from partition.\n", NULL); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; - goto out; - } - if (part_num_read != image_size) { - avb_errorv(part_name, ": Read fewer than requested bytes.\n", NULL); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; + ret = load_full_partition( + ops, part_name, image_size, &image_buf, &image_preloaded); + if (ret != AVB_SLOT_VERIFY_RESULT_OK) { goto out; } @@ -201,14 +313,31 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( goto out; } - if (digest_len != hash_desc.digest_len) { + if (hash_desc.digest_len == 0) { + // Expect a match to a persistent digest. + avb_debugv(part_name, ": No digest, using persistent digest.\n", NULL); + expected_digest_len = digest_len; + expected_digest = expected_digest_buf; + avb_assert(expected_digest_len <= sizeof(expected_digest_buf)); + ret = + read_persistent_digest(ops, part_name, digest_len, expected_digest_buf); + if (ret != AVB_SLOT_VERIFY_RESULT_OK) { + goto out; + } + } else { + // Expect a match to the digest in the descriptor. + expected_digest_len = hash_desc.digest_len; + expected_digest = desc_digest; + } + + if (digest_len != expected_digest_len) { avb_errorv( part_name, ": Digest in descriptor not of expected size.\n", NULL); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } - if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) { + if (avb_safe_memcmp(digest, expected_digest, digest_len) != 0) { avb_errorv(part_name, ": Hash of data does not match digest in descriptor.\n", NULL); @@ -234,11 +363,12 @@ out: loaded_partition->partition_name = avb_strdup(found); loaded_partition->data_size = image_size; loaded_partition->data = image_buf; + loaded_partition->preloaded = image_preloaded; image_buf = NULL; } fail: - if (image_buf != NULL) { + if (image_buf != NULL && !image_preloaded) { avb_free(image_buf); } return ret; @@ -251,6 +381,7 @@ static AvbSlotVerifyResult load_requested_partitions( AvbSlotVerifyData* slot_data) { AvbSlotVerifyResult ret; uint8_t* image_buf = NULL; + bool image_preloaded = false; size_t n; if (ops->get_size_of_partition == NULL) { @@ -260,10 +391,9 @@ static AvbSlotVerifyResult load_requested_partitions( } for (n = 0; requested_partitions[n] != NULL; n++) { - char part_name[PART_NAME_MAX_SIZE]; + char part_name[AVB_PART_NAME_MAX_SIZE]; AvbIOResult io_ret; uint64_t image_size; - size_t part_num_read; AvbPartitionData* loaded_partition; if (!avb_str_concat(part_name, @@ -288,25 +418,9 @@ static AvbSlotVerifyResult load_requested_partitions( } avb_debugv(part_name, ": Loading entire partition.\n", NULL); - image_buf = avb_malloc(image_size); - if (image_buf == NULL) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - io_ret = ops->read_from_partition( - ops, part_name, 0 /* offset */, image_size, image_buf, &part_num_read); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error loading data from partition.\n", NULL); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; - goto out; - } - if (part_num_read != image_size) { - avb_errorv(part_name, ": Read fewer than requested bytes.\n", NULL); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; + ret = load_full_partition( + ops, part_name, image_size, &image_buf, &image_preloaded); + if (ret != AVB_SLOT_VERIFY_RESULT_OK) { goto out; } @@ -324,16 +438,21 @@ static AvbSlotVerifyResult load_requested_partitions( goto out; } loaded_partition->data_size = image_size; - loaded_partition->data = image_buf; + loaded_partition->data = image_buf; /* Transferring the owner. */ + loaded_partition->preloaded = image_preloaded; image_buf = NULL; + image_preloaded = false; } ret = AVB_SLOT_VERIFY_RESULT_OK; out: - if (image_buf != NULL) { + /* Free the current buffer if any. */ + if (image_buf != NULL && !image_preloaded) { avb_free(image_buf); } + /* Buffers that are already saved in slot_data will be handled by the caller + * even on failure. */ return ret; } @@ -349,8 +468,9 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( const uint8_t* expected_public_key, size_t expected_public_key_length, AvbSlotVerifyData* slot_data, - AvbAlgorithmType* out_algorithm_type) { - char full_partition_name[PART_NAME_MAX_SIZE]; + AvbAlgorithmType* out_algorithm_type, + AvbCmdlineSubstList* out_additional_cmdline_subst) { + char full_partition_name[AVB_PART_NAME_MAX_SIZE]; AvbSlotVerifyResult ret; AvbIOResult io_ret; size_t vbmeta_offset; @@ -485,7 +605,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( NULL /* expected_public_key */, 0 /* expected_public_key_length */, slot_data, - out_algorithm_type); + out_algorithm_type, + out_additional_cmdline_subst); goto out; } else { avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL); @@ -681,7 +802,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( * checks that it matches what's in the hash descriptor. * * - hashtree descriptor: Do nothing since verification happens - * on-the-fly from within the OS. + * on-the-fly from within the OS. (Unless the descriptor uses a + * persistent digest, in which case we need to find it). * * - chained partition descriptor: Load the footer, load the vbmeta * image, verify vbmeta image (includes rollback checks, hash @@ -752,18 +874,20 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( sizeof(AvbChainPartitionDescriptor); chain_public_key = chain_partition_name + chain_desc.partition_name_len; - sub_ret = load_and_verify_vbmeta(ops, - requested_partitions, - ab_suffix, - allow_verification_error, - toplevel_vbmeta_flags, - chain_desc.rollback_index_location, - (const char*)chain_partition_name, - chain_desc.partition_name_len, - chain_public_key, - chain_desc.public_key_len, - slot_data, - NULL /* out_algorithm_type */); + sub_ret = + load_and_verify_vbmeta(ops, + requested_partitions, + ab_suffix, + allow_verification_error, + toplevel_vbmeta_flags, + chain_desc.rollback_index_location, + (const char*)chain_partition_name, + chain_desc.partition_name_len, + chain_public_key, + chain_desc.public_key_len, + slot_data, + NULL, /* out_algorithm_type */ + NULL /* out_additional_cmdline_subst */); if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { ret = sub_ret; if (!result_should_continue(ret)) { @@ -849,9 +973,90 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( } } break; - /* Explicit fall-through */ + case AVB_DESCRIPTOR_TAG_HASHTREE: { + AvbHashtreeDescriptor hashtree_desc; + + if (!avb_hashtree_descriptor_validate_and_byteswap( + (AvbHashtreeDescriptor*)descriptors[n], &hashtree_desc)) { + avb_errorv( + full_partition_name, ": Hashtree descriptor is invalid.\n", NULL); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + + /* We only need to continue when there is no digest in the descriptor. + * This is because the only processing here is to find the digest and + * make it available on the kernel command line. + */ + if (hashtree_desc.root_digest_len == 0) { + char part_name[AVB_PART_NAME_MAX_SIZE]; + size_t digest_len = 0; + uint8_t digest_buf[AVB_SHA512_DIGEST_SIZE]; + const uint8_t* desc_partition_name = + ((const uint8_t*)descriptors[n]) + sizeof(AvbHashtreeDescriptor); + + if (!avb_validate_utf8(desc_partition_name, + hashtree_desc.partition_name_len)) { + avb_error("Partition name is not valid UTF-8.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + + /* No ab_suffix for partitions without a digest in the descriptor + * because these partitions hold data unique to this device and are + * not updated using an A/B scheme. + */ + if ((hashtree_desc.flags & + AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB) == 0 && + avb_strlen(ab_suffix) != 0) { + avb_error("Cannot use A/B with a persistent root digest.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + if (hashtree_desc.partition_name_len >= AVB_PART_NAME_MAX_SIZE) { + avb_error("Partition name does not fit.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + avb_memcpy( + part_name, desc_partition_name, hashtree_desc.partition_name_len); + part_name[hashtree_desc.partition_name_len] = '\0'; + + /* Determine the expected digest size from the hash algorithm. */ + if (avb_strcmp((const char*)hashtree_desc.hash_algorithm, "sha1") == + 0) { + digest_len = AVB_SHA1_DIGEST_SIZE; + } else if (avb_strcmp((const char*)hashtree_desc.hash_algorithm, + "sha256") == 0) { + digest_len = AVB_SHA256_DIGEST_SIZE; + } else if (avb_strcmp((const char*)hashtree_desc.hash_algorithm, + "sha512") == 0) { + digest_len = AVB_SHA512_DIGEST_SIZE; + } else { + avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + + ret = read_persistent_digest(ops, part_name, digest_len, digest_buf); + if (ret != AVB_SLOT_VERIFY_RESULT_OK) { + goto out; + } + + if (out_additional_cmdline_subst) { + ret = + avb_add_root_digest_substitution(part_name, + digest_buf, + digest_len, + out_additional_cmdline_subst); + if (ret != AVB_SLOT_VERIFY_RESULT_OK) { + goto out; + } + } + } + } break; + case AVB_DESCRIPTOR_TAG_PROPERTY: - case AVB_DESCRIPTOR_TAG_HASHTREE: /* Do nothing. */ break; } @@ -886,350 +1091,6 @@ out: return ret; } -#define NUM_GUIDS 3 - -/* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with - * values. Returns NULL on OOM, otherwise the cmdline with values - * replaced. - */ -static char* sub_cmdline(AvbOps* ops, - const char* cmdline, - const char* ab_suffix, - bool using_boot_for_vbmeta) { - const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"}; - const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)", - "$(ANDROID_BOOT_PARTUUID)", - "$(ANDROID_VBMETA_PARTUUID)"}; - char* ret = NULL; - AvbIOResult io_ret; - - /* Special-case for when the top-level vbmeta struct is in the boot - * partition. - */ - if (using_boot_for_vbmeta) { - part_name_str[2] = "boot"; - } - - /* Replace unique partition GUIDs */ - for (size_t n = 0; n < NUM_GUIDS; n++) { - char part_name[PART_NAME_MAX_SIZE]; - char guid_buf[37]; - - if (!avb_str_concat(part_name, - sizeof part_name, - part_name_str[n], - avb_strlen(part_name_str[n]), - ab_suffix, - avb_strlen(ab_suffix))) { - avb_error("Partition name and suffix does not fit.\n"); - goto fail; - } - - io_ret = ops->get_unique_guid_for_partition( - ops, part_name, guid_buf, sizeof guid_buf); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - return NULL; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_error("Error getting unique GUID for partition.\n"); - goto fail; - } - - if (ret == NULL) { - ret = avb_replace(cmdline, replace_str[n], guid_buf); - } else { - char* new_ret = avb_replace(ret, replace_str[n], guid_buf); - avb_free(ret); - ret = new_ret; - } - if (ret == NULL) { - goto fail; - } - } - - return ret; - -fail: - if (ret != NULL) { - avb_free(ret); - } - return NULL; -} - -static int cmdline_append_option(AvbSlotVerifyData* slot_data, - const char* key, - const char* value) { - size_t offset, key_len, value_len; - char* new_cmdline; - - key_len = avb_strlen(key); - value_len = avb_strlen(value); - - offset = 0; - if (slot_data->cmdline != NULL) { - offset = avb_strlen(slot_data->cmdline); - if (offset > 0) { - offset += 1; - } - } - - new_cmdline = avb_calloc(offset + key_len + value_len + 2); - if (new_cmdline == NULL) { - return 0; - } - if (offset > 0) { - avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1); - new_cmdline[offset - 1] = ' '; - } - avb_memcpy(new_cmdline + offset, key, key_len); - new_cmdline[offset + key_len] = '='; - avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len); - if (slot_data->cmdline != NULL) { - avb_free(slot_data->cmdline); - } - slot_data->cmdline = new_cmdline; - - return 1; -} - -#define AVB_MAX_DIGITS_UINT64 32 - -/* Writes |value| to |digits| in base 10 followed by a NUL byte. - * Returns number of characters written excluding the NUL byte. - */ -static size_t uint64_to_base10(uint64_t value, - char digits[AVB_MAX_DIGITS_UINT64]) { - char rev_digits[AVB_MAX_DIGITS_UINT64]; - size_t n, num_digits; - - for (num_digits = 0; num_digits < AVB_MAX_DIGITS_UINT64 - 1;) { - rev_digits[num_digits++] = (value % 10) + '0'; - value /= 10; - if (value == 0) { - break; - } - } - - for (n = 0; n < num_digits; n++) { - digits[n] = rev_digits[num_digits - 1 - n]; - } - digits[n] = '\0'; - return n; -} - -static int cmdline_append_version(AvbSlotVerifyData* slot_data, - const char* key, - uint64_t major_version, - uint64_t minor_version) { - char major_digits[AVB_MAX_DIGITS_UINT64]; - char minor_digits[AVB_MAX_DIGITS_UINT64]; - char combined[AVB_MAX_DIGITS_UINT64 * 2 + 1]; - size_t num_major_digits, num_minor_digits; - - num_major_digits = uint64_to_base10(major_version, major_digits); - num_minor_digits = uint64_to_base10(minor_version, minor_digits); - avb_memcpy(combined, major_digits, num_major_digits); - combined[num_major_digits] = '.'; - avb_memcpy(combined + num_major_digits + 1, minor_digits, num_minor_digits); - combined[num_major_digits + 1 + num_minor_digits] = '\0'; - - return cmdline_append_option(slot_data, key, combined); -} - -static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data, - const char* key, - uint64_t value) { - char digits[AVB_MAX_DIGITS_UINT64]; - uint64_to_base10(value, digits); - return cmdline_append_option(slot_data, key, digits); -} - -static int cmdline_append_hex(AvbSlotVerifyData* slot_data, - const char* key, - const uint8_t* data, - size_t data_len) { - char hex_digits[17] = "0123456789abcdef"; - char* hex_data; - int ret; - size_t n; - - hex_data = avb_malloc(data_len * 2 + 1); - if (hex_data == NULL) { - return 0; - } - - for (n = 0; n < data_len; n++) { - hex_data[n * 2] = hex_digits[data[n] >> 4]; - hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f]; - } - hex_data[n * 2] = '\0'; - - ret = cmdline_append_option(slot_data, key, hex_data); - avb_free(hex_data); - return ret; -} - -static AvbSlotVerifyResult append_options( - AvbOps* ops, - AvbSlotVerifyData* slot_data, - AvbVBMetaImageHeader* toplevel_vbmeta, - AvbAlgorithmType algorithm_type, - AvbHashtreeErrorMode hashtree_error_mode) { - AvbSlotVerifyResult ret; - const char* verity_mode = NULL; - bool is_device_unlocked; - AvbIOResult io_ret; - - /* Add androidboot.vbmeta.device option. */ - if (!cmdline_append_option(slot_data, - "androidboot.vbmeta.device", - "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - /* Add androidboot.vbmeta.avb_version option. */ - if (!cmdline_append_version(slot_data, - "androidboot.vbmeta.avb_version", - AVB_VERSION_MAJOR, - AVB_VERSION_MINOR)) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - /* Set androidboot.avb.device_state to "locked" or "unlocked". */ - io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_error("Error getting device state.\n"); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; - goto out; - } - if (!cmdline_append_option(slot_data, - "androidboot.vbmeta.device_state", - is_device_unlocked ? "unlocked" : "locked")) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash - * function as is used to sign vbmeta. - */ - switch (algorithm_type) { - /* Explicit fallthrough. */ - case AVB_ALGORITHM_TYPE_NONE: - case AVB_ALGORITHM_TYPE_SHA256_RSA2048: - case AVB_ALGORITHM_TYPE_SHA256_RSA4096: - case AVB_ALGORITHM_TYPE_SHA256_RSA8192: { - AvbSHA256Ctx ctx; - size_t n, total_size = 0; - avb_sha256_init(&ctx); - for (n = 0; n < slot_data->num_vbmeta_images; n++) { - avb_sha256_update(&ctx, - slot_data->vbmeta_images[n].vbmeta_data, - slot_data->vbmeta_images[n].vbmeta_size); - total_size += slot_data->vbmeta_images[n].vbmeta_size; - } - if (!cmdline_append_option( - slot_data, "androidboot.vbmeta.hash_alg", "sha256") || - !cmdline_append_uint64_base10( - slot_data, "androidboot.vbmeta.size", total_size) || - !cmdline_append_hex(slot_data, - "androidboot.vbmeta.digest", - avb_sha256_final(&ctx), - AVB_SHA256_DIGEST_SIZE)) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - } break; - /* Explicit fallthrough. */ - case AVB_ALGORITHM_TYPE_SHA512_RSA2048: - case AVB_ALGORITHM_TYPE_SHA512_RSA4096: - case AVB_ALGORITHM_TYPE_SHA512_RSA8192: { - AvbSHA512Ctx ctx; - size_t n, total_size = 0; - avb_sha512_init(&ctx); - for (n = 0; n < slot_data->num_vbmeta_images; n++) { - avb_sha512_update(&ctx, - slot_data->vbmeta_images[n].vbmeta_data, - slot_data->vbmeta_images[n].vbmeta_size); - total_size += slot_data->vbmeta_images[n].vbmeta_size; - } - if (!cmdline_append_option( - slot_data, "androidboot.vbmeta.hash_alg", "sha512") || - !cmdline_append_uint64_base10( - slot_data, "androidboot.vbmeta.size", total_size) || - !cmdline_append_hex(slot_data, - "androidboot.vbmeta.digest", - avb_sha512_final(&ctx), - AVB_SHA512_DIGEST_SIZE)) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - } break; - case _AVB_ALGORITHM_NUM_TYPES: - avb_assert_not_reached(); - break; - } - - /* Set androidboot.veritymode and androidboot.vbmeta.invalidate_on_error */ - if (toplevel_vbmeta->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) { - verity_mode = "disabled"; - } else { - const char* dm_verity_mode = NULL; - char* new_ret; - - switch (hashtree_error_mode) { - case AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE: - if (!cmdline_append_option( - slot_data, "androidboot.vbmeta.invalidate_on_error", "yes")) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - verity_mode = "enforcing"; - dm_verity_mode = "restart_on_corruption"; - break; - case AVB_HASHTREE_ERROR_MODE_RESTART: - verity_mode = "enforcing"; - dm_verity_mode = "restart_on_corruption"; - break; - case AVB_HASHTREE_ERROR_MODE_EIO: - verity_mode = "eio"; - /* For now there's no option to specify the EIO mode. So - * just use 'ignore_zero_blocks' since that's already set - * and dm-verity-target.c supports specifying this multiple - * times. - */ - dm_verity_mode = "ignore_zero_blocks"; - break; - case AVB_HASHTREE_ERROR_MODE_LOGGING: - verity_mode = "logging"; - dm_verity_mode = "ignore_corruption"; - break; - } - new_ret = avb_replace( - slot_data->cmdline, "$(ANDROID_VERITY_MODE)", dm_verity_mode); - avb_free(slot_data->cmdline); - slot_data->cmdline = new_ret; - if (slot_data->cmdline == NULL) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - } - if (!cmdline_append_option( - slot_data, "androidboot.veritymode", verity_mode)) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto out; - } - - ret = AVB_SLOT_VERIFY_RESULT_OK; - -out: - - return ret; -} - AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, const char* const* requested_partitions, const char* ab_suffix, @@ -1243,6 +1104,7 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, AvbVBMetaImageHeader toplevel_vbmeta; bool allow_verification_error = (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR); + AvbCmdlineSubstList* additional_cmdline_subst = NULL; /* Fail early if we're missing the AvbOps needed for slot verification. * @@ -1254,7 +1116,6 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, avb_assert(ops->validate_vbmeta_public_key != NULL); avb_assert(ops->read_rollback_index != NULL); avb_assert(ops->get_unique_guid_for_partition != NULL); - /* avb_assert(ops->get_size_of_partition != NULL); */ if (out_data != NULL) { *out_data = NULL; @@ -1288,6 +1149,12 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, goto fail; } + additional_cmdline_subst = avb_new_cmdline_subst_list(); + if (additional_cmdline_subst == NULL) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto fail; + } + ret = load_and_verify_vbmeta(ops, requested_partitions, ab_suffix, @@ -1299,7 +1166,8 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, NULL /* expected_public_key */, 0 /* expected_public_key_length */, slot_data, - &algorithm_type); + &algorithm_type, + additional_cmdline_subst); if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) { goto fail; } @@ -1341,14 +1209,14 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, goto fail; } } else { - /* Add options - any failure in append_options() is either an + /* Add options - any failure in avb_append_options() is either an * I/O or OOM error. */ - AvbSlotVerifyResult sub_ret = append_options(ops, - slot_data, - &toplevel_vbmeta, - algorithm_type, - hashtree_error_mode); + AvbSlotVerifyResult sub_ret = avb_append_options(ops, + slot_data, + &toplevel_vbmeta, + algorithm_type, + hashtree_error_mode); if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { ret = sub_ret; goto fail; @@ -1358,14 +1226,19 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */ if (slot_data->cmdline != NULL) { char* new_cmdline; - new_cmdline = sub_cmdline( - ops, slot_data->cmdline, ab_suffix, using_boot_for_vbmeta); - if (new_cmdline == NULL) { - ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; - goto fail; + new_cmdline = avb_sub_cmdline(ops, + slot_data->cmdline, + ab_suffix, + using_boot_for_vbmeta, + additional_cmdline_subst); + if (new_cmdline != slot_data->cmdline) { + if (new_cmdline == NULL) { + ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; + goto fail; + } + avb_free(slot_data->cmdline); + slot_data->cmdline = new_cmdline; } - avb_free(slot_data->cmdline); - slot_data->cmdline = new_cmdline; } if (out_data != NULL) { @@ -1375,6 +1248,9 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, } } + avb_free_cmdline_subst_list(additional_cmdline_subst); + additional_cmdline_subst = NULL; + if (!allow_verification_error) { avb_assert(ret == AVB_SLOT_VERIFY_RESULT_OK); } @@ -1385,6 +1261,9 @@ fail: if (slot_data != NULL) { avb_slot_verify_data_free(slot_data); } + if (additional_cmdline_subst != NULL) { + avb_free_cmdline_subst_list(additional_cmdline_subst); + } return ret; } @@ -1415,7 +1294,7 @@ void avb_slot_verify_data_free(AvbSlotVerifyData* data) { if (loaded_partition->partition_name != NULL) { avb_free(loaded_partition->partition_name); } - if (loaded_partition->data != NULL) { + if (loaded_partition->data != NULL && !loaded_partition->preloaded) { avb_free(loaded_partition->data); } } @@ -1465,3 +1344,42 @@ const char* avb_slot_verify_result_to_string(AvbSlotVerifyResult result) { return ret; } + +void avb_slot_verify_data_calculate_vbmeta_digest(AvbSlotVerifyData* data, + AvbDigestType digest_type, + uint8_t* out_digest) { + bool ret = false; + size_t n; + + switch (digest_type) { + case AVB_DIGEST_TYPE_SHA256: { + AvbSHA256Ctx ctx; + avb_sha256_init(&ctx); + for (n = 0; n < data->num_vbmeta_images; n++) { + avb_sha256_update(&ctx, + data->vbmeta_images[n].vbmeta_data, + data->vbmeta_images[n].vbmeta_size); + } + avb_memcpy(out_digest, avb_sha256_final(&ctx), AVB_SHA256_DIGEST_SIZE); + ret = true; + } break; + + case AVB_DIGEST_TYPE_SHA512: { + AvbSHA512Ctx ctx; + avb_sha512_init(&ctx); + for (n = 0; n < data->num_vbmeta_images; n++) { + avb_sha512_update(&ctx, + data->vbmeta_images[n].vbmeta_data, + data->vbmeta_images[n].vbmeta_size); + } + avb_memcpy(out_digest, avb_sha512_final(&ctx), AVB_SHA512_DIGEST_SIZE); + ret = true; + } break; + + /* Do not add a 'default:' case here because of -Wswitch. */ + } + + if (!ret) { + avb_fatal("Unknown digest type"); + } +} diff --git a/lib/avb/libavb/avb_slot_verify.h b/lib/avb/libavb/avb_slot_verify.h index 60033cfbe3..375db3da31 100644 --- a/lib/avb/libavb/avb_slot_verify.h +++ b/lib/avb/libavb/avb_slot_verify.h @@ -114,7 +114,10 @@ const char* avb_slot_verify_result_to_string(AvbSlotVerifyResult result); /* AvbPartitionData contains data loaded from partitions when using * avb_slot_verify(). The |partition_name| field contains the name of * the partition (without A/B suffix), |data| points to the loaded - * data which is |data_size| bytes long. + * data which is |data_size| bytes long. If |preloaded| is set to true, + * this structure dose not own |data|. The caller of |avb_slot_verify| + * needs to make sure that the preloaded data outlives this + * |AvbPartitionData| structure. * * Note that this is strictly less than the partition size - it's only * the image stored there, not the entire partition nor any of the @@ -124,6 +127,7 @@ typedef struct { char* partition_name; uint8_t* data; size_t data_size; + bool preloaded; } AvbPartitionData; /* AvbVBMetaData contains a vbmeta struct loaded from a partition when @@ -256,9 +260,15 @@ typedef struct { uint64_t rollback_indexes[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS]; } AvbSlotVerifyData; -/* Fast version of avb_slot_verify_data_free, this method will not - * free bootimage */ -void avb_slot_verify_data_free_fast(AvbSlotVerifyData* data); +/* Calculates a digest of all vbmeta images in |data| using + * the digest indicated by |digest_type|. Stores the result + * in |out_digest| which must be large enough to hold a digest + * of the requested type. + */ +void avb_slot_verify_data_calculate_vbmeta_digest(AvbSlotVerifyData* data, + AvbDigestType digest_type, + uint8_t* out_digest); + /* Frees a |AvbSlotVerifyData| including all data it points to. */ void avb_slot_verify_data_free(AvbSlotVerifyData* data); diff --git a/lib/avb/libavb/avb_sysdeps.h b/lib/avb/libavb/avb_sysdeps.h index 34556e22c8..cc832970f1 100644 --- a/lib/avb/libavb/avb_sysdeps.h +++ b/lib/avb/libavb/avb_sysdeps.h @@ -108,6 +108,10 @@ void avb_free(void* ptr); /* Returns the lenght of |str|, excluding the terminating NUL-byte. */ size_t avb_strlen(const char* str) AVB_ATTR_WARN_UNUSED_RESULT; +/* Divide the |dividend| by 10 and saves back to the pointer. Return the + * remainder. */ +uint32_t avb_div_by_10(uint64_t* dividend); + #ifdef __cplusplus } #endif diff --git a/lib/avb/libavb/avb_sysdeps_posix.c b/lib/avb/libavb/avb_sysdeps_posix.c index ea40d08d06..0cbabee06c 100644 --- a/lib/avb/libavb/avb_sysdeps_posix.c +++ b/lib/avb/libavb/avb_sysdeps_posix.c @@ -76,3 +76,9 @@ void* avb_malloc_(size_t size) { void avb_free(void* ptr) { free(ptr); } + +uint32_t avb_div_by_10(uint64_t* dividend) { + uint32_t rem = (uint32_t)(*dividend % 10); + *dividend /= 10; + return rem; +} diff --git a/lib/avb/libavb/avb_util.c b/lib/avb/libavb/avb_util.c index 43662b4cb8..c04c79ae71 100644 --- a/lib/avb/libavb/avb_util.c +++ b/lib/avb/libavb/avb_util.c @@ -299,7 +299,7 @@ char* avb_replace(const char* str, const char* search, const char* replace) { char* new_str; num_new = ret_len + num_before + replace_len + 1; new_str = avb_malloc(num_new); - if (ret == NULL) { + if (new_str == NULL) { goto out; } avb_memcpy(new_str, ret, ret_len); @@ -324,7 +324,7 @@ char* avb_replace(const char* str, const char* search, const char* replace) { size_t num_remaining = avb_strlen(str_after_last_replace); size_t num_new = ret_len + num_remaining + 1; char* new_str = avb_malloc(num_new); - if (ret == NULL) { + if (new_str == NULL) { goto out; } avb_memcpy(new_str, ret, ret_len); @@ -401,3 +401,30 @@ const char* avb_basename(const char* str) { } return str; } + +void avb_uppercase(char* str) { + size_t i; + for (i = 0; str[i] != '\0'; ++i) { + if (str[i] <= 0x7A && str[i] >= 0x61) { + str[i] -= 0x20; + } + } +} + +char* avb_bin2hex(const uint8_t* data, size_t data_len) { + const char hex_digits[17] = "0123456789abcdef"; + char* hex_data; + size_t n; + + hex_data = avb_malloc(data_len * 2 + 1); + if (hex_data == NULL) { + return NULL; + } + + for (n = 0; n < data_len; n++) { + hex_data[n * 2] = hex_digits[data[n] >> 4]; + hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f]; + } + hex_data[n * 2] = '\0'; + return hex_data; +} diff --git a/lib/avb/libavb/avb_util.h b/lib/avb/libavb/avb_util.h index 07c3258768..be1b3c9b21 100644 --- a/lib/avb/libavb/avb_util.h +++ b/lib/avb/libavb/avb_util.h @@ -270,6 +270,16 @@ uint32_t avb_crc32(const uint8_t* buf, size_t buf_size); */ const char* avb_basename(const char* str); +/* Converts any ascii lowercase characters in |str| to uppercase in-place. + * |str| must be NUL-terminated and valid UTF-8. + */ +void avb_uppercase(char* str); + +/* Converts |data_len| bytes of |data| to hex and returns the result. Returns + * NULL on OOM. Caller must free the returned string with avb_free. + */ +char* avb_bin2hex(const uint8_t* data, size_t data_len); + #ifdef __cplusplus } #endif diff --git a/lib/avb/libavb/avb_version.h b/lib/avb/libavb/avb_version.h index 9d92970074..ce4313604e 100644 --- a/lib/avb/libavb/avb_version.h +++ b/lib/avb/libavb/avb_version.h @@ -37,7 +37,7 @@ extern "C" { /* The version number of AVB - keep in sync with avbtool. */ #define AVB_VERSION_MAJOR 1 -#define AVB_VERSION_MINOR 0 +#define AVB_VERSION_MINOR 1 #define AVB_VERSION_SUB 0 /* Returns a NUL-terminated string for the libavb version in use. The diff --git a/lib/avb/libavb_ab/avb_ab_flow.c b/lib/avb/libavb_ab/avb_ab_flow.c index e3c412d818..bf6eab1542 100644 --- a/lib/avb/libavb_ab/avb_ab_flow.c +++ b/lib/avb/libavb_ab/avb_ab_flow.c @@ -406,307 +406,6 @@ out: return ret; } -/* For legacy i.mx6/7, we won't enable A/B due to the limitation of - * storage capacity, but we still want to verify boot/recovery with - * AVB. */ -AvbABFlowResult avb_single_flow(AvbABOps* ab_ops, - const char* const* requested_partitions, - AvbSlotVerifyFlags flags, - AvbHashtreeErrorMode hashtree_error_mode, - AvbSlotVerifyData** out_data) { - AvbOps* ops = ab_ops->ops; - AvbSlotVerifyData* slot_data = NULL; - AvbSlotVerifyData* data = NULL; - AvbABFlowResult ret; - bool saw_and_allowed_verification_error = false; - - /* Validate boot/recovery. */ - AvbSlotVerifyResult verify_result; - - verify_result = avb_slot_verify(ops, - requested_partitions, - "", - flags, - hashtree_error_mode, - &slot_data); - switch (verify_result) { - case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - goto out; - - case AVB_SLOT_VERIFY_RESULT_ERROR_IO: - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - goto out; - - case AVB_SLOT_VERIFY_RESULT_OK: - break; - - 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. - */ - ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; - goto out; - - /* explicit fallthrough. */ - 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 (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR) { - /* Do nothing since we allow this. */ - avb_debugv("Allowing slot ", - slot_suffixes[n], - " which verified " - "with result ", - avb_slot_verify_result_to_string(verify_result), - " because " - "AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " - "is set.\n", - NULL); - saw_and_allowed_verification_error = true; - } else { - ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; - goto out; - } - break; - - case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT: - ret = AVB_AB_FLOW_RESULT_ERROR_INVALID_ARGUMENT; - goto out; - /* Do not add a 'default:' case here because of -Wswitch. */ - } - - avb_assert(slot_data != NULL); - data = slot_data; - slot_data = NULL; - if (saw_and_allowed_verification_error) { - avb_assert(flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR); - ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR; - } else { - ret = AVB_AB_FLOW_RESULT_OK; - } - -out: - if (slot_data != NULL) { - avb_slot_verify_data_free(slot_data); - } - - if (out_data != NULL) { - *out_data = data; - } else { - if (data != NULL) { - avb_slot_verify_data_free(data); - } - } - - return ret; -} - -AvbABFlowResult avb_ab_flow_fast(AvbABOps* ab_ops, - const char* const* requested_partitions, - AvbSlotVerifyFlags flags, - AvbHashtreeErrorMode hashtree_error_mode, - AvbSlotVerifyData** out_data) { - AvbOps* ops = ab_ops->ops; - AvbSlotVerifyData* slot_data[2] = {NULL, NULL}; - AvbSlotVerifyData* data = NULL; - AvbABFlowResult ret; - AvbABData ab_data, ab_data_orig; - size_t slot_index_to_boot, n; - AvbIOResult io_ret; - bool saw_and_allowed_verification_error = false; - size_t target_slot; - AvbSlotVerifyResult verify_result; - bool set_slot_unbootable = false; - - io_ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - goto out; - } - - slot_index_to_boot = 2; // Means not 0 or 1 - target_slot = (ab_data.slots[1].priority > ab_data.slots[0].priority? 1 : 0); - - for (n = 0; n < 2; n++) { - if (!slot_is_bootable(&ab_data.slots[target_slot])) { - target_slot = (target_slot == 1 ? 0 : 1); - continue; - } - verify_result = avb_slot_verify(ops, - requested_partitions, - slot_suffixes[target_slot], - flags, - hashtree_error_mode, - &slot_data[target_slot]); - switch (verify_result) { - case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - goto out; - - case AVB_SLOT_VERIFY_RESULT_ERROR_IO: - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - goto out; - - case AVB_SLOT_VERIFY_RESULT_OK: - slot_index_to_boot = target_slot; - n = 2; - break; - - 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; - break; - - /* explicit fallthrough. */ - 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 (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR) { - /* Do nothing since we allow this. */ - avb_debugv("Allowing slot ", - slot_suffixes[target_slot], - " which verified " - "with result ", - avb_slot_verify_result_to_string(verify_result), - " because " - "AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " - "is set.\n", - NULL); - saw_and_allowed_verification_error = true; - slot_index_to_boot = target_slot; - n = 2; - } else { - set_slot_unbootable = true; - } - break; - - case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT: - ret = AVB_AB_FLOW_RESULT_ERROR_INVALID_ARGUMENT; - goto out; - /* Do not add a 'default:' case here because of -Wswitch. */ - } - - if (set_slot_unbootable) { - avb_errorv("Error verifying slot ", - slot_suffixes[target_slot], - " with result ", - avb_slot_verify_result_to_string(verify_result), - " - setting unbootable.\n", - NULL); - slot_set_unbootable(&ab_data.slots[target_slot]); - set_slot_unbootable = false; - } - /* switch to another slot */ - target_slot = (target_slot == 1 ? 0 : 1); - } - - if (slot_index_to_boot == 2) { - /* No bootable slots! */ - avb_error("No bootable slots found.\n"); - ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS; - goto out; - } - - /* Update stored rollback index such that the stored rollback index - * is the largest value supporting all currently bootable slots. Do - * this for every rollback index location. - */ - for (n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) { - uint64_t rollback_index_value = 0; - - if (slot_data[0] != NULL && slot_data[1] != NULL) { - uint64_t a_rollback_index = slot_data[0]->rollback_indexes[n]; - uint64_t b_rollback_index = slot_data[1]->rollback_indexes[n]; - rollback_index_value = - (a_rollback_index < b_rollback_index ? a_rollback_index - : b_rollback_index); - } else if (slot_data[0] != NULL) { - rollback_index_value = slot_data[0]->rollback_indexes[n]; - } else if (slot_data[1] != NULL) { - rollback_index_value = slot_data[1]->rollback_indexes[n]; - } - - if (rollback_index_value != 0) { - uint64_t current_rollback_index_value; - io_ret = ops->read_rollback_index(ops, n, ¤t_rollback_index_value); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_error("Error getting rollback index for slot.\n"); - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - goto out; - } - if (current_rollback_index_value != rollback_index_value) { - io_ret = ops->write_rollback_index(ops, n, rollback_index_value); - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - goto out; - } else if (io_ret != AVB_IO_RESULT_OK) { - avb_error("Error setting stored rollback index.\n"); - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - goto out; - } - } - } - } - - /* Finally, select this slot. */ - avb_assert(slot_data[slot_index_to_boot] != NULL); - data = slot_data[slot_index_to_boot]; - slot_data[slot_index_to_boot] = NULL; - if (saw_and_allowed_verification_error) { - avb_assert(flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR); - ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR; - } else { - ret = AVB_AB_FLOW_RESULT_OK; - } - - /* ... and decrement tries remaining, if applicable. */ - if (!ab_data.slots[slot_index_to_boot].successful_boot && - ab_data.slots[slot_index_to_boot].tries_remaining > 0) { - ab_data.slots[slot_index_to_boot].tries_remaining -= 1; - } - -out: - io_ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); - if (io_ret != AVB_IO_RESULT_OK) { - if (io_ret == AVB_IO_RESULT_ERROR_OOM) { - ret = AVB_AB_FLOW_RESULT_ERROR_OOM; - } else { - ret = AVB_AB_FLOW_RESULT_ERROR_IO; - } - if (data != NULL) { - avb_slot_verify_data_free(data); - data = NULL; - } - } - - for (n = 0; n < 2; n++) { - if (slot_data[n] != NULL) { - avb_slot_verify_data_free(slot_data[n]); - } - } - - if (out_data != NULL) { - *out_data = data; - } else { - if (data != NULL) { - avb_slot_verify_data_free(data); - } - } - - return ret; -} - AvbIOResult avb_ab_mark_slot_active(AvbABOps* ab_ops, unsigned int slot_number) { AvbABData ab_data, ab_data_orig; diff --git a/lib/avb/libavb_ab/avb_ab_flow.h b/lib/avb/libavb_ab/avb_ab_flow.h index 1974ca312e..588026d5a5 100644 --- a/lib/avb/libavb_ab/avb_ab_flow.h +++ b/lib/avb/libavb_ab/avb_ab_flow.h @@ -223,19 +223,6 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops, AvbHashtreeErrorMode hashtree_error_mode, AvbSlotVerifyData** out_data); -AvbABFlowResult avb_ab_flow_fast(AvbABOps* ab_ops, - const char* const* requested_partitions, - AvbSlotVerifyFlags flags, - AvbHashtreeErrorMode hashtree_error_mode, - AvbSlotVerifyData** out_data); -/* This is for legacy i.mx6/7 which don't enable A/B but want to - * verify boot/recovery with AVB */ -AvbABFlowResult avb_single_flow(AvbABOps* ab_ops, - const char* const* requested_partitions, - AvbSlotVerifyFlags flags, - AvbHashtreeErrorMode hashtree_error_mode, - AvbSlotVerifyData** out_data); - /* Marks the slot with the given slot number as active. Returns * AVB_IO_RESULT_OK on success, error code otherwise. * diff --git a/lib/avb/libavb_ab/avb_ab_ops.h b/lib/avb/libavb_ab/avb_ab_ops.h index 725c51fdff..8d8fde7aa9 100644 --- a/lib/avb/libavb_ab/avb_ab_ops.h +++ b/lib/avb/libavb_ab/avb_ab_ops.h @@ -30,7 +30,7 @@ #ifndef AVB_AB_OPS_H_ #define AVB_AB_OPS_H_ -#include "../libavb/libavb.h" +#include <libavb/libavb.h> #ifdef __cplusplus extern "C" { diff --git a/lib/avb/libavb_ab/libavb_ab.h b/lib/avb/libavb_ab/libavb_ab.h index 970a040c6c..654ff5e771 100644 --- a/lib/avb/libavb_ab/libavb_ab.h +++ b/lib/avb/libavb_ab/libavb_ab.h @@ -25,7 +25,18 @@ #ifndef LIBAVB_AB_H_ #define LIBAVB_AB_H_ -#include "../libavb/libavb.h" +#include <libavb/libavb.h> + +/* The libavb_ab/ and boot_control/ code has been marked for some time + * as experimental in anticipation of being removed in the future. It + * is now deprecated and to continue using it you must define + * AVB_AB_I_UNDERSTAND_LIBAVB_AB_IS_DEPRECATED. It will be removed Jun + * 1 2018. + */ +#ifndef AVB_AB_I_UNDERSTAND_LIBAVB_AB_IS_DEPRECATED +#error \ + "You must define AVB_AB_I_UNDERSTAND_LIBAVB_AB_IS_DEPRECATED to use this library." +#endif /* The AVB_INSIDE_LIBAVB_AB_H preprocessor symbol is used to enforce * library users to include only this file. All public interfaces, and diff --git a/lib/avb/libavb_atx/avb_atx_ops.h b/lib/avb/libavb_atx/avb_atx_ops.h index 8d2196ba9c..53c898d623 100644 --- a/lib/avb/libavb_atx/avb_atx_ops.h +++ b/lib/avb/libavb_atx/avb_atx_ops.h @@ -30,9 +30,9 @@ #ifndef AVB_ATX_OPS_H_ #define AVB_ATX_OPS_H_ -#include "../libavb/libavb.h" +#include <libavb/libavb.h> -#include "../libavb_atx/avb_atx_types.h" +#include "avb_atx_types.h" #ifdef __cplusplus extern "C" { @@ -59,6 +59,22 @@ struct AvbAtxOps { */ AvbIOResult (*read_permanent_attributes_hash)( AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]); + + /* Provides the key version of a key used during verification. This may be + * useful for managing the minimum key version. + */ + void (*set_key_version)(AvbAtxOps* atx_ops, + size_t rollback_index_location, + uint64_t key_version); + + /* Generates |num_bytes| random bytes and stores them in |output|, + * which must point to a buffer large enough to store the bytes. + * + * Returns AVB_IO_RESULT_OK on success, otherwise an error code. + */ + AvbIOResult (*get_random)(AvbAtxOps* atx_ops, + size_t num_bytes, + uint8_t* output); }; #ifdef __cplusplus diff --git a/lib/avb/libavb_atx/avb_atx_types.h b/lib/avb/libavb_atx/avb_atx_types.h index 07705498b2..e78bbfa78c 100644 --- a/lib/avb/libavb_atx/avb_atx_types.h +++ b/lib/avb/libavb_atx/avb_atx_types.h @@ -30,7 +30,7 @@ #ifndef AVB_ATX_TYPES_H_ #define AVB_ATX_TYPES_H_ -#include "../libavb/libavb.h" +#include <libavb/libavb.h> #ifdef __cplusplus extern "C" { @@ -39,6 +39,9 @@ extern "C" { /* Size in bytes of an Android Things product ID. */ #define AVB_ATX_PRODUCT_ID_SIZE 16 +/* Size in bytes of an Android Things unlock challenge. */ +#define AVB_ATX_UNLOCK_CHALLENGE_SIZE 16 + /* Size in bytes of a serialized public key with a 4096-bit modulus. */ #define AVB_ATX_PUBLIC_KEY_SIZE (sizeof(AvbRSAPublicKeyHeader) + 1024) @@ -71,6 +74,21 @@ typedef struct AvbAtxPublicKeyMetadata { AvbAtxCertificate product_signing_key_certificate; } AVB_ATTR_PACKED AvbAtxPublicKeyMetadata; +/* Data structure of an Android Things unlock challenge. */ +typedef struct AvbAtxUnlockChallenge { + uint32_t version; + uint8_t product_id_hash[AVB_SHA256_DIGEST_SIZE]; + uint8_t challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE]; +} AVB_ATTR_PACKED AvbAtxUnlockChallenge; + +/* Data structure of an Android Things unlock credential. */ +typedef struct AvbAtxUnlockCredential { + uint32_t version; + AvbAtxCertificate product_intermediate_key_certificate; + AvbAtxCertificate product_unlock_key_certificate; + uint8_t challenge_signature[AVB_RSA4096_NUM_BYTES]; +} AVB_ATTR_PACKED AvbAtxUnlockCredential; + #ifdef __cplusplus } #endif diff --git a/lib/avb/libavb_atx/avb_atx_validate.c b/lib/avb/libavb_atx/avb_atx_validate.c index 3db455cd9b..f3c1d96841 100644 --- a/lib/avb/libavb_atx/avb_atx_validate.c +++ b/lib/avb/libavb_atx/avb_atx_validate.c @@ -22,12 +22,15 @@ * SOFTWARE. */ -#include "../libavb_atx/avb_atx_validate.h" +#include "avb_atx_validate.h" -#include "../libavb/avb_rsa.h" -#include "../libavb/avb_sha.h" -#include "../libavb/avb_sysdeps.h" -#include "../libavb/avb_util.h" +#include <libavb/avb_rsa.h> +#include <libavb/avb_sha.h> +#include <libavb/avb_sysdeps.h> +#include <libavb/avb_util.h> + +/* The most recent unlock challenge generated. */ +static uint8_t last_unlock_challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE]; /* Computes the SHA256 |hash| of |length| bytes of |data|. */ static void sha256(const uint8_t* data, @@ -59,7 +62,7 @@ static void sha256_str(const char* str, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) { /* Verifies structure and |expected_hash| of permanent |attributes|. */ static bool verify_permanent_attributes( const AvbAtxPermanentAttributes* attributes, - uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) { + const uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) { uint8_t hash[AVB_SHA256_DIGEST_SIZE]; if (attributes->version != 1) { @@ -75,10 +78,11 @@ static bool verify_permanent_attributes( } /* Verifies the format, key version, usage, and signature of a certificate. */ -static bool verify_certificate(AvbAtxCertificate* certificate, - uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], - uint64_t minimum_key_version, - uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]) { +static bool verify_certificate( + const AvbAtxCertificate* certificate, + const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + uint64_t minimum_key_version, + const uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]) { const AvbAlgorithmData* algorithm_data; uint8_t certificate_hash[AVB_SHA512_DIGEST_SIZE]; @@ -115,9 +119,10 @@ static bool verify_certificate(AvbAtxCertificate* certificate, } /* Verifies signature and fields of a PIK certificate. */ -static bool verify_pik_certificate(AvbAtxCertificate* certificate, - uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], - uint64_t minimum_version) { +static bool verify_pik_certificate( + const AvbAtxCertificate* certificate, + const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + uint64_t minimum_version) { uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; sha256_str("com.google.android.things.vboot.ca", expected_usage); @@ -131,10 +136,10 @@ static bool verify_pik_certificate(AvbAtxCertificate* certificate, /* Verifies signature and fields of a PSK certificate. */ static bool verify_psk_certificate( - AvbAtxCertificate* certificate, - uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + const AvbAtxCertificate* certificate, + const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], uint64_t minimum_version, - uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) { + const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) { uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE]; uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; @@ -148,7 +153,32 @@ static bool verify_psk_certificate( if (0 != avb_safe_memcmp(certificate->signed_data.subject, expected_subject, AVB_SHA256_DIGEST_SIZE)) { - avb_error("Product ID mismatch.\n"); + avb_error("PSK: Product ID mismatch.\n"); + return false; + } + return true; +} + +/* Verifies signature and fields of a PUK certificate. */ +static bool verify_puk_certificate( + const AvbAtxCertificate* certificate, + const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + uint64_t minimum_version, + const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) { + uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE]; + uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; + + sha256_str("com.google.android.things.vboot.unlock", expected_usage); + if (!verify_certificate( + certificate, authority, minimum_version, expected_usage)) { + avb_error("Invalid PUK certificate.\n"); + return false; + } + sha256(product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_subject); + if (0 != avb_safe_memcmp(certificate->signed_data.subject, + expected_subject, + AVB_SHA256_DIGEST_SIZE)) { + avb_error("PUK: Product ID mismatch.\n"); return false; } return true; @@ -241,6 +271,131 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( return AVB_IO_RESULT_OK; } + /* Report the key versions used during verification. */ + ops->atx_ops->set_key_version( + ops->atx_ops, + AVB_ATX_PIK_VERSION_LOCATION, + metadata.product_intermediate_key_certificate.signed_data.key_version); + ops->atx_ops->set_key_version( + ops->atx_ops, + AVB_ATX_PSK_VERSION_LOCATION, + metadata.product_signing_key_certificate.signed_data.key_version); + + *out_is_trusted = true; + return AVB_IO_RESULT_OK; +} + +AvbIOResult avb_atx_generate_unlock_challenge( + AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge) { + AvbIOResult result = AVB_IO_RESULT_OK; + AvbAtxPermanentAttributes permanent_attributes; + + /* We need the permanent attributes to compute the product_id_hash. */ + result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to read permanent attributes.\n"); + return result; + } + result = atx_ops->get_random( + atx_ops, AVB_ATX_UNLOCK_CHALLENGE_SIZE, last_unlock_challenge); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to generate random challenge.\n"); + return result; + } + out_unlock_challenge->version = 1; + sha256(permanent_attributes.product_id, + AVB_ATX_PRODUCT_ID_SIZE, + out_unlock_challenge->product_id_hash); + avb_memcpy(out_unlock_challenge->challenge, + last_unlock_challenge, + AVB_ATX_UNLOCK_CHALLENGE_SIZE); + return result; +} + +AvbIOResult avb_atx_validate_unlock_credential( + AvbAtxOps* atx_ops, + const AvbAtxUnlockCredential* unlock_credential, + bool* out_is_trusted) { + AvbIOResult result = AVB_IO_RESULT_OK; + AvbAtxPermanentAttributes permanent_attributes; + uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE]; + uint64_t minimum_version; + const AvbAlgorithmData* algorithm_data; + uint8_t challenge_hash[AVB_SHA512_DIGEST_SIZE]; + + /* Be pessimistic so we can exit early without having to remember to clear. + */ + *out_is_trusted = false; + + /* Sanity check the credential. */ + if (unlock_credential->version != 1) { + avb_error("Unsupported unlock credential format.\n"); + return AVB_IO_RESULT_OK; + } + + /* Read and verify permanent attributes. */ + result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to read permanent attributes.\n"); + return result; + } + result = atx_ops->read_permanent_attributes_hash(atx_ops, + permanent_attributes_hash); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to read permanent attributes hash.\n"); + return result; + } + if (!verify_permanent_attributes(&permanent_attributes, + permanent_attributes_hash)) { + return AVB_IO_RESULT_OK; + } + + /* Verify the PIK certificate. */ + result = atx_ops->ops->read_rollback_index( + atx_ops->ops, AVB_ATX_PIK_VERSION_LOCATION, &minimum_version); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to read PIK minimum version.\n"); + return result; + } + if (!verify_pik_certificate( + &unlock_credential->product_intermediate_key_certificate, + permanent_attributes.product_root_public_key, + minimum_version)) { + return AVB_IO_RESULT_OK; + } + + /* Verify the PUK certificate. The minimum version is shared with the PSK. */ + result = atx_ops->ops->read_rollback_index( + atx_ops->ops, AVB_ATX_PSK_VERSION_LOCATION, &minimum_version); + if (result != AVB_IO_RESULT_OK) { + avb_error("Failed to read PSK minimum version.\n"); + return result; + } + if (!verify_puk_certificate( + &unlock_credential->product_unlock_key_certificate, + unlock_credential->product_intermediate_key_certificate.signed_data + .public_key, + minimum_version, + permanent_attributes.product_id)) { + return AVB_IO_RESULT_OK; + } + + /* Verify the challenge signature. */ + algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096); + sha512(last_unlock_challenge, AVB_ATX_UNLOCK_CHALLENGE_SIZE, challenge_hash); + if (!avb_rsa_verify(unlock_credential->product_unlock_key_certificate + .signed_data.public_key, + AVB_ATX_PUBLIC_KEY_SIZE, + unlock_credential->challenge_signature, + AVB_RSA4096_NUM_BYTES, + challenge_hash, + AVB_SHA512_DIGEST_SIZE, + algorithm_data->padding, + algorithm_data->padding_len)) { + avb_error("Invalid unlock challenge signature.\n"); + return AVB_IO_RESULT_OK; + } + *out_is_trusted = true; return AVB_IO_RESULT_OK; } diff --git a/lib/avb/libavb_atx/avb_atx_validate.h b/lib/avb/libavb_atx/avb_atx_validate.h index a1a83a79a9..1a0690d491 100644 --- a/lib/avb/libavb_atx/avb_atx_validate.h +++ b/lib/avb/libavb_atx/avb_atx_validate.h @@ -30,8 +30,8 @@ #ifndef AVB_ATX_VALIDATE_H_ #define AVB_ATX_VALIDATE_H_ -#include "../libavb_atx/avb_atx_ops.h" -#include "../libavb_atx/avb_atx_types.h" +#include "avb_atx_ops.h" +#include "avb_atx_types.h" #ifdef __cplusplus extern "C" { @@ -70,6 +70,20 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( size_t public_key_metadata_length, bool* out_is_trusted); +/* Generates a challenge which can be used to create an unlock credential. */ +AvbIOResult avb_atx_generate_unlock_challenge( + AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge); + +/* Validates an unlock credential. The certificate validation is very similar to + * the validation of public key metadata except in place of the PSK is a Product + * Unlock Key (PUK) and the certificate usage field identifies it as such. The + * challenge signature field is verified against this PUK. + */ +AvbIOResult avb_atx_validate_unlock_credential( + AvbAtxOps* atx_ops, + const AvbAtxUnlockCredential* unlock_credential, + bool* out_is_trusted); + #ifdef __cplusplus } #endif diff --git a/lib/avb/libavb_atx/libavb_atx.h b/lib/avb/libavb_atx/libavb_atx.h index 90d1aad66d..839c0afa98 100644 --- a/lib/avb/libavb_atx/libavb_atx.h +++ b/lib/avb/libavb_atx/libavb_atx.h @@ -25,7 +25,7 @@ #ifndef LIBAVB_ATX_H_ #define LIBAVB_ATX_H_ -#include "../libavb/libavb.h" +#include <libavb/libavb.h> /* The AVB_INSIDE_LIBAVB_ATX_H preprocessor symbol is used to enforce * library users to include only this file. All public interfaces, and |