From cf7a4cc650e20cf5e725d970b5a9fc8c59722ce3 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Wed, 31 Jan 2018 14:45:12 +0100 Subject: [PATCH] Protect partition table by MD5 checksum --- .../bootloader_support/src/flash_partitions.c | 86 ++++++++++++------- .../esp32/include/esp_flash_data_types.h | 1 + components/partition_table/gen_esp32part.py | 12 +++ .../gen_esp32part_tests.py | 10 ++- docs/api-guides/partition-tables.rst | 7 +- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/components/bootloader_support/src/flash_partitions.c b/components/bootloader_support/src/flash_partitions.c index b60968f96..f8a24f26c 100644 --- a/components/bootloader_support/src/flash_partitions.c +++ b/components/bootloader_support/src/flash_partitions.c @@ -11,50 +11,76 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include #include "esp_flash_partitions.h" #include "esp_log.h" #include "rom/spi_flash.h" +#include "rom/md5_hash.h" +#include "esp_flash_data_types.h" static const char *TAG = "flash_parts"; esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions) { - int num_parts; - uint32_t chip_size = g_rom_flashchip.chip_size; - *num_partitions = 0; + int md5_found = 0; + int num_parts; + uint32_t chip_size = g_rom_flashchip.chip_size; + *num_partitions = 0; - for(num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) { - const esp_partition_info_t *part = &partition_table[num_parts]; + for (num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) { + const esp_partition_info_t *part = &partition_table[num_parts]; - if (part->magic == 0xFFFF - && part->type == PART_TYPE_END - && part->subtype == PART_SUBTYPE_END) { - /* TODO: check md5 */ - ESP_LOGD(TAG, "partition table verified, %d entries", num_parts); - *num_partitions = num_parts; - return ESP_OK; - } + if (part->magic == ESP_PARTITION_MAGIC) { + const esp_partition_pos_t *pos = &part->pos; + if (pos->offset > chip_size || pos->offset + pos->size > chip_size) { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x", + num_parts, pos->offset, pos->size, chip_size); + } + return ESP_ERR_INVALID_SIZE; + } + } else if (part->magic == ESP_PARTITION_MAGIC_MD5) { + if (md5_found) { + if (log_errors) { + ESP_LOGE(TAG, "Only one MD5 checksum is allowed"); + } + return ESP_ERR_INVALID_STATE; + } - if (part->magic != ESP_PARTITION_MAGIC) { - if (log_errors) { - ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); + struct MD5Context context; + unsigned char digest[16]; + MD5Init(&context); + MD5Update(&context, (unsigned char *) partition_table, num_parts * sizeof(esp_partition_info_t)); + MD5Final(digest, &context); + + unsigned char *md5sum = ((unsigned char *) part) + 16; // skip the 2B magic number and the 14B fillup bytes + + if (memcmp(md5sum, digest, sizeof(digest)) != 0) { + if (log_errors) { + ESP_LOGE(TAG, "Incorrect MD5 checksum"); + } + return ESP_ERR_INVALID_STATE; + } + //MD5 checksum matches and we continue with the next interation in + //order to detect the end of the partition table + md5_found = 1; + } else if (part->magic == 0xFFFF + && part->type == PART_TYPE_END + && part->subtype == PART_SUBTYPE_END) { + ESP_LOGD(TAG, "partition table verified, %d entries", num_parts); + *num_partitions = num_parts - md5_found; //do not count the partition where the MD5 checksum is held + return ESP_OK; + } else { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); + } + return ESP_ERR_INVALID_STATE; } - return ESP_ERR_INVALID_STATE; } - const esp_partition_pos_t *pos = &part->pos; - if (pos->offset > chip_size || pos->offset + pos->size > chip_size) { - if (log_errors) { - ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x", - num_parts, pos->offset, pos->size, chip_size); - } - return ESP_ERR_INVALID_SIZE; + if (log_errors) { + ESP_LOGE(TAG, "partition table has no terminating entry, not valid"); } - } - - if (log_errors) { - ESP_LOGE(TAG, "partition table has no terminating entry, not valid"); - } - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } diff --git a/components/esp32/include/esp_flash_data_types.h b/components/esp32/include/esp_flash_data_types.h index cb6dfc3a1..3e44b2639 100644 --- a/components/esp32/include/esp_flash_data_types.h +++ b/components/esp32/include/esp_flash_data_types.h @@ -23,6 +23,7 @@ extern "C" #define ESP_PARTITION_TABLE_ADDR 0x8000 #define ESP_PARTITION_MAGIC 0x50AA +#define ESP_PARTITION_MAGIC_MD5 0xEBEB /* OTA selection structure (two copies in the OTA data partition.) Size of 32 bytes is friendly to flash encryption */ diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index 897e637d0..7b80cabf8 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -26,8 +26,11 @@ import os import re import struct import sys +import hashlib +import binascii MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature +MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum __version__ = '1.0' @@ -112,6 +115,7 @@ class PartitionTable(list): @classmethod def from_binary(cls, b): + md5 = hashlib.md5(); result = cls() for o in range(0,len(b),32): data = b[o:o+32] @@ -119,11 +123,19 @@ class PartitionTable(list): raise InputError("Partition table length must be a multiple of 32 bytes") if data == b'\xFF'*32: return result # got end marker + if data[:2] == MD5_PARTITION_BEGIN[:2]: #check only the magic number part + if data[16:] == md5.digest(): + continue # the next iteration will check for the end marker + else: + raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:]))) + else: + md5.update(data) result.append(PartitionDefinition.from_binary(data)) raise InputError("Partition table is missing an end-of-table marker") def to_binary(self): result = b"".join(e.to_binary() for e in self) + result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest() if len(result )>= MAX_PARTITION_LENGTH: raise InputError("Binary partition table length (%d) longer than max" % len(result)) result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing diff --git a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py index 46fe45c22..4919a53d8 100755 --- a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py +++ b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py @@ -37,6 +37,10 @@ LONGER_BINARY_TABLE += b"\xAA\x50\x10\x00" + \ b"\x00\x10\x00\x00" + \ b"second" + (b"\0"*10) + \ b"\x00\x00\x00\x00" +# MD5 checksum +LONGER_BINARY_TABLE += b"\xEB\xEB" + b"\xFF" * 14 +LONGER_BINARY_TABLE += b'\xf9\xbd\x06\x1b\x45\x68\x6f\x86\x57\x1a\x2c\xd5\x2a\x1d\xa6\x5b' +# empty partition LONGER_BINARY_TABLE += b"\xFF" * 32 @@ -168,12 +172,14 @@ first, 0x30, 0xEE, 0x100400, 0x300000 """ t = PartitionTable.from_csv(csv) tb = _strip_trailing_ffs(t.to_binary()) - self.assertEqual(len(tb), 64) + self.assertEqual(len(tb), 64+32) self.assertEqual(b'\xAA\x50', tb[0:2]) # magic self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype eo, es = struct.unpack("