Merge branch 'bugfix/OTA_and_flash_encryption_incompatible' into 'master'

bootloader_support: fix bug OTA & flash encryption incompatible

github issue:https://github.com/espressif/esp-idf/issues/253

See merge request !464
This commit is contained in:
Ivan Grokhotkov 2017-02-15 13:24:10 +08:00
commit 25f05bef71
10 changed files with 189 additions and 75 deletions

View File

@ -27,6 +27,7 @@
#include "esp_spi_flash.h"
#include "esp_image_format.h"
#include "esp_secure_boot.h"
#include "esp_flash_encrypt.h"
#include "sdkconfig.h"
#include "esp_ota_ops.h"
@ -44,6 +45,10 @@ typedef struct ota_ops_entry_ {
esp_partition_t part;
uint32_t erased_size;
uint32_t wrote_size;
#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED
uint8_t partial_bytes;
uint8_t partial_data[16];
#endif
LIST_ENTRY(ota_ops_entry_) entries;
} ota_ops_entry_t;
@ -106,6 +111,7 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
{
const uint8_t *data_bytes = (const uint8_t *)data;
esp_err_t ret;
ota_ops_entry_t *it;
@ -119,7 +125,47 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
if (it->handle == handle) {
// must erase the partition before writing to it
assert(it->erased_size > 0 && "must erase the partition before writing to it");
ret = esp_partition_write(&it->part, it->wrote_size, data, size);
if(it->wrote_size == 0 && size > 0 && data_bytes[0] != 0xE9) {
ESP_LOGE(TAG, "OTA image has invalid magic byte (expected 0xE9, saw 0x%02x", data_bytes[0]);
return ESP_ERR_OTA_VALIDATE_FAILED;
}
#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED
if (esp_flash_encryption_enabled()) {
/* Can only write 16 byte blocks to flash, so need to cache anything else */
size_t copy_len;
/* check if we have partially written data from earlier */
if (it->partial_bytes != 0) {
copy_len = OTA_MIN(16 - it->partial_bytes, size);
memcpy(it->partial_data + it->partial_bytes, data_bytes, copy_len);
it->partial_bytes += copy_len;
if (it->partial_bytes != 16) {
return ESP_OK; /* nothing to write yet, just filling buffer */
}
/* write 16 byte to partition */
ret = esp_partition_write(&it->part, it->wrote_size, it->partial_data, 16);
if (ret != ESP_OK) {
return ret;
}
it->partial_bytes = 0;
memset(it->partial_data, 0xFF, 16);
it->wrote_size += 16;
data_bytes += copy_len;
size -= copy_len;
}
/* check if we need to save trailing data that we're about to write */
it->partial_bytes = size % 16;
if (it->partial_bytes != 0) {
size -= it->partial_bytes;
memcpy(it->partial_data, data_bytes + size, it->partial_bytes);
}
}
#endif
ret = esp_partition_write(&it->part, it->wrote_size, data_bytes, size);
if(ret == ESP_OK){
it->wrote_size += size;
}
@ -135,26 +181,11 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
esp_err_t esp_ota_end(esp_ota_handle_t handle)
{
ota_ops_entry_t *it;
size_t image_size;
esp_err_t ret = ESP_OK;
for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
if (it->handle == handle) {
// an ota handle need to be ended after erased and wrote data in it
if ((it->erased_size == 0) || (it->wrote_size == 0)) {
return ESP_ERR_INVALID_ARG;
}
#ifdef CONFIG_SECUREBOOTLOADER
esp_err_t ret;
size_t image_size;
if (esp_image_basic_verify(it->part.address, &image_size) != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
ret = esp_secure_boot_verify_signature(it->part.address, image_size);
if (ret != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
#endif
LIST_REMOVE(it, entries);
break;
}
}
@ -163,8 +194,44 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
return ESP_ERR_NOT_FOUND;
}
/* 'it' holds the ota_ops_entry_t for 'handle' */
// esp_ota_end() is only valid if some data was written to this handle
if ((it->erased_size == 0) || (it->wrote_size == 0)) {
ret = ESP_ERR_INVALID_ARG;
goto cleanup;
}
#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED
if (it->partial_bytes > 0 && esp_flash_encryption_enabled()) {
/* Write out last 16 bytes, if necessary */
ret = esp_partition_write(&it->part, it->wrote_size, it->partial_data, 16);
if (ret != ESP_OK) {
ret = ESP_ERR_INVALID_STATE;
goto cleanup;
}
it->wrote_size += 16;
it->partial_bytes = 0;
}
#endif
if (esp_image_basic_verify(it->part.address, true, &image_size) != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
ret = esp_secure_boot_verify_signature(it->part.address, image_size);
if (ret != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
}
#endif
cleanup:
LIST_REMOVE(it, entries);
free(it);
return ESP_OK;
return ret;
}
static uint32_t ota_select_crc(const ota_select *s)
@ -271,11 +338,9 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
}
return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 0, find_partition);
} else if (s_ota_select[0].ota_seq == 0xFFFFFFFF && s_ota_select[1].ota_seq == 0xFFFFFFFF) {
return rewrite_ota_seq(SUB_TYPE_ID(subtype) + 1, 0, find_partition);
} else {
return ESP_ERR_OTA_SELECT_INFO_INVALID;
/* Both OTA slots are invalid, probably because unformatted... */
return rewrite_ota_seq(SUB_TYPE_ID(subtype) + 1, 0, find_partition);
}
} else {
@ -285,17 +350,18 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
{
size_t image_size;
const esp_partition_t *find_partition = NULL;
if (partition == NULL) {
return ESP_ERR_INVALID_ARG;
}
#ifdef CONFIG_SECUREBOOTLOADER
size_t image_size;
if (esp_image_basic_verify(partition->address, &image_size) != ESP_OK) {
if (esp_image_basic_verify(partition->address, true, &image_size) != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
ret = esp_secure_boot_verify_signature(partition->address, image_size);
#ifdef CONFIG_SECURE_BOOT_ENABLED
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, image_size);
if (ret != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
@ -349,33 +415,34 @@ const esp_partition_t *esp_ota_get_boot_partition(void)
}
ota_app_count = get_ota_partition_count();
ESP_LOGD(TAG, "found ota bin max = %d", ota_app_count);
ESP_LOGD(TAG, "found ota app max = %d", ota_app_count);
if (s_ota_select[0].ota_seq == 0xFFFFFFFF && s_ota_select[1].ota_seq == 0xFFFFFFFF) {
ESP_LOGD(TAG, "finding factory bin......");
ESP_LOGD(TAG, "finding factory app......");
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
} else if (ota_select_valid(&s_ota_select[0]) && ota_select_valid(&s_ota_select[1])) {
ESP_LOGD(TAG, "finding ota_%d bin......", \
ESP_LOGD(TAG, "finding ota_%d app......", \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((OTA_MAX(s_ota_select[0].ota_seq, s_ota_select[1].ota_seq) - 1) % ota_app_count));
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((OTA_MAX(s_ota_select[0].ota_seq, s_ota_select[1].ota_seq) - 1) % ota_app_count), NULL);
} else if (ota_select_valid(&s_ota_select[0])) {
ESP_LOGD(TAG, "finding ota_%d bin......", \
ESP_LOGD(TAG, "finding ota_%d app......", \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[0].ota_seq - 1) % ota_app_count);
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[0].ota_seq - 1) % ota_app_count, NULL);
} else if (ota_select_valid(&s_ota_select[1])) {
ESP_LOGD(TAG, "finding ota_%d bin......", \
ESP_LOGD(TAG, "finding ota_%d app......", \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[1].ota_seq - 1) % ota_app_count);
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[1].ota_seq - 1) % ota_app_count, NULL);
} else {
ESP_LOGE(TAG, "not found current bin");
return NULL;
ESP_LOGE(TAG, "ota data invalid, no current app. Assuming factory");
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
}
}

View File

@ -73,11 +73,16 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void* data, size_t size);
/**
* @brief Finish the update and validate written data
*
* @param handle Handle obtained from esp_ota_begin
* @param handle Handle obtained from esp_ota_begin.
*
* @note After calling esp_ota_end(), the handle is no longer valid and any memory associated with it is freed (regardless of result).
*
* @return:
* - ESP_OK: if validate ota image pass
* - ESP_ERR_OTA_VALIDATE_FAILED: validate the ota image is invalid
* - ESP_OK: Newly written OTA app image is valid.
* - ESP_ERR_NOT_FOUND: OTA handle was not found.
* - ESP_ERR_INVALID_ARG: Handle was never written to.
* - ESP_ERR_OTA_VALIDATE_FAILED: OTA image is invalid (either not a valid app image, or - if secure boot is enabled - signature failed to verify.)
* - ESP_ERR_INVALID_STATE: If flash encryption is enabled, this result indicates an internal error writing the final encrypted bytes to flash.
*/
esp_err_t esp_ota_end(esp_ota_handle_t handle);

View File

@ -323,12 +323,15 @@ void bootloader_main()
} else {
if(ota_select_valid(&sa) && ota_select_valid(&sb)) {
load_part_pos = bs.ota[(((sa.ota_seq > sb.ota_seq)?sa.ota_seq:sb.ota_seq) - 1)%bs.app_count];
}else if(ota_select_valid(&sa)) {
} else if(ota_select_valid(&sa)) {
load_part_pos = bs.ota[(sa.ota_seq - 1) % bs.app_count];
}else if(ota_select_valid(&sb)) {
} else if(ota_select_valid(&sb)) {
load_part_pos = bs.ota[(sb.ota_seq - 1) % bs.app_count];
}else {
ESP_LOGE(TAG, "ota data partition info error");
} else if (bs.factory.offset != 0) {
ESP_LOGE(TAG, "ota data partition invalid, falling back to factory");
load_part_pos = bs.factory;
} else {
ESP_LOGE(TAG, "ota data partition invalid and no factory, can't boot");
return;
}
}

