Skip to content

Commit a1c1204

Browse files
committed
Fix double swap on interrupted revert
This fixes #480. When mcuboot rewrites image trailers during a swap, some information is lost. If a reset occurs before the swap completes, mcuboot may not be able to determine what which swap type to resume upon startup. Specifically, if a "revert" swap gets interupted, mcuboot will perform an extraneous swap on the subsequent boot. See mcu-tools/mcuboot#480 for details. This commit adds an additional field to the image trailer: `swap-type`. The new trailer structure is illustrated below: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ ~ ~ Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * 3) ~ ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ Encryption key 0 (16 octets) [*] ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ Encryption key 1 (16 octets) [*] ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Swap size | 0xff padding (4 octets) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Swap type | 0xff padding (7 octets) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Copy done | 0xff padding (7 octets) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Image OK | 0xff padding (7 octets) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ MAGIC (16 octets) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The `swap-type` field contains one of the `BOOT_SWAP_TYPE_[...]` constants. Every time a trailer is written, this field is written along with it. When resuming an interrupted swap, mcuboot uses this field alone to determine the type of swap being resumed. For new swap operations (non-resume case), this field is not read at all; instead, mcuboot consults the `boot_swap_tables` array to determine the swap operation to perform (as it did prior to this commit). Some additional changes were necessary to make all the simulated unit tests pass: * Before initiating a new swap operation, always write the image trailer to the scratch area. This step allows mcuboot to persist the `swap-type` field somewhere before erasing the trailer in the primary slot. If a reset occurs immediately after the erase, mcuboot recovers by using the trailer in the scratch area. * Related to the above: if the scratch area is being used to hold status bytes (because there are no spare sectors in the primary slot), erase the scratch area immediately after the trailer gets written to the primary slot. This eliminates ambiguity regarding the location of the current trailer in case a reset occurs shortly afterwards. Signed-off-by: Christopher Collins <[email protected]>
1 parent 2c88e69 commit a1c1204

File tree

6 files changed

+282
-150
lines changed

6 files changed

+282
-150
lines changed

boot/bootutil/include/bootutil/bootutil.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ struct boot_rsp {
7979
* when attempting to read/write a trailer.
8080
*/
8181
struct image_trailer {
82-
uint8_t copy_done;
82+
uint8_t swap_type;
8383
uint8_t pad1[MAX_FLASH_ALIGN - 1];
84-
uint8_t image_ok;
84+
uint8_t copy_done;
8585
uint8_t pad2[MAX_FLASH_ALIGN - 1];
86+
uint8_t image_ok;
87+
uint8_t pad3[MAX_FLASH_ALIGN - 1];
8688
uint8_t magic[16];
8789
};
8890

boot/bootutil/src/bootutil_misc.c

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,32 @@ boot_flag_decode(uint8_t flag)
119119
return BOOT_FLAG_SET;
120120
}
121121

122+
/**
123+
* Determines if a status source table is satisfied by the specified magic
124+
* code.
125+
*
126+
* @param tbl_val A magic field from a status source table.
127+
* @param val The magic value in a trailer, encoded as a
128+
* BOOT_MAGIC_[...].
129+
*
130+
* @return 1 if the two values are compatible;
131+
* 0 otherwise.
132+
*/
133+
int
134+
boot_magic_compatible_check(uint8_t tbl_val, uint8_t val)
135+
{
136+
switch (tbl_val) {
137+
case BOOT_MAGIC_ANY:
138+
return 1;
139+
140+
case BOOT_MAGIC_NOTGOOD:
141+
return val != BOOT_MAGIC_GOOD;
142+
143+
default:
144+
return tbl_val == val;
145+
}
146+
}
147+
122148
uint32_t
123149
boot_trailer_sz(uint8_t min_write_sz)
124150
{
@@ -136,7 +162,6 @@ boot_trailer_sz(uint8_t min_write_sz)
136162
static uint32_t
137163
boot_magic_off(const struct flash_area *fap)
138164
{
139-
assert(offsetof(struct image_trailer, magic) == 16);
140165
return fap->fa_size - BOOT_MAGIC_SZ;
141166
}
142167

@@ -168,17 +193,21 @@ boot_status_off(const struct flash_area *fap)
168193
return fap->fa_size - off_from_end;
169194
}
170195

