]> granicus.if.org Git - esp-idf/commitdiff
Protect partition table by MD5 checksum
authorRoland Dobai <dobai.roland@gmail.com>
Wed, 31 Jan 2018 13:45:12 +0000 (14:45 +0100)
committerRoland Dobai <dobai.roland@gmail.com>
Mon, 5 Feb 2018 10:36:03 +0000 (11:36 +0100)
components/bootloader_support/src/flash_partitions.c
components/esp32/include/esp_flash_data_types.h
components/partition_table/gen_esp32part.py
components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py
docs/api-guides/partition-tables.rst

index b60968f969609b05db9b66aef0fedbd6cd7b1672..f8a24f26c2bdb49c2a70a697a2833eff2263ccdd 100644 (file)
 // 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;
 }
 
index cb6dfc3a1f0189ffd5c60b1a2b26e24f1dd4503b..3e44b2639dc2ebe69a72ade1a26e4a4eec2841a5 100644 (file)
@@ -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 */
index 897e637d0794ef2262611a4f246e0ec5080d505b..7b80cabf8fb1ff99cde5559aead3f84d4609e555 100755 (executable)
@@ -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
index 46fe45c228516b8b0560b2d1e8013ec84709d67b..4919a53d8784447ce1401d51ff9dd11dc93e168f 100755 (executable)
@@ -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])
 
index 6a5c67669138a7cba12ee82723958df29d714bdc..ee88631232a15d21b8ad6cd21b8fc98d621a9252 100644 (file)
@@ -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
 ----------------------------