View File

@ -15,7 +15,8 @@
#define __ESP32_FLASH_ENCRYPT_H
#include <stdbool.h>
#include <esp_err.h>
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_spi_flash.h"
#include "soc/efuse_reg.h"
@ -30,9 +31,17 @@
*
* @return true if flash encryption is enabled.
*/
static inline bool esp_flash_encryption_enabled(void) {
static inline IRAM_ATTR bool esp_flash_encryption_enabled(void) {
uint32_t flash_crypt_cnt = REG_GET_FIELD(EFUSE_BLK0_RDATA0_REG, EFUSE_RD_FLASH_CRYPT_CNT);
return __builtin_parity(flash_crypt_cnt) == 1;
/* __builtin_parity is in flash, so we calculate parity inline */
bool enabled = false;
while(flash_crypt_cnt) {
if (flash_crypt_cnt & 1) {
enabled = !enabled;
}
flash_crypt_cnt >>= 1;
}
return enabled;
}
/* @brief Update on-device flash encryption

View File

@ -16,6 +16,7 @@
#include <bootloader_flash.h>
#include <esp_log.h>
#include <esp_spi_flash.h> /* including in bootloader for error values */
#include <esp_flash_encrypt.h>
#ifndef BOOTLOADER_BUILD
/* Normal app version maps to esp_spi_flash.h operations...
@ -48,7 +49,11 @@ void bootloader_munmap(const void *mapping)
esp_err_t bootloader_flash_read(size_t src, void *dest, size_t size, bool allow_decrypt)
{
return spi_flash_read(src, dest, size);
if (allow_decrypt && esp_flash_encryption_enabled()) {
return spi_flash_read_encrypted(src, dest, size);
} else {
return spi_flash_read(src, dest, size);
}
}
esp_err_t bootloader_flash_write(size_t dest_addr, void *src, size_t size, bool write_encrypted)

View File

@ -285,6 +285,9 @@ static esp_err_t encrypt_partition(int index, const esp_partition_info_t *partit
} else {
should_encrypt = false;
}
} else if (partition->type == PART_TYPE_DATA && partition->subtype == PART_SUBTYPE_DATA_OTA) {
/* check if we have ota data partition and the partition should be encrypted unconditionally */
should_encrypt = true;
}
if (!should_encrypt) {

View File

@ -82,7 +82,7 @@ void esp_crosscore_int_init() {
assert(err == ESP_OK);
}
void esp_crosscore_int_send_yield(int coreId) {
void IRAM_ATTR esp_crosscore_int_send_yield(int coreId) {
assert(coreId<portNUM_PROCESSORS);
//Mark the reason we interrupt the other CPU
portENTER_CRITICAL(&reasonSpinlock);

View File

@ -28,6 +28,7 @@
#include "esp_ipc.h"
#include "esp_attr.h"
#include "esp_spi_flash.h"
#include "esp_flash_encrypt.h"
#include "esp_log.h"
#include "cache_utils.h"
@ -52,8 +53,10 @@
This ensures stale cache entries are never read after fresh calls
to spi_flash_mmap(), while keeping the number of cache flushes to a
minimum.
Returns true if cache was flushed.
*/
static void spi_flash_ensure_unmodified_region(size_t start_addr, size_t length);
static bool spi_flash_ensure_unmodified_region(size_t start_addr, size_t length);
typedef struct mmap_entry_{
uint32_t handle;
@ -89,6 +92,7 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
esp_err_t ret;
bool did_flush, need_flush = false;
mmap_entry_t* new_entry = (mmap_entry_t*) malloc(sizeof(mmap_entry_t));
if (new_entry == 0) {
return ESP_ERR_NO_MEM;
@ -102,7 +106,7 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
spi_flash_disable_interrupts_caches_and_other_cpu();
spi_flash_ensure_unmodified_region(src_addr, size);
did_flush = spi_flash_ensure_unmodified_region(src_addr, size);
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
@ -159,8 +163,11 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
(DPORT_PRO_FLASH_MMU_TABLE[i] == entry_val &&
DPORT_APP_FLASH_MMU_TABLE[i] == entry_val));
if (s_mmap_page_refcnt[i] == 0) {
DPORT_PRO_FLASH_MMU_TABLE[i] = entry_val;
DPORT_APP_FLASH_MMU_TABLE[i] = entry_val;
if (DPORT_PRO_FLASH_MMU_TABLE[i] != entry_val || DPORT_APP_FLASH_MMU_TABLE[i] != entry_val) {
DPORT_PRO_FLASH_MMU_TABLE[i] = entry_val;
DPORT_APP_FLASH_MMU_TABLE[i] = entry_val;
need_flush = true;
}
}
++s_mmap_page_refcnt[i];
}
@ -173,6 +180,18 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
*out_ptr = (void*) (region_addr + start * SPI_FLASH_MMU_PAGE_SIZE);
ret = ESP_OK;
}
/* This is a temporary fix for an issue where some
encrypted cache reads may see stale data.
Working on a long term fix that doesn't require invalidating
entire cache.
*/
if (esp_flash_encryption_enabled() && !did_flush && need_flush) {
Cache_Flush(0);
Cache_Flush(1);
}
spi_flash_enable_interrupts_caches_and_other_cpu();
if (*out_ptr == NULL) {
free(new_entry);
@ -240,25 +259,29 @@ void spi_flash_mmap_dump()
*/
static uint32_t written_pages[256/32];
static void update_written_pages(size_t start_addr, size_t length, bool mark);
static bool update_written_pages(size_t start_addr, size_t length, bool mark);
void IRAM_ATTR spi_flash_mark_modified_region(size_t start_addr, size_t length)
{
update_written_pages(start_addr, length, true);
}
static void IRAM_ATTR spi_flash_ensure_unmodified_region(size_t start_addr, size_t length)
static IRAM_ATTR bool spi_flash_ensure_unmodified_region(size_t start_addr, size_t length)
{
update_written_pages(start_addr, length, false);
return update_written_pages(start_addr, length, false);
}
/* generic implementation for the previous two functions */
static inline IRAM_ATTR void update_written_pages(size_t start_addr, size_t length, bool mark)
static inline IRAM_ATTR bool update_written_pages(size_t start_addr, size_t length, bool mark)
{
for (uint32_t addr = start_addr; addr < start_addr + length; addr += SPI_FLASH_MMU_PAGE_SIZE) {
/* align start_addr & length to full MMU pages */
uint32_t page_start_addr = start_addr & ~(SPI_FLASH_MMU_PAGE_SIZE-1);
length += (start_addr - page_start_addr);
length = (length + SPI_FLASH_MMU_PAGE_SIZE - 1) & ~(SPI_FLASH_MMU_PAGE_SIZE-1);
for (uint32_t addr = page_start_addr; addr < page_start_addr + length; addr += SPI_FLASH_MMU_PAGE_SIZE) {
int page = addr / SPI_FLASH_MMU_PAGE_SIZE;
if (page >= 256) {
return; /* invalid address */
return false; /* invalid address */
}
int idx = page / 32;
@ -277,6 +300,8 @@ static inline IRAM_ATTR void update_written_pages(size_t start_addr, size_t leng
Cache_Flush(1);
#endif
bzero(written_pages, sizeof(written_pages));
return true;
}
}
return false;
}

View File

@ -166,10 +166,14 @@ static esp_err_t load_partitions()
item->info.type = it->type;
item->info.subtype = it->subtype;
item->info.encrypted = it->flags & PART_FLAG_ENCRYPTED;
if (esp_flash_encryption_enabled() && it->type == PART_TYPE_APP) {
/* All app partitions are encrypted if encryption is turned on */
if (esp_flash_encryption_enabled() && (
it->type == PART_TYPE_APP
|| (it->type == PART_TYPE_DATA && it->subtype == PART_SUBTYPE_DATA_OTA))) {
/* 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(it->label));
item->info.label[sizeof(it->label)] = 0;

View File

@ -95,7 +95,7 @@ static void initialise_wifi(void)
}
/*read buffer by byte still delim ,return read bytes counts*/
int read_until(char *buffer, char delim, int len)
static int read_until(char *buffer, char delim, int len)
{
// /*TODO: delim check,buffer check,further: do an buffer length limited*/
int i = 0;
@ -109,7 +109,7 @@ int read_until(char *buffer, char delim, int len)
* return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body
* otherwise return false
* */
bool resolve_pkg(char text[], int total_len, esp_ota_handle_t out_handle)
static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t out_handle)
{
/* i means current position */
int i = 0, i_read_len = 0;
@ -121,17 +121,10 @@ bool resolve_pkg(char text[], int total_len, esp_ota_handle_t out_handle)
memset(ota_write_data, 0, BUFFSIZE);
/*copy first http packet body to write buffer*/
memcpy(ota_write_data, &(text[i + 2]), i_write_len);
/*check write packet header first byte:0xE9 second byte:0x09 */
if (ota_write_data[0] == 0xE9 && i_write_len >= 2 && ota_write_data[1] == 0x09) {
ESP_LOGI(TAG, "OTA Write Header format Check OK. first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]);
} else {
ESP_LOGE(TAG, "OTA Write Header format Check Failed! first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]);
return false;
}
esp_err_t err = esp_ota_write( out_handle, (const void *)ota_write_data, i_write_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%x", err);
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
return false;
} else {
ESP_LOGI(TAG, "esp_ota_write header OK");
@ -266,7 +259,7 @@ void main_task(void *pvParameter)
task_fatal_error();
}
bool pkg_body_start = false, flag = true;
bool resp_body_start = false, flag = true;
/*deal with all receive packet*/
while (flag) {
memset(text, 0, TEXT_BUFFSIZE);
@ -275,14 +268,14 @@ void main_task(void *pvParameter)
if (buff_len < 0) { /*receive error*/
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
task_fatal_error();
} else if (buff_len > 0 && !pkg_body_start) { /*deal with packet header*/
} else if (buff_len > 0 && !resp_body_start) { /*deal with response header*/
memcpy(ota_write_data, text, buff_len);
pkg_body_start = resolve_pkg(text, buff_len, out_handle);
} else if (buff_len > 0 && pkg_body_start) { /*deal with packet body*/
resp_body_start = read_past_http_header(text, buff_len, out_handle);
} else if (buff_len > 0 && resp_body_start) { /*deal with response body*/
memcpy(ota_write_data, text, buff_len);
err = esp_ota_write( out_handle, (const void *)ota_write_data, buff_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%x", err);
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
task_fatal_error();
}
binary_file_length += buff_len;