Skip to content

Commit 8173d42

Browse files
committed
Merge branch 'feature/partition_table_md5' into 'master'
Partition table md5 check See merge request idf/esp-idf!1891
2 parents c1c4f33 + cf7a4cc commit 8173d42

File tree

5 files changed

+85
-35
lines changed

5 files changed

+85
-35
lines changed

components/bootloader_support/src/flash_partitions.c

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,50 +11,76 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
#include <string.h>
1415
#include "esp_flash_partitions.h"
1516
#include "esp_log.h"
1617
#include "rom/spi_flash.h"
18+
#include "rom/md5_hash.h"
19+
#include "esp_flash_data_types.h"
1720

1821
static const char *TAG = "flash_parts";
1922

2023
esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions)
2124
{
22-
int num_parts;
23-
uint32_t chip_size = g_rom_flashchip.chip_size;
24-
*num_partitions = 0;
25-
26-
for(num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) {
27-
const esp_partition_info_t *part = &partition_table[num_parts];
28-
29-
if (part->magic == 0xFFFF
30-
&& part->type == PART_TYPE_END
31-
&& part->subtype == PART_SUBTYPE_END) {
32-
/* TODO: check md5 */
33-
ESP_LOGD(TAG, "partition table verified, %d entries", num_parts);
34-
*num_partitions = num_parts;
35-
return ESP_OK;
36-
}
25+
int md5_found = 0;
26+
int num_parts;
27+
uint32_t chip_size = g_rom_flashchip.chip_size;
28+
*num_partitions = 0;
3729

38-
if (part->magic != ESP_PARTITION_MAGIC) {
39-
if (log_errors) {
40-
ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic);
41-
}
42-
return ESP_ERR_INVALID_STATE;
43-
}
30+
for (num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) {
31+
const esp_partition_info_t *part = &partition_table[num_parts];
32+
33+
if (part->magic == ESP_PARTITION_MAGIC) {
34+
const esp_partition_pos_t *pos = &part->pos;
35+
if (pos->offset > chip_size || pos->offset + pos->size > chip_size) {
36+
if (log_errors) {
37+
ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x",
38+
num_parts, pos->offset, pos->size, chip_size);
39+
}
40+
return ESP_ERR_INVALID_SIZE;
41+
}
42+
} else if (part->magic == ESP_PARTITION_MAGIC_MD5) {
43+
if (md5_found) {
44+
if (log_errors) {
45+
ESP_LOGE(TAG, "Only one MD5 checksum is allowed");
46+
}
47+
return ESP_ERR_INVALID_STATE;
48+
}
4449

45-
const esp_partition_pos_t *pos = &part->pos;
46-
if (pos->offset > chip_size || pos->offset + pos->size > chip_size) {
47-
if (log_errors) {
48-
ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x",
49-
num_parts, pos->offset, pos->size, chip_size);
50+
struct MD5Context context;
51+
unsigned char digest[16];
52+
MD5Init(&context);
53+
MD5Update(&context, (unsigned char *) partition_table, num_parts * sizeof(esp_partition_info_t));
54+
MD5Final(digest, &context);
55+
56+
unsigned char *md5sum = ((unsigned char *) part) + 16; // skip the 2B magic number and the 14B fillup bytes
57+
58+
if (memcmp(md5sum, digest, sizeof(digest)) != 0) {
59+
if (log_errors) {
60+
ESP_LOGE(TAG, "Incorrect MD5 checksum");
61+
}
62+
return ESP_ERR_INVALID_STATE;
63+
}
64+
//MD5 checksum matches and we continue with the next interation in
65+
//order to detect the end of the partition table
66+
md5_found = 1;
67+
} else if (part->magic == 0xFFFF
68+
&& part->type == PART_TYPE_END
69+
&& part->subtype == PART_SUBTYPE_END) {
70+
ESP_LOGD(TAG, "partition table verified, %d entries", num_parts);
71+
*num_partitions = num_parts - md5_found; //do not count the partition where the MD5 checksum is held
72+
return ESP_OK;
73+
} else {
74+
if (log_errors) {
75+
ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic);
76+
}
77+
return ESP_ERR_INVALID_STATE;
5078
}
51-
return ESP_ERR_INVALID_SIZE;
5279
}
53-
}
5480

55-
if (log_errors) {
56-
ESP_LOGE(TAG, "partition table has no terminating entry, not valid");
57-
}
58-
return ESP_ERR_INVALID_STATE;
81+
if (log_errors) {
82+
ESP_LOGE(TAG, "partition table has no terminating entry, not valid");
83+
}
84+
return ESP_ERR_INVALID_STATE;
5985
}
6086