196+
uint32_t
197+
boot_swap_type_off(const struct flash_area *fap)
198+
{
199+
return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 3;
200+
}
201+
171202
static uint32_t
172203
boot_copy_done_off(const struct flash_area *fap)
173204
{
174-
assert(offsetof(struct image_trailer, copy_done) == 0);
175205
return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 2;
176206
}
177207

178208
static uint32_t
179209
boot_image_ok_off(const struct flash_area *fap)
180210
{
181-
assert(offsetof(struct image_trailer, image_ok) == 8);
182211
return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN;
183212
}
184213

@@ -216,6 +245,16 @@ boot_read_swap_state(const struct flash_area *fap,
216245
state->magic = boot_magic_decode(magic);
217246
}
218247

248+
off = boot_swap_type_off(fap);
249+
rc = flash_area_read_is_empty(fap, off, &state->swap_type,
250+
sizeof state->swap_type);
251+
if (rc < 0) {
252+
return BOOT_EFLASH;
253+
}
254+
if (rc == 1 || state->swap_type > BOOT_SWAP_TYPE_REVERT) {
255+
state->swap_type = BOOT_SWAP_TYPE_NONE;
256+
}
257+
219258
off = boot_copy_done_off(fap);
220259
rc = flash_area_read_is_empty(fap, off, &state->copy_done,
221260
sizeof state->copy_done);
@@ -405,34 +444,19 @@ boot_write_magic(const struct flash_area *fap)
405444
}
406445

