kopia lustrzana https://github.com/espressif/esp-idf
Protect partition table by MD5 checksum
rodzic
8ef7434d55
commit
cf7a4cc650
|
@ -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 <string.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("<LL", tb[4:12])
|
||||
self.assertEqual(eo, 0x100400) # offset
|
||||
self.assertEqual(es, 0x300000) # size
|
||||
self.assertEqual(b"\xEB\xEB" + b"\xFF" * 14, tb[32:48])
|
||||
self.assertEqual(b'\x43\x03\x3f\x33\x40\x87\x57\x51\x69\x83\x9b\x40\x61\xb1\x27\x26', tb[48:64])
|
||||
|
||||
def test_multiple_entries(self):
|
||||
csv = """
|
||||
|
@ -182,7 +188,7 @@ second,0x31, 0xEF, , 0x100000
|
|||
"""
|
||||
t = PartitionTable.from_csv(csv)
|
||||
tb = _strip_trailing_ffs(t.to_binary())
|
||||
self.assertEqual(len(tb), 96)
|
||||
self.assertEqual(len(tb), 96+32)
|
||||
self.assertEqual(b'\xAA\x50', tb[0:2])
|
||||
self.assertEqual(b'\xAA\x50', tb[32:34])
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ Overview
|
|||
|
||||
A single ESP32's flash can contain multiple apps, as well as many different kinds of data (calibration data, filesystems, parameter storage, etc). For this reason a partition table is flashed to offset 0x8000 in the flash.
|
||||
|
||||
Partition table length is 0xC00 bytes (maximum 95 partition table entries). If the partition table is signed due to `secure boot`, the signature is appended after the table data.
|
||||
Partition table length is 0xC00 bytes (maximum 95 partition table entries). An MD5 checksum is appended after the table data. If the partition table is signed due to `secure boot`, the signature is appended after the partition table.
|
||||
|
||||
Each entry in the partition table has a name (label), type (app, data, or something else), subtype and the offset in flash where the partition is loaded.
|
||||
|
||||
|
@ -148,6 +148,11 @@ To display the contents of a binary partition table on stdout (this is how the s
|
|||
|
||||
``gen_esp32part.py`` takes one optional argument, ``--verify``, which will also verify the partition table during conversion (checking for overlapping partitions, unaligned partitions, etc.)
|
||||
|
||||
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.
|
||||
|
||||
Flashing the partition table
|
||||
----------------------------
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue