From e916cf52a373f20d06d29987a720e9f7dfb1c131 Mon Sep 17 00:00:00 2001 From: Konstantin Kondrashov Date: Wed, 13 Feb 2019 17:32:23 +0800 Subject: [PATCH] bootloader: Add support of anti-rollback Added: * set a secure version in app/bootloader. * description anti-rollback to ota part * emulate the secure_version write and read operations * efuse_em partition. * a description about a rollback for native_ota_example. Closes: TW26335 --- components/app_update/esp_ota_ops.c | 123 +++++++++++++++--- components/app_update/include/esp_ota_ops.h | 24 ++++ components/app_update/test/test_switch_ota.c | 19 ++- components/bootloader/Kconfig.projbuild | 45 +++++++ .../include/bootloader_common.h | 12 ++ .../bootloader_support/include/esp_efuse.h | 37 ++++++ .../src/bootloader_common.c | 27 ++-- .../src/bootloader_utility.c | 98 ++++++++++++-- components/bootloader_support/src/efuse.c | 121 +++++++++++++++++ components/esp32/cpu_start.c | 8 +- components/esp32/esp_err_to_name.c | 16 +++ .../esp32/include/esp_flash_data_types.h | 1 + components/partition_table/CMakeLists.txt | 9 ++ components/partition_table/Makefile.projbuild | 9 +- components/partition_table/gen_esp32part.py | 1 + .../partition_table/project_include.cmake | 3 + components/spi_flash/include/esp_partition.h | 1 + docs/en/api-reference/system/ota.rst | 68 ++++++++++ examples/system/ota/README.md | 19 +++ .../native_ota_example/main/Kconfig.projbuild | 11 ++ .../main/native_ota_example.c | 22 +++- .../system/ota/native_ota_example/version.txt | 1 + 22 files changed, 635 insertions(+), 40 deletions(-) create mode 100644 examples/system/ota/native_ota_example/version.txt diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index 22b13f5301..02eab8eb6b 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -40,6 +40,8 @@ #include "bootloader_common.h" #include "sys/param.h" #include "esp_system.h" +#include "esp_efuse.h" + #define SUB_TYPE_ID(i) (i & 0x0F) @@ -144,10 +146,21 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp return ESP_ERR_INVALID_ARG; } - if (partition == esp_ota_get_running_partition()) { + const esp_partition_t* running_partition = esp_ota_get_running_partition(); + if (partition == running_partition) { return ESP_ERR_OTA_PARTITION_CONFLICT; } +#ifdef CONFIG_APP_ROLLBACK_ENABLE + esp_ota_img_states_t ota_state_running_part; + if (esp_ota_get_state_partition(running_partition, &ota_state_running_part) == ESP_OK) { + if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) { + ESP_LOGE(TAG, "Running app has not confirmed state (ESP_OTA_IMG_PENDING_VERIFY)"); + return ESP_ERR_OTA_ROLLBACK_INVALID_STATE; + } + } +#endif + // If input image size is 0 or OTA_SIZE_UNKNOWN, erase entire partition if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) { ret = esp_partition_erase_range(partition, 0, partition->size); @@ -388,6 +401,22 @@ esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition) return ESP_ERR_NOT_FOUND; } } else { +#ifdef CONFIG_APP_ANTI_ROLLBACK + esp_app_desc_t partition_app_desc; + esp_err_t err = esp_ota_get_partition_description(partition, &partition_app_desc); + if (err != ESP_OK) { + return err; + } + + if (esp_efuse_check_secure_version(partition_app_desc.secure_version) == false) { + ESP_LOGE(TAG, "This a new partition can not be booted due to a secure version is lower than stored in efuse. Partition will be erased."); + esp_err_t err = esp_partition_erase_range(partition, 0, partition->size); + if (err != ESP_OK) { + return err; + } + return ESP_ERR_OTA_SMALL_SEC_VER; + } +#endif return esp_rewrite_ota_data(partition->subtype); } } else { @@ -560,6 +589,69 @@ esp_err_t esp_ota_get_partition_description(const esp_partition_t *partition, es return ESP_OK; } +#ifdef CONFIG_APP_ANTI_ROLLBACK +static esp_err_t esp_ota_set_anti_rollback(void) { + const esp_app_desc_t *app_desc = esp_ota_get_app_description(); + return esp_efuse_update_secure_version(app_desc->secure_version); +} +#endif + +// Checks applications on the slots which can be booted in case of rollback. +// Returns true if the slots have at least one app (except the running app). +bool esp_ota_check_rollback_is_possible(void) +{ + esp_ota_select_entry_t otadata[2]; + if (read_otadata(otadata) == NULL) { + return false; + } + + int ota_app_count = get_ota_partition_count(); + if (ota_app_count == 0) { + return false; + } + + bool valid_otadata[2]; + valid_otadata[0] = bootloader_common_ota_select_valid(&otadata[0]); + valid_otadata[1] = bootloader_common_ota_select_valid(&otadata[1]); + + int active_ota = bootloader_common_select_otadata(otadata, valid_otadata, true); + if (active_ota == -1) { + return false; + } + int last_active_ota = (~active_ota)&1; + + const esp_partition_t *partition = NULL; +#ifndef CONFIG_APP_ANTI_ROLLBACK + if (valid_otadata[last_active_ota] == false) { + partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); + if (partition != NULL) { + if(image_validate(partition, ESP_IMAGE_VERIFY_SILENT) == ESP_OK) { + return true; + } + } + } +#endif + + if (valid_otadata[last_active_ota] == true) { + int slot = (otadata[last_active_ota].ota_seq - 1) % ota_app_count; + partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN + slot, NULL); + if (partition != NULL) { + if(image_validate(partition, ESP_IMAGE_VERIFY_SILENT) == ESP_OK) { +#ifdef CONFIG_APP_ANTI_ROLLBACK + esp_app_desc_t app_desc; + if (esp_ota_get_partition_description(partition, &app_desc) == ESP_OK && + esp_efuse_check_secure_version(app_desc.secure_version) == true) { + return true; + } +#else + return true; +#endif + } + } + } + return false; +} + // if valid == false - will done rollback with reboot. After reboot will boot previous OTA[x] or Factory partition. // if valid == true - it confirm that current OTA[x] is workable. Reboot will not happen. static esp_err_t esp_ota_current_ota_is_workable(bool valid) @@ -575,8 +667,18 @@ static esp_err_t esp_ota_current_ota_is_workable(bool valid) if (valid == true && otadata[active_otadata].ota_state != ESP_OTA_IMG_VALID) { otadata[active_otadata].ota_state = ESP_OTA_IMG_VALID; ESP_LOGD(TAG, "OTA[current] partition is marked as VALID"); - return rewrite_ota_seq(otadata, otadata[active_otadata].ota_seq, active_otadata, otadata_partition); + esp_err_t err = rewrite_ota_seq(otadata, otadata[active_otadata].ota_seq, active_otadata, otadata_partition); +#ifdef CONFIG_APP_ANTI_ROLLBACK + if (err == ESP_OK) { + return esp_ota_set_anti_rollback(); + } +#endif + return err; } else if (valid == false) { + if (esp_ota_check_rollback_is_possible() == false) { + ESP_LOGE(TAG, "Rollback is not possible, do not have any suitable apps in slots"); + return ESP_ERR_OTA_ROLLBACK_FAILED; + } ESP_LOGD(TAG, "OTA[current] partition is marked as INVALID"); otadata[active_otadata].ota_state = ESP_OTA_IMG_INVALID; esp_err_t err = rewrite_ota_seq(otadata, otadata[active_otadata].ota_seq, active_otadata, otadata_partition); @@ -612,26 +714,11 @@ static bool check_invalid_otadata (const esp_ota_select_entry_t *s) { static int get_last_invalid_otadata(const esp_ota_select_entry_t *two_otadata) { - int num_invalid_otadata = -1; bool invalid_otadata[2]; invalid_otadata[0] = check_invalid_otadata(&two_otadata[0]); invalid_otadata[1] = check_invalid_otadata(&two_otadata[1]); - if (invalid_otadata[0] == true && invalid_otadata[1] == true) { - if (MIN(two_otadata[0].ota_seq, two_otadata[1].ota_seq) == two_otadata[0].ota_seq) { - num_invalid_otadata = 0; - } else { - num_invalid_otadata = 1; - } - } else { - for (int i = 0; i < 2; ++i) { - if(invalid_otadata[i]) { - num_invalid_otadata = i; - break; - } - } - } - + int num_invalid_otadata = bootloader_common_select_otadata(two_otadata, invalid_otadata, false); ESP_LOGD(TAG, "Invalid otadata[%d]", num_invalid_otadata); return num_invalid_otadata; } diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index ff07cec5a0..94a34b4f98 100644 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -34,6 +34,10 @@ extern "C" #define ESP_ERR_OTA_PARTITION_CONFLICT (ESP_ERR_OTA_BASE + 0x01) /*!< Error if request was to write or erase the current running partition */ #define ESP_ERR_OTA_SELECT_INFO_INVALID (ESP_ERR_OTA_BASE + 0x02) /*!< Error if OTA data partition contains invalid content */ #define ESP_ERR_OTA_VALIDATE_FAILED (ESP_ERR_OTA_BASE + 0x03) /*!< Error if OTA app image is invalid */ +#define ESP_ERR_OTA_SMALL_SEC_VER (ESP_ERR_OTA_BASE + 0x04) /*!< Error if the firmware has a secure version less than the running firmware. */ +#define ESP_ERR_OTA_ROLLBACK_FAILED (ESP_ERR_OTA_BASE + 0x05) /*!< Error if flash does not have valid firmware in passive partition and hence rollback is not possible */ +#define ESP_ERR_OTA_ROLLBACK_INVALID_STATE (ESP_ERR_OTA_BASE + 0x06) /*!< Error if current active firmware is still marked in pending validation state (ESP_OTA_IMG_PENDING_VERIFY), essentially first boot of firmware image post upgrade and hence firmware upgrade is not possible */ + /** * @brief Opaque handle for an application OTA update @@ -62,6 +66,10 @@ const esp_app_desc_t *esp_ota_get_app_description(void); * On success, this function allocates memory that remains in use * until esp_ota_end() is called with the returned handle. * + * Note: If the rollback option is enabled and the running application has the ESP_OTA_IMG_PENDING_VERIFY state then + * it will lead to the ESP_ERR_OTA_ROLLBACK_INVALID_STATE error. Confirm the running app before to run download a new app, + * use esp_ota_mark_app_valid_cancel_rollback() function for it (this should be done as early as possible when you first download a new application). + * * @param partition Pointer to info for partition which will receive the OTA update. Required. * @param image_size Size of new OTA app image. Partition will be erased in order to receive this size of image. If 0 or OTA_SIZE_UNKNOWN, the entire partition is erased. * @param out_handle On success, returns a handle which should be used for subsequent esp_ota_write() and esp_ota_end() calls. @@ -75,6 +83,7 @@ const esp_app_desc_t *esp_ota_get_app_description(void); * - ESP_ERR_OTA_SELECT_INFO_INVALID: The OTA data partition contains invalid data. * - ESP_ERR_INVALID_SIZE: Partition doesn't fit in configured flash size. * - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed. + * - ESP_ERR_OTA_ROLLBACK_INVALID_STATE: If the running app has not confirmed state. Before performing an update, the application must be valid. */ esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp_ota_handle_t* out_handle); @@ -208,8 +217,11 @@ esp_err_t esp_ota_mark_app_valid_cancel_rollback(); * @brief This function is called to roll back to the previously workable app with reboot. * * If rollback is successful then device will reset else API will return with error code. + * Checks applications on a flash drive that can be booted in case of rollback. + * If the flash does not have at least one app (except the running app) then rollback is not possible. * @return * - ESP_FAIL: if not successful. + * - ESP_ERR_OTA_ROLLBACK_FAILED: The rollback is not possible due to flash does not have any apps. */ esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot(); @@ -242,6 +254,18 @@ esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_ */ esp_err_t esp_ota_erase_last_boot_app_partition(void); +/** + * @brief Checks applications on the slots which can be booted in case of rollback. + * + * These applications should be valid (marked in otadata as not UNDEFINED, INVALID or ABORTED and crc is good) and be able booted, + * and secure_version of app >= secure_version of efuse (if anti-rollback is enabled). + * + * @return + * - True: Returns true if the slots have at least one app (except the running app). + * - False: The rollback is not possible. + */ +bool esp_ota_check_rollback_is_possible(void); + #ifdef __cplusplus } #endif diff --git a/components/app_update/test/test_switch_ota.c b/components/app_update/test/test_switch_ota.c index a3cb113c6a..f2adb2f799 100644 --- a/components/app_update/test/test_switch_ota.c +++ b/components/app_update/test/test_switch_ota.c @@ -237,6 +237,12 @@ static void reset_output_pin(uint32_t num_pin) } #endif +static void mark_app_valid(void) +{ +#ifdef CONFIG_APP_ROLLBACK_ENABLE + TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback()); +#endif +} /* @brief Checks and prepares the partition so that the factory app is launched after that. */ @@ -262,15 +268,18 @@ static void test_flow1(void) case 3: ESP_LOGI(TAG, "OTA0"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); + mark_app_valid(); copy_current_app_to_next_part_and_reboot(cur_app); break; case 4: ESP_LOGI(TAG, "OTA1"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, cur_app->subtype); + mark_app_valid(); copy_current_app_to_next_part_and_reboot(cur_app); break; case 5: ESP_LOGI(TAG, "OTA0"); + mark_app_valid(); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); erase_ota_data(); break; @@ -302,6 +311,7 @@ static void test_flow2(void) case 3: ESP_LOGI(TAG, "OTA0"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); + mark_app_valid(); copy_current_app_to_next_part(cur_app, get_next_update_partition()); corrupt_ota_data(CORR_CRC_1_SECTOR_OTA_DATA); reboot_as_deep_sleep(); @@ -338,11 +348,13 @@ static void test_flow3(void) case 3: ESP_LOGI(TAG, "OTA0"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); + mark_app_valid(); copy_current_app_to_next_part_and_reboot(cur_app); break; case 4: ESP_LOGI(TAG, "OTA1"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, cur_app->subtype); + mark_app_valid(); copy_current_app_to_next_part(cur_app, get_next_update_partition()); corrupt_ota_data(CORR_CRC_2_SECTOR_OTA_DATA); reboot_as_deep_sleep(); @@ -394,7 +406,7 @@ static void test_flow4(void) case 3: ESP_LOGI(TAG, "OTA0"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); - + mark_app_valid(); TEST_ESP_OK(nvs_flash_init()); TEST_ESP_OK(nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_get_i32(handle, "boot_count", &boot_count_nvs)); @@ -523,7 +535,7 @@ static void test_rollback1(void) #else TEST_ASSERT_EQUAL(ESP_OTA_IMG_PENDING_VERIFY, ota_state); #endif - esp_ota_mark_app_valid_cancel_rollback(); + TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback()); TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state)); TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state); reboot_as_deep_sleep(); @@ -602,7 +614,7 @@ static void test_rollback2(void) #else TEST_ASSERT_EQUAL(ESP_OTA_IMG_PENDING_VERIFY, ota_state); #endif - esp_ota_mark_app_valid_cancel_rollback(); + TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback()); TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition()); TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state)); TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state); @@ -683,6 +695,7 @@ static void test_erase_last_app_flow(void) case 3: ESP_LOGI(TAG, "OTA0"); TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); + mark_app_valid(); app_update(); reboot_as_deep_sleep(); break; diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index bdf84fa7b9..a51ff6f025 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -173,6 +173,51 @@ menu "Bootloader config" If the app is working, then it is marked as valid. Otherwise, it is marked as not valid and rolls back to the previous working app. A reboot is performed, and the app is booted before the software update. Note: If during the first boot a new app the power goes out or the WDT works, then roll back will happen. + Rollback is possible only between the apps with the same security versions. + + config APP_ANTI_ROLLBACK + bool "Enable app anti-rollback support" + depends on APP_ROLLBACK_ENABLE + default n + help + This option prevents rollback to previous firmware/application image with lower security version. + + config APP_SECURE_VERSION + int "eFuse secure version of app" + depends on APP_ANTI_ROLLBACK + default 0 + help + The secure version is the sequence number stored in the header of each firmware. + The security version is set in the bootloader, version is recorded in the eFuse field + as the number of set ones. The allocated number of bits in the efuse field + for storing the security version is limited (see APP_SECURE_VERSION_SIZE_EFUSE_FIELD option). + + Bootloader: When bootloader selects an app to boot, an app is selected that has + a security version greater or equal that recorded in eFuse field. + The app is booted with a higher (or equal) secure version. + + The security version is worth increasing if in previous versions there is + a significant vulnerability and their use is not acceptable. + + Your partition table should has a scheme with ota_0 + ota_1 (without factory). + + config APP_SECURE_VERSION_SIZE_EFUSE_FIELD + int "Size of the efuse secure version field" + depends on APP_ANTI_ROLLBACK + range 1 32 + default 32 + help + The size of the efuse secure version field. Its length is limited to 32 bits. + This determines how many times the security version can be increased. + + config EFUSE_SECURE_VERSION_EMULATE + bool "Emulate operations with efuse secure version(only test)" + default n + depends on APP_ANTI_ROLLBACK + help + This option allow emulate read/write operations with efuse secure version. + It allow to test anti-rollback implemention without permanent write eFuse bits. + In partition table should be exist this partition `emul_efuse, data, 5, , 0x2000`. endmenu # Bootloader diff --git a/components/bootloader_support/include/bootloader_common.h b/components/bootloader_support/include/bootloader_common.h index f57b4b8a9a..d18b97ceb3 100644 --- a/components/bootloader_support/include/bootloader_common.h +++ b/components/bootloader_support/include/bootloader_common.h @@ -111,6 +111,18 @@ esp_err_t bootloader_common_get_sha256_of_partition(uint32_t address, uint32_t s */ int bootloader_common_get_active_otadata(esp_ota_select_entry_t *two_otadata); +/** + * @brief Returns the number of active otadata. + * + * @param[in] two_otadata Pointer on array from two otadata structures. + * @param[in] valid_two_otadata Pointer on array from two bools. True means select. + * @param[in] max True - will select the maximum ota_seq number, otherwise the minimum. + * + * @return The number of active otadata (0 or 1). + * - -1: If it does not have active otadata. + */ +int bootloader_common_select_otadata(const esp_ota_select_entry_t *two_otadata, bool *valid_two_otadata, bool max); + /** * @brief Returns esp_app_desc structure for app partition. This structure includes app version. * diff --git a/components/bootloader_support/include/esp_efuse.h b/components/bootloader_support/include/esp_efuse.h index c094a6ab44..3f446cb939 100644 --- a/components/bootloader_support/include/esp_efuse.h +++ b/components/bootloader_support/include/esp_efuse.h @@ -16,6 +16,7 @@ #include "soc/efuse_reg.h" #include "esp_err.h" +#include "stdbool.h" #ifdef __cplusplus extern "C" { @@ -91,6 +92,42 @@ esp_err_t esp_efuse_apply_34_encoding(const uint8_t *in_bytes, uint32_t *out_wor */ void esp_efuse_write_random_key(uint32_t blk_wdata0_reg); +/* @brief Return secure_version from efuse field. + * @return Secure version from efuse field + */ +uint32_t esp_efuse_read_secure_version(); + +/* @brief Check secure_version from app and secure_version and from efuse field. + * + * @param secure_version Secure version from app. + * @return + * - True: If version of app is equal or more then secure_version from efuse. + */ +bool esp_efuse_check_secure_version(uint32_t secure_version); + +/* @brief Write efuse field by secure_version value. + * + * Update the secure_version value is available if the coding scheme is None. + * Note: Do not use this function in your applications. This function is called as part of the other API. + * + * @param[in] secure_version Secure version from app. + * @return + * - ESP_OK: Successful. + * - ESP_FAIL: secure version of app cannot be set to efuse field. + * - ESP_ERR_NOT_SUPPORTED: Anti rollback is not supported with the 3/4 and Repeat coding scheme. + */ +esp_err_t esp_efuse_update_secure_version(uint32_t secure_version); + +/* @brief Initializes variables: offset and size to simulate the work of an eFuse. + * + * Note: To simulate the work of an eFuse need to set CONFIG_EFUSE_SECURE_VERSION_EMULATE option + * and to add in the partition.csv file a line `efuse_em, data, efuse, , 0x2000,`. + * + * @param[in] offset The starting address of the partition where the eFuse data will be located. + * @param[in] size The size of the partition. + */ +void esp_efuse_init(uint32_t offset, uint32_t size); + #ifdef __cplusplus } #endif diff --git a/components/bootloader_support/src/bootloader_common.c b/components/bootloader_support/src/bootloader_common.c index 7486164164..29e591d1af 100644 --- a/components/bootloader_support/src/bootloader_common.c +++ b/components/bootloader_support/src/bootloader_common.c @@ -199,15 +199,15 @@ esp_err_t bootloader_common_get_sha256_of_partition (uint32_t address, uint32_t return ESP_OK; } -int bootloader_common_get_active_otadata(esp_ota_select_entry_t *two_otadata) +int bootloader_common_select_otadata(const esp_ota_select_entry_t *two_otadata, bool *valid_two_otadata, bool max) { + if (two_otadata == NULL || valid_two_otadata == NULL) { + return -1; + } int active_otadata = -1; - - bool valid_otadata[2]; - valid_otadata[0] = bootloader_common_ota_select_valid(&two_otadata[0]); - valid_otadata[1] = bootloader_common_ota_select_valid(&two_otadata[1]); - if (valid_otadata[0] && valid_otadata[1]) { - if (MAX(two_otadata[0].ota_seq, two_otadata[1].ota_seq) == two_otadata[0].ota_seq) { + if (valid_two_otadata[0] && valid_two_otadata[1]) { + int condition = (max == true) ? MAX(two_otadata[0].ota_seq, two_otadata[1].ota_seq) : MIN(two_otadata[0].ota_seq, two_otadata[1].ota_seq); + if (condition == two_otadata[0].ota_seq) { active_otadata = 0; } else { active_otadata = 1; @@ -215,7 +215,7 @@ int bootloader_common_get_active_otadata(esp_ota_select_entry_t *two_otadata) ESP_LOGD(TAG, "Both OTA copies are valid"); } else { for (int i = 0; i < 2; ++i) { - if (valid_otadata[i]) { + if (valid_two_otadata[i]) { active_otadata = i; ESP_LOGD(TAG, "Only otadata[%d] is valid", i); break; @@ -225,6 +225,17 @@ int bootloader_common_get_active_otadata(esp_ota_select_entry_t *two_otadata) return active_otadata; } +int bootloader_common_get_active_otadata(esp_ota_select_entry_t *two_otadata) +{ + if (two_otadata == NULL) { + return -1; + } + bool valid_two_otadata[2]; + valid_two_otadata[0] = bootloader_common_ota_select_valid(&two_otadata[0]); + valid_two_otadata[1] = bootloader_common_ota_select_valid(&two_otadata[1]); + return bootloader_common_select_otadata(two_otadata, valid_two_otadata, true); +} + esp_err_t bootloader_common_get_partition_description(const esp_partition_pos_t *partition, esp_app_desc_t *app_desc) { if (partition == NULL || app_desc == NULL || partition->offset == 0) { diff --git a/components/bootloader_support/src/bootloader_utility.c b/components/bootloader_support/src/bootloader_utility.c index 80cac89fd6..720294fa48 100644 --- a/components/bootloader_support/src/bootloader_utility.c +++ b/components/bootloader_support/src/bootloader_utility.c @@ -51,6 +51,7 @@ #include "bootloader_common.h" #include "bootloader_utility.h" #include "bootloader_sha.h" +#include "esp_efuse.h" static const char* TAG = "boot"; @@ -166,6 +167,12 @@ bool bootloader_utility_load_partition_table(bootloader_state_t* bs) case PART_SUBTYPE_DATA_NVS_KEYS: partition_usage = "NVS keys"; break; + case PART_SUBTYPE_DATA_EFUSE_EM: + partition_usage = "efuse"; +#ifdef CONFIG_EFUSE_SECURE_VERSION_EMULATE + esp_efuse_init(partition->pos.offset, partition->pos.size); +#endif + break; default: partition_usage = "Unknown data"; break; @@ -234,6 +241,54 @@ static esp_err_t write_otadata(esp_ota_select_entry_t *otadata, uint32_t offset, return err; } +static bool check_anti_rollback(const esp_partition_pos_t *partition) +{ +#ifdef CONFIG_APP_ANTI_ROLLBACK + esp_app_desc_t app_desc; + esp_err_t err = bootloader_common_get_partition_description(partition, &app_desc); + return err == ESP_OK && esp_efuse_check_secure_version(app_desc.secure_version) == true; +#else + return true; +#endif +} + +#ifdef CONFIG_APP_ANTI_ROLLBACK +static void update_anti_rollback(const esp_partition_pos_t *partition) +{ + esp_app_desc_t app_desc; + esp_err_t err = bootloader_common_get_partition_description(partition, &app_desc); + if (err == ESP_OK) { + esp_efuse_update_secure_version(app_desc.secure_version); + } +} + +static int get_active_otadata_with_check_anti_rollback(const bootloader_state_t *bs, esp_ota_select_entry_t *two_otadata) +{ + uint32_t ota_seq; + uint32_t ota_slot; + bool valid_otadata[2]; + + valid_otadata[0] = bootloader_common_ota_select_valid(&two_otadata[0]); + valid_otadata[1] = bootloader_common_ota_select_valid(&two_otadata[1]); + + bool sec_ver_valid_otadata[2] = { 0 }; + for (int i = 0; i < 2; ++i) { + if (valid_otadata[i] == true) { + ota_seq = two_otadata[i].ota_seq - 1; // Raw OTA sequence number. May be more than # of OTA slots + ota_slot = ota_seq % bs->app_count; // Actual OTA partition selection + if (check_anti_rollback(&bs->ota[ota_slot]) == false) { + // invalid. This otadata[i] will not be selected as active. + ESP_LOGD(TAG, "OTA slot %d has an app with secure_version, this version is smaller than in the device. This OTA slot will not be selected.", ota_slot); + } else { + sec_ver_valid_otadata[i] = true; + } + } + } + + return bootloader_common_select_otadata(two_otadata, sec_ver_valid_otadata, true); +} +#endif + int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs) { esp_ota_select_entry_t otadata[2]; @@ -250,6 +305,7 @@ int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs) ESP_LOGD(TAG, "otadata[0]: sequence values 0x%08x", otadata[0].ota_seq); ESP_LOGD(TAG, "otadata[1]: sequence values 0x%08x", otadata[1].ota_seq); + #ifdef CONFIG_APP_ROLLBACK_ENABLE bool write_encrypted = esp_flash_encryption_enabled(); for (int i = 0; i < 2; ++i) { @@ -260,6 +316,8 @@ int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs) } } #endif + +#ifndef CONFIG_APP_ANTI_ROLLBACK if ((bootloader_common_ota_select_invalid(&otadata[0]) && bootloader_common_ota_select_invalid(&otadata[1])) || bs->app_count == 0) { @@ -280,18 +338,38 @@ int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs) } } else { int active_otadata = bootloader_common_get_active_otadata(otadata); +#else + ESP_LOGI(TAG, "Enabled a check secure version of app for anti rollback"); + ESP_LOGI(TAG, "Secure version (from eFuse) = %d", esp_efuse_read_secure_version()); + // When CONFIG_APP_ANTI_ROLLBACK is enabled factory partition should not be in partition table, only two ota_app are there. + if ((otadata[0].ota_seq == UINT32_MAX || otadata[0].crc != bootloader_common_ota_select_crc(&otadata[0])) && + (otadata[1].ota_seq == UINT32_MAX || otadata[1].crc != bootloader_common_ota_select_crc(&otadata[1]))) { + ESP_LOGI(TAG, "otadata[0..1] in initial state"); + // both otadata are initial(0xFFFFFFFF) or incorrect crc. + // will set correct ota_seq. + ota_has_initial_contents = true; + } else { + int active_otadata = get_active_otadata_with_check_anti_rollback(bs, otadata); +#endif if (active_otadata != -1) { ESP_LOGD(TAG, "Active otadata[%d]", active_otadata); + uint32_t ota_seq = otadata[active_otadata].ota_seq - 1; // Raw OTA sequence number. May be more than # of OTA slots + boot_index = ota_seq % bs->app_count; // Actual OTA partition selection + ESP_LOGD(TAG, "Mapping seq %d -> OTA slot %d", ota_seq, boot_index); #ifdef CONFIG_APP_ROLLBACK_ENABLE if (otadata[active_otadata].ota_state == ESP_OTA_IMG_NEW) { ESP_LOGD(TAG, "otadata[%d] is selected as new and marked PENDING_VERIFY state", active_otadata); otadata[active_otadata].ota_state = ESP_OTA_IMG_PENDING_VERIFY; write_otadata(&otadata[active_otadata], bs->ota_info.offset + FLASH_SECTOR_SIZE * active_otadata, write_encrypted); } -#endif - uint32_t ota_seq = otadata[active_otadata].ota_seq - 1; // Raw OTA sequence number. May be more than # of OTA slots - boot_index = ota_seq % bs->app_count; // Actual OTA partition selection - ESP_LOGD(TAG, "Mapping seq %d -> OTA slot %d", ota_seq, boot_index); +#endif // CONFIG_APP_ROLLBACK_ENABLE + +#ifdef CONFIG_APP_ANTI_ROLLBACK + if(otadata[active_otadata].ota_state == ESP_OTA_IMG_VALID) { + update_anti_rollback(&bs->ota[boot_index]); + } +#endif // CONFIG_APP_ANTI_ROLLBACK + } else if (bs->factory.offset != 0) { ESP_LOGE(TAG, "ota data partition invalid, falling back to factory"); boot_index = FACTORY_INDEX; @@ -326,15 +404,19 @@ static bool try_load_partition(const esp_partition_pos_t *partition, esp_image_m // otadata has initial content(0xFFFFFFFF), then set actual ota_seq. static void set_actual_ota_seq(const bootloader_state_t *bs, int index) { - if (index >= 0 && ota_has_initial_contents == true) { + if (index > FACTORY_INDEX && ota_has_initial_contents == true) { esp_ota_select_entry_t otadata; + memset(&otadata, 0xFF, sizeof(otadata)); otadata.ota_seq = index + 1; otadata.ota_state = ESP_OTA_IMG_VALID; otadata.crc = bootloader_common_ota_select_crc(&otadata); bool write_encrypted = esp_flash_encryption_enabled(); write_otadata(&otadata, bs->ota_info.offset + FLASH_SECTOR_SIZE * 0, write_encrypted); - ESP_LOGD(TAG, "Set actual ota_seq=%d in otadata[0]", otadata.ota_seq); + ESP_LOGI(TAG, "Set actual ota_seq=%d in otadata[0]", otadata.ota_seq); +#ifdef CONFIG_APP_ANTI_ROLLBACK + update_anti_rollback(&bs->ota[index]); +#endif } } @@ -362,7 +444,7 @@ void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_ continue; } ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size); - if (try_load_partition(&part, &image_data)) { + if (check_anti_rollback(&part) && try_load_partition(&part, &image_data)) { set_actual_ota_seq(bs, index); load_image(&image_data); } @@ -376,7 +458,7 @@ void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_ continue; } ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size); - if (try_load_partition(&part, &image_data)) { + if (check_anti_rollback(&part) && try_load_partition(&part, &image_data)) { set_actual_ota_seq(bs, index); load_image(&image_data); } diff --git a/components/bootloader_support/src/efuse.c b/components/bootloader_support/src/efuse.c index 35d0a64e19..637d6e4947 100644 --- a/components/bootloader_support/src/efuse.c +++ b/components/bootloader_support/src/efuse.c @@ -112,3 +112,124 @@ void esp_efuse_write_random_key(uint32_t blk_wdata0_reg) bzero(buf, sizeof(buf)); bzero(raw, sizeof(raw)); } + + +#ifdef CONFIG_EFUSE_SECURE_VERSION_EMULATE + +#include "bootloader_flash.h" +#include "esp_flash_encrypt.h" + +static uint32_t esp_efuse_flash_offset = 0; +static uint32_t esp_efuse_flash_size = 0; +void esp_efuse_init(uint32_t offset, uint32_t size) +{ + esp_efuse_flash_offset = offset; + esp_efuse_flash_size = size; +} + +static uint32_t emulate_secure_version_read() +{ + uint32_t secure_version; + uint32_t offset = esp_efuse_flash_offset; + if (offset == 0) { + ESP_LOGE(TAG, "emulate secure_version can not be used"); + return 0; + } + const uint32_t *efuse_place_in_flash = bootloader_mmap(offset, esp_efuse_flash_size); + if (!efuse_place_in_flash) { + ESP_LOGE(TAG, "secure_version can not be read from (0x%x, 0x%x) flash", offset, esp_efuse_flash_size); + return 0; + } + memcpy(&secure_version, efuse_place_in_flash, sizeof(uint32_t)); + bootloader_munmap(efuse_place_in_flash); + secure_version = ~secure_version; + ESP_LOGV(TAG, "Read 0x%08x secure_version from flash", secure_version); + return secure_version; +} + +static void emulate_secure_version_write(uint32_t secure_version) +{ + uint32_t secure_version_wr = ~secure_version; + uint32_t offset = esp_efuse_flash_offset; + if (offset == 0) { + ESP_LOGE(TAG, "emulate secure_version can not be used"); + return; + } + esp_err_t err = bootloader_flash_write(offset, &secure_version_wr, sizeof(secure_version_wr), false); + if (err != ESP_OK) { + ESP_LOGE(TAG, "secure_version can not be written to flash. err = 0x%x", err); + } + ESP_LOGV(TAG, "Write 0x%08x secure_version into flash", secure_version); +} +#endif + +// This efuse register is used whole for secure version (32 bits). +#define EFUSE_BLK_RD_ANTI_ROLLBACK EFUSE_BLK3_RDATA4_REG +#define EFUSE_BLK_WR_ANTI_ROLLBACK EFUSE_BLK3_WDATA4_REG + +uint32_t esp_efuse_read_secure_version() +{ +#ifdef CONFIG_APP_ANTI_ROLLBACK + uint32_t secure_version; + +#ifdef CONFIG_EFUSE_SECURE_VERSION_EMULATE + secure_version = emulate_secure_version_read(); +#else + secure_version = REG_READ(EFUSE_BLK_RD_ANTI_ROLLBACK); +#endif // CONFIG_EFUSE_SECURE_VERSION_EMULATE + + return __builtin_popcount(secure_version & ((1ULL << CONFIG_APP_SECURE_VERSION_SIZE_EFUSE_FIELD) - 1)); +#else + return 0; +#endif +} + +#ifdef CONFIG_APP_ANTI_ROLLBACK +static void write_anti_rollback(uint32_t new_bits) +{ +#ifdef CONFIG_EFUSE_SECURE_VERSION_EMULATE + emulate_secure_version_write(new_bits); +#else + esp_efuse_reset(); + REG_WRITE(EFUSE_BLK_WR_ANTI_ROLLBACK, new_bits); + esp_efuse_burn_new_values(); +#endif +} +#endif + +bool esp_efuse_check_secure_version(uint32_t secure_version) +{ + uint32_t sec_ver_hw = esp_efuse_read_secure_version(); + return secure_version >= sec_ver_hw; +} + +esp_err_t esp_efuse_update_secure_version(uint32_t secure_version) +{ +#ifdef CONFIG_APP_ANTI_ROLLBACK + if (CONFIG_APP_SECURE_VERSION_SIZE_EFUSE_FIELD < secure_version) { + ESP_LOGE(TAG, "Max secure version is %d. Given %d version can not be written.", CONFIG_APP_SECURE_VERSION_SIZE_EFUSE_FIELD, secure_version); + return ESP_ERR_INVALID_ARG; + } +#ifndef CONFIG_EFUSE_SECURE_VERSION_EMULATE + uint32_t coding_scheme = REG_READ(EFUSE_BLK0_RDATA6_REG) & EFUSE_CODING_SCHEME_M; + if (coding_scheme != EFUSE_CODING_SCHEME_VAL_NONE) { + ESP_LOGE(TAG, "Anti rollback is not supported with a 3/4 coding scheme."); + return ESP_ERR_NOT_SUPPORTED; + } +#endif + uint32_t sec_ver_hw = esp_efuse_read_secure_version(); + // If secure_version is the same as in eFuse field than it is ok just go out. + if (sec_ver_hw < secure_version) { + uint32_t num_bit_hw = (1ULL << sec_ver_hw) - 1; + uint32_t num_bit_app = (1ULL << secure_version) - 1; + // Repeated programming of programmed bits is strictly forbidden + uint32_t new_bits = num_bit_app - num_bit_hw; // get only new bits + write_anti_rollback(new_bits); + ESP_LOGI(TAG, "Anti-rollback is set. eFuse field is updated(%d).", secure_version); + } else if (sec_ver_hw > secure_version) { + ESP_LOGE(TAG, "Anti-rollback is not set. secure_version of app is lower that eFuse field(%d).", sec_ver_hw); + return ESP_FAIL; + } +#endif + return ESP_OK; +} diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 7dacf38af1..d2f7a63177 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -186,7 +186,7 @@ void IRAM_ATTR call_start_cpu0() ESP_EARLY_LOGI(TAG, "App version: %s", app_desc->version); #endif #ifdef CONFIG_APP_SECURE_VERSION - ESP_EARLY_LOGI(TAG, "Secure version: %x", app_desc->secure_version); + ESP_EARLY_LOGI(TAG, "Secure version: %d", app_desc->secure_version); #endif #ifdef CONFIG_APP_COMPILE_TIME_DATE ESP_EARLY_LOGI(TAG, "Compile time: %s", app_desc->time); @@ -514,6 +514,12 @@ static void main_task(void* args) // Now that the application is about to start, disable boot watchdog #ifndef CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE rtc_wdt_disable(); +#endif +#ifdef CONFIG_EFUSE_SECURE_VERSION_EMULATE + const esp_partition_t *efuse_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_EFUSE_EM, NULL); + if (efuse_partition) { + esp_efuse_init(efuse_partition->address, efuse_partition->size); + } #endif app_main(); vTaskDelete(NULL); diff --git a/components/esp32/esp_err_to_name.c b/components/esp32/esp_err_to_name.c index dd1b81614c..ba09a439f8 100644 --- a/components/esp32/esp_err_to_name.c +++ b/components/esp32/esp_err_to_name.c @@ -224,6 +224,22 @@ static const esp_err_msg_t esp_err_msg_table[] = { # endif # ifdef ESP_ERR_OTA_VALIDATE_FAILED ERR_TBL_IT(ESP_ERR_OTA_VALIDATE_FAILED), /* 5379 0x1503 Error if OTA app image is invalid */ +# endif +# ifdef ESP_ERR_OTA_SMALL_SEC_VER + ERR_TBL_IT(ESP_ERR_OTA_SMALL_SEC_VER), /* 5380 0x1504 Error if the firmware has a secure version + less than the running firmware. */ +# endif +# ifdef ESP_ERR_OTA_ROLLBACK_FAILED + ERR_TBL_IT(ESP_ERR_OTA_ROLLBACK_FAILED), /* 5381 0x1505 Error if flash does not have valid firmware + in passive partition and hence rollback is + not possible */ +# endif +# ifdef ESP_ERR_OTA_ROLLBACK_INVALID_STATE + ERR_TBL_IT(ESP_ERR_OTA_ROLLBACK_INVALID_STATE), /* 5382 0x1506 Error if current active firmware is still + marked in pending validation state + (ESP_OTA_IMG_PENDING_VERIFY), essentially + first boot of firmware image post upgrade + and hence firmware upgrade is not possible */ # endif // components/bootloader_support/include/esp_image_format.h # ifdef ESP_ERR_IMAGE_BASE diff --git a/components/esp32/include/esp_flash_data_types.h b/components/esp32/include/esp_flash_data_types.h index d0f6ff32b8..998e522f06 100644 --- a/components/esp32/include/esp_flash_data_types.h +++ b/components/esp32/include/esp_flash_data_types.h @@ -72,6 +72,7 @@ typedef struct { #define PART_SUBTYPE_DATA_RF 0x01 #define PART_SUBTYPE_DATA_WIFI 0x02 #define PART_SUBTYPE_DATA_NVS_KEYS 0x04 +#define PART_SUBTYPE_DATA_EFUSE_EM 0x05 #define PART_TYPE_END 0xff #define PART_SUBTYPE_END 0xff diff --git a/components/partition_table/CMakeLists.txt b/components/partition_table/CMakeLists.txt index 6020820fe0..492dfb8107 100644 --- a/components/partition_table/CMakeLists.txt +++ b/components/partition_table/CMakeLists.txt @@ -69,6 +69,15 @@ if(CONFIG_SECURE_BOOT_ENABLED AND VERBATIM) endif() +# If anti-rollback option is set then factory partition should not be in Partition Table. +# In this case, should be used the partition table with two ota app without the factory. +if(CONFIG_APP_ANTI_ROLLBACK AND FACTORY_OFFSET) + fail_at_build_time(check_table_contents + "ERROR: Anti-rollback option is enabled. Partition table should consist of two ota app without factory partition.") +add_dependencies(bootloader check_table_contents) +add_dependencies(app check_table_contents) +endif() + add_dependencies(bootloader partition_table) add_dependencies(app partition_table) diff --git a/components/partition_table/Makefile.projbuild b/components/partition_table/Makefile.projbuild index 7128e6ab6f..57aeca2b93 100644 --- a/components/partition_table/Makefile.projbuild +++ b/components/partition_table/Makefile.projbuild @@ -60,7 +60,7 @@ $(PARTITION_TABLE_BIN_UNSIGNED): $(PARTITION_TABLE_CSV_PATH) $(SDKCONFIG_MAKEFIL @echo "Building partitions from $(PARTITION_TABLE_CSV_PATH)..." $(GEN_ESP32PART) $< $@ -all_binaries: $(PARTITION_TABLE_BIN) partition_table_get_info +all_binaries: $(PARTITION_TABLE_BIN) partition_table_get_info check_table_contents partition_table_get_info: $(PARTITION_TABLE_BIN) $(eval PHY_DATA_OFFSET:=$(shell $(GET_PART_INFO) --partition-type data --partition-subtype phy \ @@ -71,12 +71,19 @@ partition_table_get_info: $(PARTITION_TABLE_BIN) --partition-table-file $(PARTITION_TABLE_BIN) get_partition_info --info offset)) $(eval OTA_DATA_SIZE:=$(shell $(GET_PART_INFO) --partition-type data --partition-subtype ota \ --partition-table-file $(PARTITION_TABLE_BIN) get_partition_info --info size)) + $(eval FACTORY_OFFSET:=$(shell $(GET_PART_INFO) --partition-type app --partition-subtype factory \ + --partition-table-file $(PARTITION_TABLE_BIN) get_partition_info --info offset)) export APP_OFFSET export PHY_DATA_OFFSET export OTA_DATA_OFFSET export OTA_DATA_SIZE +# If anti-rollback option is set then factory partition should not be in Partition Table. +# In this case, should be used the partition table with two ota app without the factory. +check_table_contents: partition_table_get_info + @echo $(if $(CONFIG_APP_ANTI_ROLLBACK), $(if $(FACTORY_OFFSET), $(error "ERROR: Anti-rollback option is enabled. Partition table should consist of two ota app without factory partition."), ""), "") + PARTITION_TABLE_FLASH_CMD = $(ESPTOOLPY_SERIAL) write_flash $(PARTITION_TABLE_OFFSET) $(PARTITION_TABLE_BIN) ESPTOOL_ALL_FLASH_ARGS += $(PARTITION_TABLE_OFFSET) $(PARTITION_TABLE_BIN) diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index c7b8d50214..1707507403 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -60,6 +60,7 @@ SUBTYPES = { "nvs": 0x02, "coredump": 0x03, "nvs_keys": 0x04, + "efuse": 0x05, "esphttpd": 0x80, "fat": 0x81, "spiffs": 0x82, diff --git a/components/partition_table/project_include.cmake b/components/partition_table/project_include.cmake index 9d564ca51f..03ee63b3fd 100644 --- a/components/partition_table/project_include.cmake +++ b/components/partition_table/project_include.cmake @@ -60,6 +60,9 @@ get_partition_info(OTADATA_PARTITION_OFFSET get_partition_info(OTADATA_PARTITION_SIZE "--partition-type data --partition-subtype ota" "size") +get_partition_info(FACTORY_OFFSET + "--partition-type app --partition-subtype factory" "offset") + endif() set(BOOTLOADER_OFFSET 0x1000) diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index b1203696b8..6537967eb7 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -71,6 +71,7 @@ typedef enum { ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02, //!< NVS partition ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03, //!< COREDUMP partition ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS = 0x04, //!< Partition for NVS keys + ESP_PARTITION_SUBTYPE_DATA_EFUSE_EM = 0x05, //!< Partition for emulate eFuse bits ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80, //!< ESPHTTPD partition ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81, //!< FAT partition diff --git a/docs/en/api-reference/system/ota.rst b/docs/en/api-reference/system/ota.rst index 500b9512d2..95b0b32a0b 100644 --- a/docs/en/api-reference/system/ota.rst +++ b/docs/en/api-reference/system/ota.rst @@ -121,6 +121,74 @@ A brief description of where the states are set: * ``ESP_OTA_IMG_ABORTED`` state is set if there was no confirmation of the application operability and occurs reboots (if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled). * ``ESP_OTA_IMG_PENDING_VERIFY`` state is set in a bootloader if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled and selected app has ``ESP_OTA_IMG_NEW`` state. +.. _anti-rollback: + +Anti-rollback +------------- + +Anti-rollback prevents rollback to application with security version lower than one programmed in eFuse of chip. + +This function works if set :ref:`CONFIG_APP_ANTI_ROLLBACK` option. In the bootloader, when selecting a bootable application, an additional security version check is added which is on the chip and in the application image. The version in the bootable firmware must be greater than or equal to the version in the chip. + + +:ref:`CONFIG_APP_ANTI_ROLLBACK` and :ref:`CONFIG_APP_ROLLBACK_ENABLE` options are used together. In this case, rollback is possible only on the security version which is equal or higher than the version in the chip. + + +A typical anti-rollback scheme is +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- New firmware released with the elimination of vulnerabilities with the previous version of security. +- After the developer makes sure that this firmware is working. He can increase the security version and release a new firmware. +- Download new application. +- To make it bootable, run the function :cpp:func:`esp_ota_set_boot_partition`. If the security version of the new application is smaller than the version in the chip, the new application will be erased. Update to new firmware is not possible. +- Reboot. +- In the bootloader, an application with a security version greater than or equal to the version in the chip will be selected. If otadata is in the initial state, and one firmware was loaded via a serial channel, whose secure version is higher than the chip, then the secure version of efuse will be immediately updated in the bootloader. +- New application booted. Then the application should perform diagnostics of the operation and if it is completed successfully, you should call :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` function to mark the running application with the ``ESP_OTA_IMG_VALID`` state and update the secure version on chip. Note that if was called :cpp:func:`esp_ota_mark_app_invalid_rollback_and_reboot` function a rollback may not happend due to the device may not have any bootable apps then it will return ``ESP_ERR_OTA_ROLLBACK_FAILED`` error and stay in the ``ESP_OTA_IMG_PENDING_VERIFY`` state. +- The next update of app is possible if a running app is in the ``ESP_OTA_IMG_VALID`` state. + +Recommendation: + +If you want to avoid the download/erase overhead in case of the app from the server has security version lower then running app you have to get ``new_app_info.secure_version`` from the first package of an image and compare it with the secure version of efuse. Use ``esp_efuse_check_secure_version(new_app_info.secure_version)`` function if it is true then continue downloading otherwise abort. + +.. code-block:: c + + .... + bool image_header_was_checked = false; + while (1) { + int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE); + ... + if (data_read > 0) { + if (image_header_was_checked == false) { + esp_app_desc_t new_app_info; + if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { + // check current version with downloading + if (esp_efuse_check_secure_version(new_app_info.secure_version) == false) { + ESP_LOGE(TAG, "This a new app can not be downloaded due to a secure version is lower than stored in efuse."); + http_cleanup(client); + task_fatal_error(); + } + + image_header_was_checked = true; + + esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + } + } + esp_ota_write( update_handle, (const void *)ota_write_data, data_read); + } + } + ... + +Restrictions: + +- The number of bits in the ``secure_version`` field is limited to 32 bits. This means that only 32 times you can do an anti-rollback. You can reduce the length of this efuse field use :ref:`CONFIG_APP_SECURE_VERSION_SIZE_EFUSE_FIELD` option. +- Anti-rollback only works if the encoding scheme for efuse is set to ``NONE``. +- The partition table should not have a factory partition, only two of the app. + +``security_version``: + +- In application image it is stored in ``esp_app_desc`` structure. The number is set :ref:`CONFIG_APP_SECURE_VERSION`. +- In ESP32 it is stored in efuse ``EFUSE_BLK3_RDATA4_REG``. (when a eFuse bit is programmed to 1, it can never be reverted to 0). The number of bits set in this register is the ``security_version`` from app. + .. _secure-ota-updates: Secure OTA Updates Without Secure boot diff --git a/examples/system/ota/README.md b/examples/system/ota/README.md index a5b77ea03d..ced9391944 100644 --- a/examples/system/ota/README.md +++ b/examples/system/ota/README.md @@ -109,6 +109,25 @@ When the example starts up, it will print "Starting OTA example..." then: 3. Write the image to flash, and configure the next boot from this image. 4. Reboot +## Support the rollback + +This feature allows you to roll back to the previous firmware if the app is not operable. Option :ref:`CONFIG_APP_ROLLBACK_ENABLE` allows you to track the first boot of the application (see the``Over The Air Updates (OTA)`` article). +For ``native_ota_example``, added a bit of code to demonstrate how a rollback works. To use it, you need enable the :ref:`CONFIG_APP_ROLLBACK_ENABLE` option in Kconfig and under the "Example Configuration" submenu to set "Number of the GPIO input for diagnostic" to manage the rollback process. + +To trigger a rollback, this GPIO must be pulled low while the message `Diagnostics (5 sec)...` which will be on first boot. +If GPIO is not pulled low then the operable of the app will be confirmed. + +## Support the version of application + +For ``native_ota_example``, code has been added to demonstrate how to check the version of the application and prevent infinite firmware updates. Only the application with the new version can be downloaded. Version checking is performed after the very first firmware image package has been received, which contains data about the firmware version. The application version can be taken from three places: + +1. If ``PROJECT_VER`` variable set in project Cmake/Makefile file, its value will be used. +2. Else, if the ``$PROJECT_PATH/version.txt`` exists, its contents will be used as ``PROJECT_VER``. +3. Else, if the project is located inside a Git repository, the output of ``git describe`` will be used. +4. Otherwise, ``PROJECT_VER`` will be "1". + +In ``native_ota_example``, ``$PROJECT_PATH/version.txt`` is used to define the version of app. Change the version in the file to compile the new firmware. + ## Troubleshooting * Check your PC can ping the ESP32 at its IP, and that the IP, AP and other configuration settings are correct in menuconfig. diff --git a/examples/system/ota/native_ota_example/main/Kconfig.projbuild b/examples/system/ota/native_ota_example/main/Kconfig.projbuild index 628dfda5d3..ebb9995729 100644 --- a/examples/system/ota/native_ota_example/main/Kconfig.projbuild +++ b/examples/system/ota/native_ota_example/main/Kconfig.projbuild @@ -22,4 +22,15 @@ menu "Example Configuration" See example README.md for details. + config GPIO_DIAGNOSTIC + int "Number of the GPIO input for diagnostic" + range 0 39 + default 4 + help + Used to demonstrate how a rollback works. + The selected GPIO will be configured as an input with internal pull-up enabled. + To trigger a rollback, this GPIO must be pulled low while the message + `Diagnostics (5 sec)...` which will be on first boot. + If GPIO is not pulled low then the operable of the app will be confirmed. + endmenu diff --git a/examples/system/ota/native_ota_example/main/native_ota_example.c b/examples/system/ota/native_ota_example/main/native_ota_example.c index 0263b0447d..854e77fac4 100644 --- a/examples/system/ota/native_ota_example/main/native_ota_example.c +++ b/examples/system/ota/native_ota_example/main/native_ota_example.c @@ -22,6 +22,7 @@ #include "nvs.h" #include "nvs_flash.h" +#include "driver/gpio.h" #define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID #define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD @@ -260,6 +261,25 @@ static void ota_example_task(void *pvParameter) return ; } +static bool diagnostic(void) +{ + gpio_config_t io_conf; + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << CONFIG_GPIO_DIAGNOSTIC); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + ESP_LOGI(TAG, "Diagnostics (5 sec)..."); + vTaskDelay(5000 / portTICK_PERIOD_MS); + + bool diagnostic_is_ok = gpio_get_level(CONFIG_GPIO_DIAGNOSTIC); + + gpio_reset_pin(CONFIG_GPIO_DIAGNOSTIC); + return diagnostic_is_ok; +} + void app_main() { uint8_t sha_256[HASH_LEN] = { 0 }; @@ -288,7 +308,7 @@ void app_main() if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) { if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { // run diagnostic function ... - bool diagnostic_is_ok = true; + bool diagnostic_is_ok = diagnostic(); if (diagnostic_is_ok) { ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ..."); esp_ota_mark_app_valid_cancel_rollback(); diff --git a/examples/system/ota/native_ota_example/version.txt b/examples/system/ota/native_ota_example/version.txt new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/examples/system/ota/native_ota_example/version.txt @@ -0,0 +1 @@ +1 \ No newline at end of file -- 2.40.0