CVE-2024-28183: Insufficient Information (NVD-CWE-noinfo)
value: ESP-IDF is the development framework for Espressif SoCs supported on Windows, Linux and macOS. A Time-of-Check to Time-of-Use (TOCTOU) vulnerability was discovered in the implementation of the ESP-IDF bootloader which could allow an attacker with physical access to flash of the device to bypass anti-rollback protection. Anti-rollback prevents rollback to application with security version lower than one programmed in eFuse of chip. This attack can allow to boot past (passive) application partition having lower security version of the same device even in the presence of the flash encryption scheme. The attack requires carefully modifying the flash contents after the anti-rollback checks have been performed by the bootloader (before loading the application). The vulnerability is fixed in 4.4.7 and 5.2.1.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-25T15:15Z
Repository: https://github.com/espressif/esp-idf
Language: C
Files changed: components/bootloader_support/include/esp_image_format.h, components/bootloader_support/src/esp_image_format.c, components/esp_app_format/include/esp_app_desc.h, components/esp_system/startup_funcs.c, tools/test_apps/system/bootloader_sections/sdkconfig.ci.anti_rollback
Commit date: 2024-02-07 22:23:10 +0800
Commit message: fix(ota): additional checks for secure version in anti-rollback case

Some additional checks related to secure version of the application in
anti-rollback case have been added to avoid any attempts to boot lower
security version but valid application (e.g., passive partition image).

- Read secure_version under sha256 protection

- First check has been added in the bootloader to ensure correct secure
  version after application verification and loading stage. This check
  happens before setting up the flash cache mapping and handling over
  the final control to application. This check ensures that application
  was not swapped (e.g., to lower security version but valid image) just
  before the load stage in bootloader.

- Second check has been added in the application startup code to ensure
  that currently booting app has higher security version than the one
  programmed in the eFuse for anti-rollback scenario. This will ensure
  that only the legit application boots-up on the device for
  anti-rollback case.

Diff: diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h
index 5ec2ff0282..f677b08fe5 100644
--- a/components/bootloader_support/include/esp_image_format.h
+++ b/components/bootloader_support/include/esp_image_format.h
@@ -31,10 +31,11 @@ typedef struct {
   esp_image_header_t image; /* Header for entire image */
   esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
   uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
   uint32_t image_len; /* Length of image on flash, in bytes */
   uint8_t image_digest[32]; /* appended SHA-256 digest */
+  uint32_t secure_version; /* secure version for anti-rollback, it is covered by sha256 (set if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y) */
 } esp_image_metadata_t;
 
 typedef enum {
     ESP_IMAGE_VERIFY,            /* Verify image contents, not load to memory, load metadata. Print errors. */
     ESP_IMAGE_VERIFY_SILENT,     /* Verify image contents, not load to memory, load metadata. Don't print errors. */
diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c
index 0e9928d0af..9016ae296d 100644
--- a/components/bootloader_support/src/esp_image_format.c
+++ b/components/bootloader_support/src/esp_image_format.c
@@ -1,7 +1,7 @@
 /*
- * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
  *
  * SPDX-License-Identifier: Apache-2.0
  */
 #include <string.h>
 #include <sys/param.h>
@@ -16,10 +16,12 @@
 #include <bootloader_random.h>
 #include <bootloader_sha.h>
 #include "bootloader_util.h"
 #include "bootloader_common.h"
 #include "esp_rom_sys.h"
+#include "esp_efuse.h"
+#include "esp_app_desc.h"
 #include "bootloader_memory_utils.h"
 #include "soc/soc_caps.h"
 #include "hal/cache_ll.h"
 #if CONFIG_IDF_TARGET_ESP32C2
 #include "esp32c2/rom/rtc.h"
@@ -74,14 +76,14 @@ static bool should_load(uint32_t load_addr);
 /* Return true if load_addr is an address the bootloader should map via flash cache */
 static bool should_map(uint32_t load_addr);
 
 static esp_err_t process_segments(esp_image_metadata_t *data, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum);
 /* Load or verify a segment */
-static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum);
+static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata);
 
 /* split segment and verify if data_len is too long */
