-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
The changes added to save and restore the data at the bootloader "magic key" location in RAM are flawed. These changes were made in a commit on Apr 6, 2016 and discussed in issue arduino/Arduino#2474.
The code is located in file CDC.cpp, which as of this writing is here.
The problem is that data can be restored to the magic key location even if it hasn't been saved in the first place, possibly overwriting data that the sketch has put there. This is because received USB information can cause the else at line 132 to execute before the if statement has ever become true.
The following sketch demonstrates the problem, when compiled under IDE 1.8.1 with core version 1.6.17, for a Leonardo or other ATMega32u4 based board. overlapArray[] has been set to sit over the magic key location at RAM address 0x800. If the IDE serial monitor is opened, the printed data will show that the array data has been corrupted with the value that was at RAMEND - 1.
// Bootloader magic key overlap test
// allocate some global variable space so that...
uint8_t globalArray[1698];
// this array will overlap the bootloader magic key
uint8_t overlapArray[8];
void setup() {
Serial.begin(9600);
// fill the array to make sure it's not optimised out
for (uint16_t i = 0; i < sizeof(globalArray); i++) {
globalArray[i] = i;
}
// fill the overlap array with F0 F1 F2 F3 F4 F5 F6 F7
for (uint8_t i = 0; i < sizeof(overlapArray); i++) {
overlapArray[i] = i + 0xF0;
}
delay(5000);
}
void loop() {
Serial.print("\noverlapArray =");
for (uint8_t i = 0; i < sizeof(overlapArray); i++) {
Serial.print(" 0x");
Serial.print(overlapArray[i], HEX);
}
Serial.print("\n*(RAMEND-1) = 0x");
Serial.println(*(uint16_t *) (RAMEND-1), HEX);
delay(2000);
}
I've come up with a proposal to address the problem:
-
Only inintiate the boot sequence (signaled by baudrate = 1200 and DTR off) if DTR was previously on.
-
Only restore the data if it was previously saved. Conversly, only save data if previously saved data has been restored.
This change requires 2 boolean global variables to be added and creates 54 more bytes of code.
A unified diff against the CDC.cpp file above follows. I can create a pull request for it if desired.
--- CDC.cpp 2016-07-01 07:16:50.000000000 -0400
+++ CDC.cpp.new 2017-03-02 13:10:11.498486656 -0500
@@ -36,6 +36,12 @@
bool _updatedLUFAbootloader = false;
+static bool boot_allowed = false;
+
+#if MAGIC_KEY_POS != (RAMEND-1)
+static bool magic_key_saved = false;
+#endif
+
#define WEAK __attribute__ ((weak))
extern const CDCDescriptor _cdcInterface PROGMEM;
@@ -116,18 +122,20 @@
#endif
// We check DTR state to determine if host port is open (bit 0 of lineState).
- if (1200 == _usbLineInfo.dwDTERate && (_usbLineInfo.lineState & 0x01) == 0)
+ if (1200 == _usbLineInfo.dwDTERate && (_usbLineInfo.lineState & 0x01) == 0 && boot_allowed)
{
#if MAGIC_KEY_POS != (RAMEND-1)
// Backup ram value if its not a newer bootloader.
// This should avoid memory corruption at least a bit, not fully
- if (magic_key_pos != (RAMEND-1)) {
+ if (magic_key_pos != (RAMEND-1) && !magic_key_saved) {
*(uint16_t *)(RAMEND-1) = *(uint16_t *)magic_key_pos;
+ magic_key_saved = true;
}
#endif
// Store boot key
*(uint16_t *)magic_key_pos = MAGIC_KEY;
wdt_enable(WDTO_120MS);
+ boot_allowed = false;
}
else
{
@@ -135,13 +143,19 @@
// twiggle more than once before stabilizing.
// To avoid spurious resets we set the watchdog to 250ms and eventually
// cancel if DTR goes back high.
-
+ if ((_usbLineInfo.lineState & 0x01) == 1) {
+ boot_allowed = true;
+ }
wdt_disable();
wdt_reset();
#if MAGIC_KEY_POS != (RAMEND-1)
// Restore backed up (old bootloader) magic key data
if (magic_key_pos != (RAMEND-1)) {
- *(uint16_t *)magic_key_pos = *(uint16_t *)(RAMEND-1);
+ if (magic_key_saved)
+ {
+ magic_key_saved = false;
+ *(uint16_t *)magic_key_pos = *(uint16_t *)(RAMEND-1);
+ }
} else
#endif
{
Note that there is still a potential for boot problems if certain system variables end up overlapping the magic key location, such as _updatedLUFAbootloader, the new flags, or other USB storage, because they could be overwritten by the magic key (0x7777), or overwrite the magic key, during the watchdog timer window. This could cause incorrect program flow or corrupted USB data.
A solution for _updatedLUFAbootloader would be to not use it. Instead, test for NEW_LUFA_SIGNATURE directly in program space at CDC.cpp line 112.
For the others, I don't see that much can be done unless these variables can somehow be placed in an area guaranteed not to overlap the magic key.
As before, there's also a chance that the sketch could change a variable at the magic number location during the watchdog window and then have it set back to the saved value if the boot sequence is aborted and the watchdog is reset before triggering.