diff --git a/examples/system/ota/advanced_https_ota/anti_rollback_partition.csv b/examples/system/ota/advanced_https_ota/anti_rollback_partition.csv new file mode 100644 index 0000000000..899b6b1721 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/anti_rollback_partition.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x4000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +ota_0, app, ota_0, , 3584K, +ota_1, app, ota_1, , 3584K, +emul_efuse, data, 5, , 0x2000 diff --git a/examples/system/ota/advanced_https_ota/example_test.py b/examples/system/ota/advanced_https_ota/example_test.py index 8beefc0663..3b2d64371f 100644 --- a/examples/system/ota/advanced_https_ota/example_test.py +++ b/examples/system/ota/advanced_https_ota/example_test.py @@ -453,6 +453,70 @@ def test_examples_protocol_advanced_https_ota_example_redirect_url(env, extra_da dut1.reset() +@ttfw_idf.idf_example_test(env_tag="Example_8Mflash_Ethernet") +def test_examples_protocol_advanced_https_ota_example_anti_rollback(env, extra_data): + """ + Working of OTA when anti_rollback is enabled and security version of new image is less than current one. + Application should return with error message in this case. + steps: | + 1. join AP + 2. Generate binary file with lower security version + 3. Fetch OTA image over HTTPS + 4. Check working of anti_rollback feature + """ + dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT, app_config_name='anti_rollback') + server_port = 8001 + # Original binary file generated after compilation + bin_name = "advanced_https_ota.bin" + # Modified firmware image to lower security version in its header. This is to enable negative test case + anti_rollback_bin_name = "advanced_https_ota_lower_sec_version.bin" + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, bin_name) + file_size = os.path.getsize(binary_file) + f = open(binary_file, "rb+") + fo = open(os.path.join(dut1.app.binary_path, anti_rollback_bin_name), "wb+") + fo.write(f.read(file_size)) + # Change security_version to 0 for negative test case + fo.seek(36) + fo.write(b'\x00') + fo.close() + f.close() + binary_file = os.path.join(dut1.app.binary_path, anti_rollback_bin_name) + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET) + # start test + host_ip = get_my_ip() + if (get_server_status(host_ip, server_port) is False): + thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port)) + thread1.daemon = True + thread1.start() + dut1.start_app() + # Positive Case + dut1.expect("Loaded app from partition at offset", timeout=30) + try: + ip_address = dut1.expect(re.compile(r" eth ip: ([^,]+),"), timeout=30) + print("Connected to AP with IP: {}".format(ip_address)) + except DUT.ExpectTimeout: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP') + dut1.expect("Starting Advanced OTA example", timeout=30) + + # Use originally generated image with secure_version=1 + print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + bin_name)) + dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + bin_name) + dut1.expect("Loaded app from partition at offset", timeout=60) + dut1.expect(re.compile(r" eth ip: ([^,]+),"), timeout=30) + dut1.expect("App is valid, rollback cancelled successfully", 30) + + # Negative Case + dut1.expect("Starting Advanced OTA example", timeout=30) + # Use modified image with secure_version=0 + print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + anti_rollback_bin_name)) + dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + anti_rollback_bin_name) + dut1.expect("New firmware security version is less than eFuse programmed, 0 < 1", timeout=30) + os.remove(anti_rollback_bin_name) + + if __name__ == '__main__': test_examples_protocol_advanced_https_ota_example() test_examples_protocol_advanced_https_ota_example_chunked() @@ -460,3 +524,4 @@ if __name__ == '__main__': test_examples_protocol_advanced_https_ota_example_truncated_bin() test_examples_protocol_advanced_https_ota_example_truncated_header() test_examples_protocol_advanced_https_ota_example_random() + test_examples_protocol_advanced_https_ota_example_anti_rollback() diff --git a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c index 7fbbca93dd..a00a281e72 100644 --- a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c +++ b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c @@ -20,6 +20,10 @@ #include "nvs_flash.h" #include "protocol_examples_common.h" +#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK +#include "esp_efuse.h" +#endif + #if CONFIG_EXAMPLE_CONNECT_WIFI #include "esp_wifi.h" #endif @@ -49,6 +53,19 @@ static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) } #endif +#ifdef CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK + /** + * Secure version check from firmware image header prevents subsequent download and flash write of + * entire firmware image. However this is optional because it is also taken care in API + * esp_https_ota_finish at the end of OTA update procedure. + */ + const uint32_t hw_sec_version = esp_efuse_read_secure_version(); + if (new_app_info->secure_version < hw_sec_version) { + ESP_LOGW(TAG, "New firmware security version is less than eFuse programmed, %d < %d", new_app_info->secure_version, hw_sec_version); + return ESP_FAIL; + } +#endif + return ESP_OK; } @@ -167,6 +184,25 @@ void app_main(void) */ ESP_ERROR_CHECK(example_connect()); +#if defined(CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE) && defined(CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK) + /** + * We are treating successful WiFi connection as a checkpoint to cancel rollback + * process and mark newly updated firmware image as active. For production cases, + * please tune the checkpoint behavior per end application requirement. + */ + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_ota_img_states_t ota_state; + if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) { + if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { + if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK) { + ESP_LOGI(TAG, "App is valid, rollback cancelled successfully"); + } else { + ESP_LOGE(TAG, "Failed to cancel rollback"); + } + } + } +#endif + #if CONFIG_EXAMPLE_CONNECT_WIFI /* Ensure to disable any WiFi power save mode, this allows best throughput * and hence timings for overall OTA operation. diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.ci.anti_rollback b/examples/system/ota/advanced_https_ota/sdkconfig.ci.anti_rollback new file mode 100644 index 0000000000..bd54a72a98 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/sdkconfig.ci.anti_rollback @@ -0,0 +1,22 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="anti_rollback_partition.csv" +CONFIG_PARTITION_TABLE_FILENAME="anti_rollback_partition.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y +CONFIG_BOOTLOADER_APP_SECURE_VERSION=1 +CONFIG_BOOTLOADER_EFUSE_SECURE_VERSION_EMULATE=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y diff --git a/tools/ci/config/target-test.yml b/tools/ci/config/target-test.yml index 9f0a100f90..c48de18afb 100644 --- a/tools/ci/config/target-test.yml +++ b/tools/ci/config/target-test.yml @@ -163,6 +163,12 @@ example_test_001C: - ESP32 - Example_GENERIC +example_test_001D: + extends: .example_test_template + tags: + - ESP32 + - Example_8Mflash_Ethernet + example_test_002: extends: .example_test_template image: $CI_DOCKER_REGISTRY/ubuntu-test-env$BOT_DOCKER_IMAGE_TAG