| /* |
| * 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_ab_flow.h" |
| |
| bool avb_ab_data_verify_and_byteswap(const AvbABData* src, AvbABData* dest) { |
| /* Ensure magic is correct. */ |
| if (avb_safe_memcmp(src->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN) != 0) { |
| avb_error("Magic is incorrect.\n"); |
| return false; |
| } |
| |
| avb_memcpy(dest, src, sizeof(AvbABData)); |
| dest->crc32 = avb_be32toh(dest->crc32); |
| |
| /* Ensure we don't attempt to access any fields if the major version |
| * is not supported. |
| */ |
| if (dest->version_major > AVB_AB_MAJOR_VERSION) { |
| avb_error("No support for given major version.\n"); |
| return false; |
| } |
| |
| /* Bail if CRC32 doesn't match. */ |
| if (dest->crc32 != |
| avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t))) { |
| avb_error("CRC32 does not match.\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void avb_ab_data_update_crc_and_byteswap(const AvbABData* src, |
| AvbABData* dest) { |
| avb_memcpy(dest, src, sizeof(AvbABData)); |
| dest->crc32 = avb_htobe32( |
| avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t))); |
| } |
| |
| void avb_ab_data_init(AvbABData* data) { |
| avb_memset(data, '\0', sizeof(AvbABData)); |
| avb_memcpy(data->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN); |
| data->version_major = AVB_AB_MAJOR_VERSION; |
| data->version_minor = AVB_AB_MINOR_VERSION; |
| data->slots[0].priority = AVB_AB_MAX_PRIORITY; |
| data->slots[0].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
| data->slots[0].successful_boot = 0; |
| data->slots[1].priority = AVB_AB_MAX_PRIORITY - 1; |
| data->slots[1].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
| data->slots[1].successful_boot = 0; |
| } |
| |
| /* The AvbABData struct is stored 2048 bytes into the 'misc' partition |
| * following the 'struct bootloader_message' field. The struct is |
| * compatible with the guidelines in bootable/recovery/bootloader.h - |
| * e.g. it is stored in the |slot_suffix| field, starts with a |
| * NUL-byte, and is 32 bytes long. |
| */ |
| #define AB_METADATA_MISC_PARTITION_OFFSET 2048 |
| |
| AvbIOResult avb_ab_data_read(AvbABOps* ab_ops, AvbABData* data) { |
| AvbOps* ops = ab_ops->ops; |
| AvbABData serialized; |
| AvbIOResult io_ret; |
| size_t num_bytes_read; |
| |
| io_ret = ops->read_from_partition(ops, |
| "misc", |
| AB_METADATA_MISC_PARTITION_OFFSET, |
| sizeof(AvbABData), |
| &serialized, |
| &num_bytes_read); |
| if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
| return AVB_IO_RESULT_ERROR_OOM; |
| } else if (io_ret != AVB_IO_RESULT_OK || |
| num_bytes_read != sizeof(AvbABData)) { |
| avb_error("Error reading A/B metadata.\n"); |
| return AVB_IO_RESULT_ERROR_IO; |
| } |
| |
| if (!avb_ab_data_verify_and_byteswap(&serialized, data)) { |
| avb_error( |
| "Error validating A/B metadata from disk. " |
| "Resetting and writing new A/B metadata to disk.\n"); |
| avb_ab_data_init(data); |
| return avb_ab_data_write(ab_ops, data); |
| } |
| |
| return AVB_IO_RESULT_OK; |
| } |
| |
| AvbIOResult avb_ab_data_write(AvbABOps* ab_ops, const AvbABData* data) { |
| AvbOps* ops = ab_ops->ops; |
| AvbABData serialized; |
| AvbIOResult io_ret; |
| |
| avb_ab_data_update_crc_and_byteswap(data, &serialized); |
| io_ret = ops->write_to_partition(ops, |
| "misc", |
| AB_METADATA_MISC_PARTITION_OFFSET, |
| sizeof(AvbABData), |
| &serialized); |
| if (io_ret == AVB_IO_RESULT_ERROR_OOM) { |
| return AVB_IO_RESULT_ERROR_OOM; |
| } else if (io_ret != AVB_IO_RESULT_OK) { |
| avb_error("Error writing A/B metadata.\n"); |
| return AVB_IO_RESULT_ERROR_IO; |
| } |
| return AVB_IO_RESULT_OK; |
| } |
| |
| static bool slot_is_bootable(AvbABSlotData* slot) { |
| return slot->priority > 0 && |
| (slot->successful_boot || (slot->tries_remaining > 0)); |
| } |
| |
| static void 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. |
| */ |
| static void slot_normalize(AvbABSlotData* slot) { |
| if (slot->priority > 0) { |
| if (slot->tries_remaining == 0 && !slot->successful_boot) { |
| /* We've exhausted all tries -> unbootable. */ |
| 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. |
| */ |
| slot_set_unbootable(slot); |
| } |
| } else { |
| slot_set_unbootable(slot); |
| } |
| } |
| |
| static const char* slot_suffixes[2] = {"_a", "_b"}; |
| |
| /* Helper function to load metadata - returns AVB_IO_RESULT_OK on |
| * success, error code otherwise. |
| */ |
| static AvbIOResult 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). |
| */ |
| slot_normalize(&ab_data->slots[0]); |
| slot_normalize(&ab_data->slots[1]); |
| return AVB_IO_RESULT_OK; |
| } |
| |
| /* Writes A/B metadata to disk only if it has changed - returns |
| * AVB_IO_RESULT_OK on success, error code otherwise. |
| */ |
| static AvbIOResult 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; |
| } |
| |
| AvbABFlowResult avb_ab_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[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; |
| |
| 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; |
| } |
| |
| /* Validate all bootable slots. */ |
| for (n = 0; n < 2; n++) { |
| if (slot_is_bootable(&ab_data.slots[n])) { |
| AvbSlotVerifyResult verify_result; |
| bool set_slot_unbootable = false; |
| |
| verify_result = avb_slot_verify(ops, |
| requested_partitions, |
| slot_suffixes[n], |
| flags, |
| hashtree_error_mode, |
| &slot_data[n]); |
| 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. |
| */ |
| 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[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 { |
| 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[n], |
| " with result ", |
| avb_slot_verify_result_to_string(verify_result), |
| " - setting unbootable.\n", |
| NULL); |
| slot_set_unbootable(&ab_data.slots[n]); |
| } |
| } |
| } |
| |
| if (slot_is_bootable(&ab_data.slots[0]) && |
| slot_is_bootable(&ab_data.slots[1])) { |
| if (ab_data.slots[1].priority > ab_data.slots[0].priority) { |
| slot_index_to_boot = 1; |
| } else { |
| slot_index_to_boot = 0; |
| } |
| } else if (slot_is_bootable(&ab_data.slots[0])) { |
| slot_index_to_boot = 0; |
| } else if (slot_is_bootable(&ab_data.slots[1])) { |
| slot_index_to_boot = 1; |
| } else { |
| /* 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; |
| unsigned int other_slot_number; |
| AvbIOResult ret; |
| |
| avb_assert(slot_number < 2); |
| |
| ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
| if (ret != AVB_IO_RESULT_OK) { |
| goto out; |
| } |
| |
| /* Make requested slot top priority, unsuccessful, and with max tries. */ |
| ab_data.slots[slot_number].priority = AVB_AB_MAX_PRIORITY; |
| ab_data.slots[slot_number].tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
| ab_data.slots[slot_number].successful_boot = 0; |
| |
| /* Ensure other slot doesn't have as high a priority. */ |
| other_slot_number = 1 - slot_number; |
| if (ab_data.slots[other_slot_number].priority == AVB_AB_MAX_PRIORITY) { |
| ab_data.slots[other_slot_number].priority = AVB_AB_MAX_PRIORITY - 1; |
| } |
| |
| ret = AVB_IO_RESULT_OK; |
| |
| out: |
| if (ret == AVB_IO_RESULT_OK) { |
| ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
| } |
| return ret; |
| } |
| |
| AvbIOResult avb_ab_mark_slot_unbootable(AvbABOps* ab_ops, |
| unsigned int slot_number) { |
| AvbABData ab_data, ab_data_orig; |
| AvbIOResult ret; |
| |
| avb_assert(slot_number < 2); |
| |
| ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
| if (ret != AVB_IO_RESULT_OK) { |
| goto out; |
| } |
| |
| slot_set_unbootable(&ab_data.slots[slot_number]); |
| |
| ret = AVB_IO_RESULT_OK; |
| |
| out: |
| if (ret == AVB_IO_RESULT_OK) { |
| ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
| } |
| return ret; |
| } |
| |
| AvbIOResult avb_ab_mark_slot_successful(AvbABOps* ab_ops, |
| unsigned int slot_number) { |
| AvbABData ab_data, ab_data_orig; |
| AvbIOResult ret; |
| |
| avb_assert(slot_number < 2); |
| |
| ret = load_metadata(ab_ops, &ab_data, &ab_data_orig); |
| if (ret != AVB_IO_RESULT_OK) { |
| goto out; |
| } |
| |
| if (!slot_is_bootable(&ab_data.slots[slot_number])) { |
| avb_error("Cannot mark unbootable slot as successful.\n"); |
| ret = AVB_IO_RESULT_OK; |
| goto out; |
| } |
| |
| ab_data.slots[slot_number].tries_remaining = 0; |
| ab_data.slots[slot_number].successful_boot = 1; |
| |
| ret = AVB_IO_RESULT_OK; |
| |
| out: |
| if (ret == AVB_IO_RESULT_OK) { |
| ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig); |
| } |
| return ret; |
| } |
| |
| const char* avb_ab_flow_result_to_string(AvbABFlowResult result) { |
| const char* ret = NULL; |
| |
| switch (result) { |
| case AVB_AB_FLOW_RESULT_OK: |
| ret = "OK"; |
| break; |
| |
| case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR: |
| ret = "OK_WITH_VERIFICATION_ERROR"; |
| break; |
| |
| case AVB_AB_FLOW_RESULT_ERROR_OOM: |
| ret = "ERROR_OOM"; |
| break; |
| |
| case AVB_AB_FLOW_RESULT_ERROR_IO: |
| ret = "ERROR_IO"; |
| break; |
| |
| case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS: |
| ret = "ERROR_NO_BOOTABLE_SLOTS"; |
| break; |
| |
| case AVB_AB_FLOW_RESULT_ERROR_INVALID_ARGUMENT: |
| ret = "ERROR_INVALID_ARGUMENT"; |
| break; |
| /* Do not add a 'default:' case here because of -Wswitch. */ |
| } |
| |
| if (ret == NULL) { |
| avb_error("Unknown AvbABFlowResult value.\n"); |
| ret = "(unknown)"; |
| } |
| |
| return ret; |
| } |