-static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum);
+static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata);
 
 /* Verify the main image header */
 static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
 
 /* Verify a segment header */
@@ -225,10 +227,25 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_
         }
 #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
         cache_ll_writeback_all(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA, CACHE_LL_ID_ALL);
 #endif
     }
+
+#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+    /* For anti-rollback case, reconfirm security version of the application to prevent FI attacks */
+    bool sec_ver = false;
+    if (do_load) {
+        sec_ver = esp_efuse_check_secure_version(data->secure_version);
+        if (!sec_ver) {
+            err = ESP_FAIL;
+            goto err;
+        }
+    }
+    /* Ensure that the security version check passes for image loading scenario */
+    ESP_FAULT_ASSERT(!do_load || sec_ver == true);
+#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+
 #endif // BOOTLOADER_BUILD
 
     // Success!
     return ESP_OK;
 
@@ -507,11 +524,11 @@ static esp_err_t process_segments(esp_image_metadata_t *data, bool silent, bool
     uint32_t start_segments = data->start_addr + data->image_len;
     uint32_t next_addr = start_segments;
     for (int i = 0; i < data->image.segment_count; i++) {
         esp_image_segment_header_t *header = &data->segments[i];
         ESP_LOGV(TAG, "loading segment header %d at offset 0x%"PRIx32, i, next_addr);
-        CHECK_ERR(process_segment(i, next_addr, header, silent, do_load, sha_handle, checksum));
+        CHECK_ERR(process_segment(i, next_addr, header, silent, do_load, sha_handle, checksum, data));
         next_addr += sizeof(esp_image_segment_header_t);
         data->segment_data[i] = next_addr;
         next_addr += header->data_len;
     }
     // Segments all loaded, verify length
@@ -528,11 +545,11 @@ err:
         err = ESP_ERR_IMAGE_INVALID;
     }
     return err;
 }
 
-static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum)
+static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
 {
     esp_err_t err;
 
     /* read segment header */
     err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
@@ -586,11 +603,11 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
         ESP_FAULT_ASSERT(!do_load || verify_load_addresses(0, load_addr, load_addr + data_len_remain, false, false));
 #endif
         uint32_t offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0;
         /* Data we could map in case we are not aligned to PAGE boundary is one page size lesser. */
         data_len = MIN(data_len_remain, ((free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE));
-        CHECK_ERR(process_segment_data(load_addr, data_addr, data_len, do_load, sha_handle, checksum));
+        CHECK_ERR(process_segment_data(index, load_addr, data_addr, data_len, do_load, sha_handle, checksum, metadata));
         data_addr += data_len;
         data_len_remain -= data_len;
     }
 
     return ESP_OK;
@@ -601,11 +618,46 @@ err:
     }
 
     return err;
 }
 
-static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum)
+#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+/* The __attribute__((optimize("O0"))) is used to disable optimizations for this function,
+ * preventing the compiler from potentially optimizing data_buffer and reading data directly from src.
+ * This is crucial as we want to read from Flash only once, ensuring the integrity of the data.
+ */
+__attribute__((optimize("O0")))
+static size_t process_esp_app_desc_data(const uint32_t *src, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
+{
+    /* Using data_buffer here helps to securely read secure_version
+     * (for anti-rollback) from esp_app_desc_t, preventing FI attack.
+     * We read data from Flash into this buffer, which is covered by sha256.
+     * Therefore, if the flash is under attackers control and contents are modified
+     * the sha256 comparison will fail.
+     *
+     * The esp_app_desc_t structure is located in DROM and is always in segment #0.
+     *
+     * esp_app_desc_t is always at #0 segment (index==0).
+     * secure_version field of esp_app_desc_t is located at #2 word (w_i==1).
+     */
+    uint32_t data_buffer[2];
+    memcpy(data_buffer, src, sizeof(data_buffer));
+    assert(data_buffer[0] == ESP_APP_DESC_MAGIC_WORD);
+    metadata->secure_version = data_buffer[1];
+    if (checksum != NULL) {
+        *checksum ^= data_buffer[0] ^ data_buffer[1];
+    }
+    if (sha_handle != NULL) {
+        bootloader_sha256_data(sha_handle, data_buffer, sizeof(data_buffer));
+    }
+    ESP_FAULT_ASSERT(memcmp(data_buffer, src, sizeof(data_buffer)) == 0);
+    ESP_FAULT_ASSERT(memcmp(&metadata->secure_version, &src[1], sizeof(uint32_t)) == 0);
+    return sizeof(data_buffer);
+}
+#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+
+static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
 {
     // If we are not loading, and the checksum is empty, skip processing this
     // segment for data
     if (!do_load && checksum == NULL) {
         ESP_LOGD(TAG, "skipping checksum for segment");
@@ -638,10 +690,20 @@ static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, ui
     uint32_t *dest = (uint32_t *)load_addr;
 #endif
 
     const uint32_t *src = data;
 
+#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+    if (segment == 0) {
+        // The esp_app_desc_t structure is located in DROM and is always in segment #0.
+        size_t len = process_esp_app_desc_data(src, sha_handle, checksum, metadata);
+        data_len -= len;
+        src += len / 4;
+        // In BOOTLOADER_BUILD, for DROM (segment #0) we do not load it into dest (only map it), do_load = false.
+    }
+#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+
     for (size_t i = 0; i < data_len; i += 4) {
         int w_i = i / 4; // Word index
         uint32_t w = src[w_i];
         if (checksum != NULL) {
             *checksum ^= w;
diff --git a/components/esp_app_format/include/esp_app_desc.h b/components/esp_app_format/include/esp_app_desc.h
index f4c7d04b1a..1bafd95202 100644
--- a/components/esp_app_format/include/esp_app_desc.h
+++ b/components/esp_app_format/include/esp_app_desc.h
@@ -36,10 +36,11 @@ typedef struct {
     uint32_t reserv2[20];       /*!< reserv2 */
 } esp_app_desc_t;
 
 /** @cond */
 ESP_STATIC_ASSERT(sizeof(esp_app_desc_t) == 256, "esp_app_desc_t should be 256 bytes");
+ESP_STATIC_ASSERT(offsetof(esp_app_desc_t, secure_version) == 4, "secure_version field must be at 4 offset");
 /** @endcond */
 
 /**
  * @brief   Return esp_app_desc structure. This structure includes app version.
  *
diff --git a/components/esp_system/startup_funcs.c b/components/esp_system/startup_funcs.c
index 551cc6b11b..79ceccac3a 100644
--- a/components/esp_system/startup_funcs.c
+++ b/components/esp_system/startup_funcs.c
@@ -198,10 +198,16 @@ ESP_SYSTEM_INIT_FN(init_virtual_efuse, CORE, BIT(0), 140)
 }
 #endif // CONFIG_EFUSE_VIRTUAL
 
 ESP_SYSTEM_INIT_FN(init_secure, CORE, BIT(0), 150)
 {
+#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
+    // For anti-rollback case, recheck security version before we boot-up the current application
+    const esp_app_desc_t *desc = esp_app_get_description();
+    ESP_RETURN_ON_FALSE(esp_efuse_check_secure_version(desc->secure_version), ESP_FAIL, TAG, "Incorrect secure version of app");
+#endif
+
 #ifdef CONFIG_SECURE_FLASH_ENC_ENABLED
     esp_flash_encryption_init_checks();
 #endif
 
 #if defined(CONFIG_SECURE_BOOT) || defined(CONFIG_SECURE_SIGNED_ON_UPDATE_NO_SECURE_BOOT)
diff --git a/tools/test_apps/system/bootloader_sections/sdkconfig.ci.anti_rollback b/tools/test_apps/system/bootloader_sections/sdkconfig.ci.anti_rollback
index 346883f5a4..468731442e 100644
--- a/tools/test_apps/system/bootloader_sections/sdkconfig.ci.anti_rollback
+++ b/tools/test_apps/system/bootloader_sections/sdkconfig.ci.anti_rollback
@@ -1,6 +1,6 @@
 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
 CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y
 CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
 CONFIG_PARTITION_TABLE_CUSTOM=y
 CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
-CONFIG_PARTITION_TABLE_OFFSET=0x9000
+CONFIG_PARTITION_TABLE_OFFSET=0xA000

Vulnerable code:  * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum);
static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum);
        CHECK_ERR(process_segment(i, next_addr, header, silent, do_load, sha_handle, checksum));
static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum)
        CHECK_ERR(process_segment_data(load_addr, data_addr, data_len, do_load, sha_handle, checksum));
static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum)
CONFIG_PARTITION_TABLE_OFFSET=0x9000
Fixed code:   uint32_t secure_version; /* secure version for anti-rollback, it is covered by sha256 (set if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y) */
 * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
#include "esp_efuse.h"
#include "esp_app_desc.h"
static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata);
static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata);

#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
    /* For anti-rollback case, reconfirm security version of the application to prevent FI attacks */
    bool sec_ver = false;
    if (do_load) {
        sec_ver = esp_efuse_check_secure_version(data->secure_version);
        if (!sec_ver) {
            err = ESP_FAIL;
            goto err;
        }
    }
    /* Ensure that the security version check passes for image loading scenario */
    ESP_FAULT_ASSERT(!do_load || sec_ver == true);
#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK

        CHECK_ERR(process_segment(i, next_addr, header, silent, do_load, sha_handle, checksum, data));
static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
        CHECK_ERR(process_segment_data(index, load_addr, data_addr, data_len, do_load, sha_handle, checksum, metadata));
#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
/* The __attribute__((optimize("O0"))) is used to disable optimizations for this function,
 * preventing the compiler from potentially optimizing data_buffer and reading data directly from src.
 * This is crucial as we want to read from Flash only once, ensuring the integrity of the data.
 */
__attribute__((optimize("O0")))
static size_t process_esp_app_desc_data(const uint32_t *src, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
{
    /* Using data_buffer here helps to securely read secure_version
     * (for anti-rollback) from esp_app_desc_t, preventing FI attack.
     * We read data from Flash into this buffer, which is covered by sha256.
     * Therefore, if the flash is under attackers control and contents are modified
     * the sha256 comparison will fail.
     *
     * The esp_app_desc_t structure is located in DROM and is always in segment #0.
     *
     * esp_app_desc_t is always at #0 segment (index==0).
     * secure_version field of esp_app_desc_t is located at #2 word (w_i==1).
     */
    uint32_t data_buffer[2];
    memcpy(data_buffer, src, sizeof(data_buffer));
    assert(data_buffer[0] == ESP_APP_DESC_MAGIC_WORD);
    metadata->secure_version = data_buffer[1];
    if (checksum != NULL) {
        *checksum ^= data_buffer[0] ^ data_buffer[1];
    }
    if (sha_handle != NULL) {
        bootloader_sha256_data(sha_handle, data_buffer, sizeof(data_buffer));
    }
    ESP_FAULT_ASSERT(memcmp(data_buffer, src, sizeof(data_buffer)) == 0);
    ESP_FAULT_ASSERT(memcmp(&metadata->secure_version, &src[1], sizeof(uint32_t)) == 0);
    return sizeof(data_buffer);
}
#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK

static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
    if (segment == 0) {
        // The esp_app_desc_t structure is located in DROM and is always in segment #0.
        size_t len = process_esp_app_desc_data(src, sha_handle, checksum, metadata);
        data_len -= len;
        src += len / 4;
        // In BOOTLOADER_BUILD, for DROM (segment #0) we do not load it into dest (only map it), do_load = false.
    }
#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK

ESP_STATIC_ASSERT(offsetof(esp_app_desc_t, secure_version) == 4, "secure_version field must be at 4 offset");
#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
    // For anti-rollback case, recheck security version before we boot-up the current application
    const esp_app_desc_t *desc = esp_app_get_description();
    ESP_RETURN_ON_FALSE(esp_efuse_check_secure_version(desc->secure_version), ESP_FAIL, TAG, "Incorrect secure version of app");
#endif

CONFIG_PARTITION_TABLE_OFFSET=0xA000