407446
static int
408-
boot_write_flag(int flag, const struct flash_area *fap)
447+
boot_write_trailer_byte(const struct flash_area *fap, uint32_t off,
448+
uint8_t val)
409449
{
410-
uint32_t off;
411-
int rc;
412450
uint8_t buf[BOOT_MAX_ALIGN];
413451
uint8_t align;
414452
uint8_t erased_val;
415-
416-
switch (flag) {
417-
case BOOT_FLAG_COPY_DONE:
418-
off = boot_copy_done_off(fap);
419-
BOOT_LOG_DBG("writing copy_done; fa_id=%d off=0x%x (0x%x)",
420-
fap->fa_id, off, fap->fa_off + off);
421-
break;
422-
case BOOT_FLAG_IMAGE_OK:
423-
off = boot_image_ok_off(fap);
424-
BOOT_LOG_DBG("writing image_ok; fa_id=%d off=0x%x (0x%x)",
425-
fap->fa_id, off, fap->fa_off + off);
426-
break;
427-
default:
428-
return BOOT_EBADARGS;
429-
}
453+
int rc;
430454

431455
align = flash_area_align(fap);
432456
assert(align <= BOOT_MAX_ALIGN);
433457
erased_val = flash_area_erased_val(fap);
434458
memset(buf, erased_val, BOOT_MAX_ALIGN);
435-
buf[0] = BOOT_FLAG_SET;
459+
buf[0] = val;
436460

437461
rc = flash_area_write(fap, off, buf, align);
438462
if (rc != 0) {
@@ -445,13 +469,39 @@ boot_write_flag(int flag, const struct flash_area *fap)
445469
int
446470
boot_write_copy_done(const struct flash_area *fap)
447471
{
448-
return boot_write_flag(BOOT_FLAG_COPY_DONE, fap);
472+
uint32_t off;
473+
474+
off = boot_copy_done_off(fap);
475+
BOOT_LOG_DBG("writing copy_done; fa_id=%d off=0x%x (0x%x)",
476+
fap->fa_id, off, fap->fa_off + off);
477+
return boot_write_trailer_byte(fap, off, BOOT_FLAG_SET);
449478
}
450479

451480
int
452481
boot_write_image_ok(const struct flash_area *fap)
453482
{
454-
return boot_write_flag(BOOT_FLAG_IMAGE_OK, fap);
483+
uint32_t off;
484+
485+
off = boot_image_ok_off(fap);
486+
BOOT_LOG_DBG("writing image_ok; fa_id=%d off=0x%x (0x%x)",
487+
fap->fa_id, off, fap->fa_off + off);
488+
return boot_write_trailer_byte(fap, off, BOOT_FLAG_SET);
489+
}
490+
491+
/**
492+
* Writes the specified value to the `swap-type` field of an image trailer.
493+
* This value is persisted so that the boot loader knows what swap operation to
494+
* resume in case of an unexpected reset.
495+
*/
496+
int
497+
boot_write_swap_type(const struct flash_area *fap, uint8_t swap_type)
498+
{
499+
uint32_t off;
500+
501+
off = boot_swap_type_off(fap);
502+
BOOT_LOG_DBG("writing swap_type; fa_id=%d off=0x%x (0x%x), swap_type=0x%x",
503+
fap->fa_id, off, fap->fa_off + off, swap_type);
504+
return boot_write_trailer_byte(fap, off, swap_type);
455505
}
456506

457507
int
@@ -524,10 +574,10 @@ boot_swap_type(void)
524574
for (i = 0; i < BOOT_SWAP_TABLES_COUNT; i++) {
525575
table = boot_swap_tables + i;
526576

527-
if ((table->magic_primary_slot == BOOT_MAGIC_ANY ||
528-
table->magic_primary_slot == primary_slot.magic) &&
529-
(table->magic_secondary_slot == BOOT_MAGIC_ANY ||
530-
table->magic_secondary_slot == secondary_slot.magic) &&
577+
if (boot_magic_compatible_check(table->magic_primary_slot,
578+
primary_slot.magic) &&
579+
boot_magic_compatible_check(table->magic_secondary_slot,
580+
secondary_slot.magic) &&
531581
(table->image_ok_primary_slot == BOOT_FLAG_ANY ||
532582
table->image_ok_primary_slot == primary_slot.image_ok) &&
533583
(table->image_ok_secondary_slot == BOOT_FLAG_ANY ||
@@ -566,6 +616,7 @@ boot_set_pending(int permanent)
566616
{
567617
const struct flash_area *fap;
568618
struct boot_swap_state state_secondary_slot;
619+
uint8_t swap_type;
569620
int rc;
570621

571622
rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY,
@@ -591,6 +642,15 @@ boot_set_pending(int permanent)
591642
rc = boot_write_image_ok(fap);
592643
}
593644

645+
if (rc == 0) {
646+
if (permanent) {
647+
swap_type = BOOT_SWAP_TYPE_PERM;
648+
} else {
649+
swap_type = BOOT_SWAP_TYPE_TEST;
650+
}
651+
rc = boot_write_swap_type(fap, swap_type);
652+
}
653+
594654
flash_area_close(fap);
595655
return rc;
596656

boot/bootutil/src/bootutil_priv.h

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct boot_status {
6161
uint32_t idx; /* Which area we're operating on */
6262
uint8_t state; /* Which part of the swapping process are we at */
6363
uint8_t use_scratch; /* Are status bytes ever written to scratch? */
64+
uint8_t swap_type; /* The type of swap in effect */
6465
uint32_t swap_size; /* Total size of swapped image */
6566
#ifdef MCUBOOT_ENC_IMAGES
6667
uint8_t enckey[2][BOOT_ENC_KEY_SIZE];
@@ -71,6 +72,7 @@ struct boot_status {
7172
#define BOOT_MAGIC_BAD 2
7273
#define BOOT_MAGIC_UNSET 3
7374
#define BOOT_MAGIC_ANY 4 /* NOTE: control only, not dependent on sector */
75+
#define BOOT_MAGIC_NOTGOOD 5 /* NOTE: control only, not dependent on sector */
7476

7577
/*
7678
* NOTE: leave BOOT_FLAG_SET equal to one, this is written to flash!
@@ -89,31 +91,42 @@ struct boot_status {
8991
/**
9092
* End-of-image slot structure.
9193
*
92-
* 0 1 2 3
93-
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
94-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
95-
* ~ ~
96-
* ~ Swap status (variable, aligned) ~
97-
* ~ ~
98-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
99-
* | Swap size |
100-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101-
* ~ padding with erased val (MAX ALIGN - 4) ~
102-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
103-
* | Copy done | padding with erased val (MAX ALIGN - 1) ~
104-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
105-
* | Image OK | padding with erased val (MAX ALIGN - 1) ~
106-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
107-
* ~ MAGIC (16 octets) ~
108-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
94+
* 0 1 2 3
95+
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
96+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97+
* ~ ~
98+
* ~ Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * 3) ~
99+
* ~ ~
100+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101+
* | Encryption key 0 (16 octets) [*] |
102+
* | |
103+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104+
* | Encryption key 1 (16 octets) [*] |
105+
* | |
106+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
107+
* | Swap size (4 octets) |
108+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
109+
* | Swap type | 0xff padding (7 octets) |
110+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
111+
* | Copy done | 0xff padding (7 octets) |
112+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
113+
* | Image OK | 0xff padding (7 octets) |
114+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
115+
* | MAGIC (16 octets) |
116+
* | |
117+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
118+
*
119+
* [*]: Only present if the encryption option is enabled
120+
* (`MCUBOOT_ENC_IMAGES`).
109121
*/
110122

111123
extern const uint32_t boot_img_magic[4];
112124

113125
struct boot_swap_state {
114-
uint8_t magic; /* One of the BOOT_MAGIC_[...] values. */
115-
uint8_t copy_done;
116-
uint8_t image_ok;
126+
uint8_t magic; /* One of the BOOT_MAGIC_[...] values. */
127+
uint8_t swap_type; /* One of the BOOT_SWAP_TYPE_[...] values. */
128+
uint8_t copy_done; /* One of the BOOT_FLAG_[...] values. */
129+
uint8_t image_ok; /* One of the BOOT_FLAG_[...] values. */
117130
};
118131

119132
#define BOOT_MAX_IMG_SECTORS MCUBOOT_MAX_IMG_SECTORS
@@ -143,9 +156,6 @@ struct boot_swap_state {
143156
#define BOOT_STATUS_SOURCE_SCRATCH 1
144157
#define BOOT_STATUS_SOURCE_PRIMARY_SLOT 2
145158

146-
#define BOOT_FLAG_IMAGE_OK 0
147-
#define BOOT_FLAG_COPY_DONE 1
148-
149159
extern const uint32_t BOOT_MAGIC_SZ;
150160

151161
/**
@@ -180,9 +190,11 @@ struct boot_loader_state {
180190
int bootutil_verify_sig(uint8_t *hash, uint32_t hlen, uint8_t *sig,
181191
size_t slen, uint8_t key_id);
182192

193+
int boot_magic_compatible_check(uint8_t tbl_val, uint8_t val);
183194
uint32_t boot_trailer_sz(uint8_t min_write_sz);
184195
int boot_status_entries(const struct flash_area *fap);
185196
uint32_t boot_status_off(const struct flash_area *fap);
197+
uint32_t boot_swap_type_off(const struct flash_area *fap);
186198
int boot_read_swap_state(const struct flash_area *fap,
187199
struct boot_swap_state *state);
188200
int boot_read_swap_state_by_id(int flash_area_id,
@@ -192,6 +204,7 @@ int boot_write_status(struct boot_status *bs);
192204
int boot_schedule_test_swap(void);
193205
int boot_write_copy_done(const struct flash_area *fap);
194206
int boot_write_image_ok(const struct flash_area *fap);
207+
int boot_write_swap_type(const struct flash_area *fap, uint8_t swap_type);
195208
int boot_write_swap_size(const struct flash_area *fap, uint32_t swap_size);
196209
int boot_read_swap_size(uint32_t *swap_size);
197210
#ifdef MCUBOOT_ENC_IMAGES

0 commit comments

Comments
 (0)