components/esp32/include/esp_flash_data_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extern "C"
2323

2424
#define ESP_PARTITION_TABLE_ADDR 0x8000
2525
#define ESP_PARTITION_MAGIC 0x50AA
26+
#define ESP_PARTITION_MAGIC_MD5 0xEBEB
2627

2728
/* OTA selection structure (two copies in the OTA data partition.)
2829
Size of 32 bytes is friendly to flash encryption */

components/partition_table/gen_esp32part.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
import re
2727
import struct
2828
import sys
29+
import hashlib
30+
import binascii
2931

3032
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
33+
MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum
3134

3235
__version__ = '1.0'
3336

@@ -112,18 +115,27 @@ def verify(self):
112115

113116
@classmethod
114117
def from_binary(cls, b):
118+
md5 = hashlib.md5();
115119
result = cls()
116120
for o in range(0,len(b),32):
117121
data = b[o:o+32]
118122
if len(data) != 32:
119123
raise InputError("Partition table length must be a multiple of 32 bytes")
120124
if data == b'\xFF'*32:
121125
return result # got end marker
126+
if data[:2] == MD5_PARTITION_BEGIN[:2]: #check only the magic number part
127+
if data[16:] == md5.digest():
128+
continue # the next iteration will check for the end marker
129+
else:
130+
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
131+
else:
132+
md5.update(data)
122133
result.append(PartitionDefinition.from_binary(data))
123134
raise InputError("Partition table is missing an end-of-table marker")
124135

125136
def to_binary(self):
126137
result = b"".join(e.to_binary() for e in self)
138+
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
127139
if len(result )>= MAX_PARTITION_LENGTH:
128140
raise InputError("Binary partition table length (%d) longer than max" % len(result))
129141
result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing

components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
b"\x00\x10\x00\x00" + \
3838
b"second" + (b"\0"*10) + \
3939
b"\x00\x00\x00\x00"
40+
# MD5 checksum
41+
LONGER_BINARY_TABLE += b"\xEB\xEB" + b"\xFF" * 14
42+
LONGER_BINARY_TABLE += b'\xf9\xbd\x06\x1b\x45\x68\x6f\x86\x57\x1a\x2c\xd5\x2a\x1d\xa6\x5b'
43+
# empty partition
4044
LONGER_BINARY_TABLE += b"\xFF" * 32
4145

4246

@@ -168,12 +172,14 @@ def test_binary_entry(self):
168172
"""
169173
t = PartitionTable.from_csv(csv)
170174
tb = _strip_trailing_ffs(t.to_binary())
171-
self.assertEqual(len(tb), 64)
175+
self.assertEqual(len(tb), 64+32)
172176
self.assertEqual(b'\xAA\x50', tb[0:2]) # magic
173177
self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype
174178
eo, es = struct.unpack("<LL", tb[4:12])
175179
self.assertEqual(eo, 0x100400) # offset
176180
self.assertEqual(es, 0x300000) # size
181+
self.assertEqual(b"\xEB\xEB" + b"\xFF" * 14, tb[32:48])
182+
self.assertEqual(b'\x43\x03\x3f\x33\x40\x87\x57\x51\x69\x83\x9b\x40\x61\xb1\x27\x26', tb[48:64])
177183

178184
def test_multiple_entries(self):
179185
csv = """
@@ -182,7 +188,7 @@ def test_multiple_entries(self):
182188
"""
183189
t = PartitionTable.from_csv(csv)
184190
tb = _strip_trailing_ffs(t.to_binary())
185-
self.assertEqual(len(tb), 96)
191+
self.assertEqual(len(tb), 96+32)
186192
self.assertEqual(b'\xAA\x50', tb[0:2])
187193
self.assertEqual(b'\xAA\x50', tb[32:34])
188194

docs/api-guides/partition-tables.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Overview
66

77
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.
88

9-
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.
9+
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.
1010

1111
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.
1212

@@ -148,6 +148,11 @@ To display the contents of a binary partition table on stdout (this is how the s
148148

149149
``gen_esp32part.py`` takes one optional argument, ``--verify``, which will also verify the partition table during conversion (checking for overlapping partitions, unaligned partitions, etc.)
150150

151+
MD5 checksum
152+
~~~~~~~~~~~~
153+
154+
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.
155+
151156
Flashing the partition table
152157
----------------------------
153158

0 commit comments

Comments
 (0)