// 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;
-
- 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;
- }
+ int md5_found = 0;
+ int num_parts;
+ uint32_t chip_size = g_rom_flashchip.chip_size;
+ *num_partitions = 0;
- if (part->magic != ESP_PARTITION_MAGIC) {
- if (log_errors) {
- ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic);
- }
- return ESP_ERR_INVALID_STATE;
- }
+ 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 == 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;
+ }
- 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);
+ 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_SIZE;
}
- }
- if (log_errors) {
- ESP_LOGE(TAG, "partition table has no terminating entry, not valid");
- }
- return ESP_ERR_INVALID_STATE;
+ if (log_errors) {
+ ESP_LOGE(TAG, "partition table has no terminating entry, not valid");
+ }
+ return ESP_ERR_INVALID_STATE;
}
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'
@classmethod
def from_binary(cls, b):
+ md5 = hashlib.md5();
result = cls()
for o in range(0,len(b),32):
data = b[o:o+32]
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
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
"""
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 = """
"""
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])
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.
``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
----------------------------