Merge branch 'bugfix/partition_table_integrity_check' into 'master'

paritition_table: Verify the partition table md5sum when loading in the app

Closes IDF-2411

See merge request espressif/esp-idf!12252
This commit is contained in:
Angus Gratton 2021-05-18 08:40:32 +00:00
commit 7c11d95af3
9 changed files with 130 additions and 30 deletions

View File

@ -672,6 +672,8 @@ menu "Security features"
config SECURE_FLASH_ENCRYPTION_MODE_RELEASE
bool "Release"
select PARTITION_TABLE_MD5 if !ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS
endchoice
menu "Potentially insecure options"

View File

@ -42,6 +42,9 @@ extern "C" {
#define PART_FLAG_ENCRYPTED (1<<0)
/* The md5sum value is found this many bytes after the ESP_PARTITION_MAGIC_MD5 offset */
#define ESP_PARTITION_MD5_OFFSET 16
/* Pre-partition table fixed flash offsets */
#define ESP_BOOTLOADER_DIGEST_OFFSET 0x0
#define ESP_BOOTLOADER_OFFSET CONFIG_BOOTLOADER_OFFSET_IN_FLASH /* Offset of bootloader image. Has matching value in bootloader KConfig.projbuild file. */

View File

@ -60,7 +60,7 @@ esp_err_t esp_partition_table_verify(const esp_partition_info_t *partition_table
esp_rom_md5_update(&context, (unsigned char *) partition_table, num_parts * sizeof(esp_partition_info_t));
esp_rom_md5_final(digest, &context);
unsigned char *md5sum = ((unsigned char *) part) + 16; // skip the 2B magic number and the 14B fillup bytes
unsigned char *md5sum = ((unsigned char *) part) + ESP_PARTITION_MD5_OFFSET;
if (memcmp(md5sum, digest, sizeof(digest)) != 0) {
if (log_errors) {

View File

@ -653,10 +653,11 @@ menu "ESP32-specific"
that after enabling this Wi-Fi/Bluetooth will not work.
config ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS
bool "App compatible with bootloaders before IDF v2.1"
bool "App compatible with bootloaders before ESP-IDF v2.1"
select ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS
default n
help
Bootloaders before IDF v2.1 did less initialisation of the
Bootloaders before ESP-IDF v2.1 did less initialisation of the
system clock. This setting needs to be enabled to build an app
which can be booted by these older bootloaders.
@ -668,6 +669,22 @@ menu "ESP32-specific"
Enabling this setting adds approximately 1KB to the app's IRAM usage.
config ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS
bool "App compatible with bootloader and partition table before ESP-IDF v3.1"
default n
help
Partition tables before ESP-IDF V3.1 do not contain an MD5 checksum
field, and the bootloader before ESP-IDF v3.1 cannot read a partition
table that contains an MD5 checksum field.
Enable this option only if your app needs to boot on a bootloader and/or
partition table that was generated from a version *before* ESP-IDF v3.1.
If this option and Flash Encryption are enabled at the same time, and any
data partitions in the partition table are marked Encrypted, then the
partition encrypted flag should be manually verified in the app before accessing
the partition (see CVE-2021-27926).
config ESP32_APP_INIT_CLK
bool
default y if ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS

View File

@ -34,6 +34,8 @@ typedef struct MD5Context {
uint8_t in[64];
} md5_context_t;
#define ESP_ROM_MD5_DIGEST_LEN 16
/**
* @brief Initialize the MD5 context
*
@ -46,9 +48,9 @@ void esp_rom_md5_init(md5_context_t *context);
*
* @param context MD5 context which has been initialized by `MD5Init`
* @param buf Input buffer
* @param len Buffer length
* @param len Buffer length in bytes
*/
void esp_rom_md5_update(md5_context_t *context, const uint8_t *buf, uint32_t len);
void esp_rom_md5_update(md5_context_t *context, const void *buf, uint32_t len);
/**
* @brief Extract the MD5 result, and erase the context
@ -56,7 +58,7 @@ void esp_rom_md5_update(md5_context_t *context, const uint8_t *buf, uint32_t len
* @param digest Where to store the 128-bit digest value
* @param context MD5 context
*/
void esp_rom_md5_final(uint8_t digest[16], md5_context_t *context);
void esp_rom_md5_final(uint8_t *digest, md5_context_t *context);
#ifdef __cplusplus
}

View File

@ -121,6 +121,7 @@ menu "Partition Table"
config PARTITION_TABLE_MD5
bool "Generate an MD5 checksum for the partition table"
default y
depends on !ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS
help
Generate an MD5 checksum for the partition table for protecting the
integrity of the table. The generation should be turned off for legacy

View File

@ -24,6 +24,7 @@
#include "esp_partition.h"
#include "esp_flash_encrypt.h"
#include "esp_log.h"
#include "esp_rom_md5.h"
#include "bootloader_common.h"
#include "bootloader_util.h"
#include "esp_ota_ops.h"
@ -36,8 +37,6 @@
#endif
#include "sys/queue.h"
typedef struct partition_list_item_ {
esp_partition_t info;
bool user_registered;
@ -164,24 +163,53 @@ static esp_partition_iterator_opaque_t* iterator_create(esp_partition_type_t typ
// This function is called only once, with s_partition_list_lock taken.
static esp_err_t load_partitions(void)
{
const uint32_t* ptr;
const uint8_t *p_start;
const uint8_t *p_end;
spi_flash_mmap_handle_t handle;
// Temporary list of loaded partitions, if valid then we copy this to s_partition_list
typeof(s_partition_list) new_partitions_list = SLIST_HEAD_INITIALIZER(s_partition_list);
partition_list_item_t* last = NULL;
#if CONFIG_PARTITION_TABLE_MD5
const uint8_t *md5_part = NULL;
const uint8_t *stored_md5;
uint8_t calc_md5[ESP_ROM_MD5_DIGEST_LEN];
md5_context_t context;
esp_rom_md5_init(&context);
#endif
// map 64kB block where partition table is located
esp_err_t err = spi_flash_mmap(ESP_PARTITION_TABLE_OFFSET & 0xffff0000,
SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, (const void**) &ptr, &handle);
SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, (const void **)&p_start, &handle);
if (err != ESP_OK) {
return err;
}
// calculate partition address within mmap-ed region
const esp_partition_info_t* it = (const esp_partition_info_t*)
(ptr + (ESP_PARTITION_TABLE_OFFSET & 0xffff) / sizeof(*ptr));
const esp_partition_info_t* end = it + SPI_FLASH_SEC_SIZE / sizeof(*it);
// tail of the linked list of partitions
partition_list_item_t* last = NULL;
for (; it != end; ++it) {
if (it->magic != ESP_PARTITION_MAGIC) {
p_start += (ESP_PARTITION_TABLE_OFFSET & 0xffff);
p_end = p_start + SPI_FLASH_SEC_SIZE;
for(const uint8_t *p_entry = p_start; p_entry < p_end; p_entry += sizeof(esp_partition_info_t)) {
esp_partition_info_t entry;
// copying to RAM instead of using pointer to flash to avoid any chance of TOCTOU due to cache miss
// when flash encryption is used
memcpy(&entry, p_entry, sizeof(entry));
#if CONFIG_PARTITION_TABLE_MD5
if (entry.magic == ESP_PARTITION_MAGIC_MD5) {
md5_part = p_entry;
break;
}
#endif
if (entry.magic != ESP_PARTITION_MAGIC) {
break;
}
#if CONFIG_PARTITION_TABLE_MD5
esp_rom_md5_update(&context, &entry, sizeof(entry));
#endif
// allocate new linked list item and populate it with data from partition table
partition_list_item_t* item = (partition_list_item_t*) calloc(sizeof(partition_list_item_t), 1);
if (item == NULL) {
@ -189,35 +217,70 @@ static esp_err_t load_partitions(void)
break;
}
item->info.flash_chip = esp_flash_default_chip;
item->info.address = it->pos.offset;
item->info.size = it->pos.size;
item->info.type = it->type;
item->info.subtype = it->subtype;
item->info.encrypted = it->flags & PART_FLAG_ENCRYPTED;
item->info.address = entry.pos.offset;
item->info.size = entry.pos.size;
item->info.type = entry.type;
item->info.subtype = entry.subtype;
item->info.encrypted = entry.flags & PART_FLAG_ENCRYPTED;
item->user_registered = false;
if (!esp_flash_encryption_enabled()) {
/* If flash encryption is not turned on, no partitions should be treated as encrypted */
item->info.encrypted = false;
} else if (it->type == PART_TYPE_APP
|| (it->type == PART_TYPE_DATA && it->subtype == PART_SUBTYPE_DATA_OTA)
|| (it->type == PART_TYPE_DATA && it->subtype == PART_SUBTYPE_DATA_NVS_KEYS)) {
} else if (entry.type == PART_TYPE_APP
|| (entry.type == PART_TYPE_DATA && entry.subtype == PART_SUBTYPE_DATA_OTA)
|| (entry.type == PART_TYPE_DATA && entry.subtype == PART_SUBTYPE_DATA_NVS_KEYS)) {
/* If encryption is turned on, all app partitions and OTA data
are always encrypted */
item->info.encrypted = true;
}
// it->label may not be zero-terminated
strncpy(item->info.label, (const char*) it->label, sizeof(item->info.label) - 1);
item->info.label[sizeof(it->label)] = 0;
// note: if label in flash is not null terminated, one byte will be truncated here
strlcpy(item->info.label, (const char*) entry.label, sizeof(item->info.label));
// add it to the list
if (last == NULL) {
SLIST_INSERT_HEAD(&s_partition_list, item, next);
SLIST_INSERT_HEAD(&new_partitions_list, item, next);
} else {
SLIST_INSERT_AFTER(last, item, next);
}
last = item;
}
#if CONFIG_PARTITION_TABLE_MD5
if (md5_part == NULL) {
ESP_LOGE(TAG, "No MD5 found in partition table");
err = ESP_ERR_NOT_FOUND;
} else {
stored_md5 = md5_part + ESP_PARTITION_MD5_OFFSET;
esp_rom_md5_final(calc_md5, &context);
ESP_LOG_BUFFER_HEXDUMP("calculated md5", calc_md5, ESP_ROM_MD5_DIGEST_LEN, ESP_LOG_VERBOSE);
ESP_LOG_BUFFER_HEXDUMP("stored md5", stored_md5, ESP_ROM_MD5_DIGEST_LEN, ESP_LOG_VERBOSE);
if (memcmp(calc_md5, stored_md5, ESP_ROM_MD5_DIGEST_LEN) != 0) {
ESP_LOGE(TAG, "Partition table MD5 mismatch");
err = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "Partition table MD5 verified");
}
}
#endif
if (err == ESP_OK) {
/* Don't copy the list to the static variable unless it's verified */
s_partition_list = new_partitions_list;
} else {
/* Otherwise, free all the memory we just allocated */
partition_list_item_t *it = new_partitions_list.slh_first;
while (it) {
partition_list_item_t *next = it->next.sle_next;
free(it);
it = next;
}
}
spi_flash_munmap(handle);
return err;
}

View File

@ -35,6 +35,11 @@ The bootloader does not support booting apps from older versions of ESP-IDF. Whe
Bootloaders built from very old versions of ESP-IDF (before ESP-IDF V2.1) perform less hardware configuration than newer versions. When using a bootloader from these early ESP-IDF versions and building a new app, enable the config option :ref:`CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS`.
Before ESP-IDF V3.1
^^^^^^^^^^^^^^^^^^^
Bootloaders built from versions of ESP-IDF before V3.1 do not support MD5 checksums in the partition table binary. When using a bootloader from these ESP-IDF versions and building a new app, enable the config option :ref:`CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS`.
SPI Flash Configuration
^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -192,7 +192,14 @@ MD5 checksum
The binary format of the partition table contains an MD5 checksum computed based on the partition table. This checksum is used for checking the integrity of the partition table during the boot.
The MD5 checksum generation can be disabled by the ``--disable-md5sum`` option of ``gen_esp32part.py`` or by the :ref:`CONFIG_PARTITION_TABLE_MD5` option. This is useful for example when one uses a legacy bootloader which cannot process MD5 checksums and the boot fails with the error message ``invalid magic number 0xebeb``.
.. only:: esp32
The MD5 checksum generation can be disabled by the ``--disable-md5sum`` option of ``gen_esp32part.py`` or by the :ref:`CONFIG_PARTITION_TABLE_MD5` option. This is useful for example when one :ref:`uses a bootloader from ESP-IDF before v3.1 <CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS>` which cannot process MD5 checksums and the boot fails with the error message ``invalid magic number 0xebeb``.
.. only:: not esp32
The MD5 checksum generation can be disabled by the ``--disable-md5sum`` option of ``gen_esp32part.py`` or by the :ref:`CONFIG_PARTITION_TABLE_MD5` option.
Flashing the partition table
----------------------------