working upgrade! (for esp32 classic)

This commit is contained in:
Will Tatam
2025-11-09 11:17:59 +00:00
parent c5631b8fe3
commit 34445dbe0f
2 changed files with 106 additions and 17 deletions

View File

@@ -265,7 +265,8 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data,
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA) #if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
// Verify complete buffered bootloader using ESP-IDF validation approach // Verify complete buffered bootloader using ESP-IDF validation approach
// This matches the key validation steps from esp_image_verify() in ESP-IDF // This matches the key validation steps from esp_image_verify() in ESP-IDF
bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloaderErrorMsg) { // Returns the actual bootloader data pointer and length via the buffer and len parameters
bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg) {
if (!bootloaderErrorMsg) { if (!bootloaderErrorMsg) {
DEBUG_PRINTLN(F("bootloaderErrorMsg is null")); DEBUG_PRINTLN(F("bootloaderErrorMsg is null"));
return false; return false;
@@ -284,6 +285,7 @@ bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloader
// Offset 23: hash_appended // Offset 23: hash_appended
const size_t MIN_IMAGE_HEADER_SIZE = 24; const size_t MIN_IMAGE_HEADER_SIZE = 24;
const size_t BOOTLOADER_OFFSET = 0x1000;
// 1. Validate minimum size for header // 1. Validate minimum size for header
if (len < MIN_IMAGE_HEADER_SIZE) { if (len < MIN_IMAGE_HEADER_SIZE) {
@@ -291,9 +293,26 @@ bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloader
return false; return false;
} }
// Check if the bootloader starts at offset 0x1000 (common in partition table dumps)
// This happens when someone uploads a complete flash dump instead of just the bootloader
if (len > BOOTLOADER_OFFSET + MIN_IMAGE_HEADER_SIZE &&
buffer[BOOTLOADER_OFFSET] == 0xE9 &&
buffer[0] != 0xE9) {
DEBUG_PRINTF_P(PSTR("Bootloader magic byte detected at offset 0x%04X - adjusting buffer\n"), BOOTLOADER_OFFSET);
// Adjust buffer pointer to start at the actual bootloader
buffer = buffer + BOOTLOADER_OFFSET;
len = len - BOOTLOADER_OFFSET;
// Re-validate size after adjustment
if (len < MIN_IMAGE_HEADER_SIZE) {
*bootloaderErrorMsg = "Bootloader at offset 0x1000 too small - invalid header";
return false;
}
}
// 2. Magic byte check (matches esp_image_verify step 1) // 2. Magic byte check (matches esp_image_verify step 1)
if (buffer[0] != 0xE9) { if (buffer[0] != 0xE9) {
*bootloaderErrorMsg = "Invalid bootloader magic byte"; *bootloaderErrorMsg = "Invalid bootloader magic byte (expected 0xE9, got 0x" + String(buffer[0], HEX) + ")";
return false; return false;
} }
@@ -378,12 +397,15 @@ bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloader
// 7. Basic segment structure validation // 7. Basic segment structure validation
// Each segment has a header: load_addr (4 bytes) + data_len (4 bytes) // Each segment has a header: load_addr (4 bytes) + data_len (4 bytes)
size_t offset = MIN_IMAGE_HEADER_SIZE; size_t offset = MIN_IMAGE_HEADER_SIZE;
size_t actualBootloaderSize = MIN_IMAGE_HEADER_SIZE;
for (uint8_t i = 0; i < segmentCount && offset + 8 <= len; i++) { for (uint8_t i = 0; i < segmentCount && offset + 8 <= len; i++) {
uint32_t segmentSize = buffer[offset + 4] | (buffer[offset + 5] << 8) | uint32_t segmentSize = buffer[offset + 4] | (buffer[offset + 5] << 8) |
(buffer[offset + 6] << 16) | (buffer[offset + 7] << 24); (buffer[offset + 6] << 16) | (buffer[offset + 7] << 24);
// Segment size sanity check (shouldn't be > 32KB for bootloader segments) // Segment size sanity check
if (segmentSize > 0x8000) { // ESP32 classic bootloader segments can be larger, C3 are smaller
if (segmentSize > 0x20000) { // 128KB max per segment (very generous)
*bootloaderErrorMsg = "Segment " + String(i) + " too large: " + String(segmentSize) + " bytes"; *bootloaderErrorMsg = "Segment " + String(i) + " too large: " + String(segmentSize) + " bytes";
return false; return false;
} }
@@ -391,12 +413,47 @@ bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloader
offset += 8 + segmentSize; // Skip segment header and data offset += 8 + segmentSize; // Skip segment header and data
} }
// 8. Verify total size is reasonable actualBootloaderSize = offset;
if (len > 0x8000) { // Bootloader shouldn't exceed 32KB
*bootloaderErrorMsg = "Bootloader too large: " + String(len) + " bytes"; // 8. Check for appended SHA256 hash (byte 23 in header)
// If hash_appended != 0, there's a 32-byte SHA256 hash after the segments
uint8_t hashAppended = buffer[23];
if (hashAppended != 0) {
// SHA256 hash is appended (32 bytes)
actualBootloaderSize += 32;
DEBUG_PRINTF_P(PSTR("Bootloader has appended SHA256 hash\n"));
}
// 9. The image may also have a 1-byte checksum after segments/hash
// Check if there's at least one more byte available
if (actualBootloaderSize < len) {
// There's likely a checksum byte
actualBootloaderSize += 1;
}
// 10. Align to 16 bytes (ESP32 requirement for flash writes)
// The bootloader image must be 16-byte aligned
if (actualBootloaderSize % 16 != 0) {
size_t alignedSize = ((actualBootloaderSize + 15) / 16) * 16;
// Make sure we don't exceed available data
if (alignedSize <= len) {
actualBootloaderSize = alignedSize;
}
}
DEBUG_PRINTF_P(PSTR("Bootloader validation: %d segments, actual size %d bytes (buffer size %d bytes, hash_appended=%d)\n"),
segmentCount, actualBootloaderSize, len, hashAppended);
// 11. Verify we have enough data for all segments + hash + checksum
if (offset > len) {
*bootloaderErrorMsg = "Bootloader truncated - expected at least " + String(offset) + " bytes, have " + String(len) + " bytes";
return false; return false;
} }
// Update len to reflect actual bootloader size (including hash and checksum, with alignment)
// This is critical - we must write the complete image including checksums
len = actualBootloaderSize;
return true; return true;
} }
@@ -547,25 +604,57 @@ void handleBootloaderOTAData(AsyncWebServerRequest *request, size_t index, uint8
DEBUG_PRINTLN(F("Bootloader Upload Complete - validating and flashing")); DEBUG_PRINTLN(F("Bootloader Upload Complete - validating and flashing"));
if (context->buffer && context->bytesBuffered > 0) { if (context->buffer && context->bytesBuffered > 0) {
// Prepare pointers for verification (may be adjusted if bootloader at offset)
const uint8_t* bootloaderData = context->buffer;
size_t bootloaderSize = context->bytesBuffered;
// Verify the complete bootloader image before flashing // Verify the complete bootloader image before flashing
if (!verifyBootloaderImage(context->buffer, context->bytesBuffered, &context->errorMessage)) { // Note: verifyBootloaderImage may adjust bootloaderData pointer and bootloaderSize
// for validation purposes only
if (!verifyBootloaderImage(bootloaderData, bootloaderSize, &context->errorMessage)) {
DEBUG_PRINTLN(F("Bootloader validation failed!")); DEBUG_PRINTLN(F("Bootloader validation failed!"));
// Error message already set by verifyBootloaderImage // Error message already set by verifyBootloaderImage
} else { } else {
// Calculate offset to write to flash
// If bootloaderData was adjusted (partition table detected), we need to skip it in flash too
size_t flashOffset = context->bootloaderOffset;
const uint8_t* dataToWrite = context->buffer;
size_t bytesToWrite = context->bytesBuffered;
// If validation adjusted the pointer, it means we have a partition table at the start
// In this case, we should skip writing the partition table and write bootloader at 0x1000
if (bootloaderData != context->buffer) {
// bootloaderData was adjusted - skip partition table in our data
size_t partitionTableSize = bootloaderData - context->buffer;
dataToWrite = bootloaderData;
bytesToWrite = bootloaderSize;
DEBUG_PRINTF_P(PSTR("Skipping %d bytes of partition table data\n"), partitionTableSize);
}
DEBUG_PRINTF_P(PSTR("Bootloader validation passed - writing %d bytes to flash at 0x%04X\n"),
bytesToWrite, flashOffset);
// Calculate erase size (must be multiple of 4KB)
size_t eraseSize = ((bytesToWrite + 0xFFF) / 0x1000) * 0x1000;
if (eraseSize > context->maxBootloaderSize) {
eraseSize = context->maxBootloaderSize;
}
// Erase bootloader region // Erase bootloader region
DEBUG_PRINTLN(F("Erasing bootloader region...")); DEBUG_PRINTF_P(PSTR("Erasing %d bytes at 0x%04X...\n"), eraseSize, flashOffset);
esp_err_t err = esp_flash_erase_region(NULL, context->bootloaderOffset, context->maxBootloaderSize); esp_err_t err = esp_flash_erase_region(NULL, flashOffset, eraseSize);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err); DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err);
context->errorMessage = "Flash erase failed (error code: " + String(err) + ")"; context->errorMessage = "Flash erase failed (error code: " + String(err) + ")";
} else { } else {
// Write buffered data to flash // Write the validated bootloader data to flash
err = esp_flash_write(NULL, context->buffer, context->bootloaderOffset, context->bytesBuffered); err = esp_flash_write(NULL, dataToWrite, flashOffset, bytesToWrite);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err); DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err);
context->errorMessage = "Flash write failed (error code: " + String(err) + ")"; context->errorMessage = "Flash write failed (error code: " + String(err) + ")";
} else { } else {
DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written\n"), context->bytesBuffered); DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written to 0x%04X\n"),
bytesToWrite, flashOffset);
// Invalidate cached bootloader hash // Invalidate cached bootloader hash
invalidateBootloaderSHA256Cache(); invalidateBootloaderSHA256Cache();
context->uploadComplete = true; context->uploadComplete = true;

View File

@@ -55,12 +55,12 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data,
/** /**
* Verify complete buffered bootloader using ESP-IDF validation approach * Verify complete buffered bootloader using ESP-IDF validation approach
* This matches the key validation steps from esp_image_verify() in ESP-IDF * This matches the key validation steps from esp_image_verify() in ESP-IDF
* @param buffer Pointer to bootloader binary data * @param buffer Reference to pointer to bootloader binary data (will be adjusted if offset detected)
* @param len Length of bootloader data * @param len Reference to length of bootloader data (will be adjusted to actual size)
* @param bootloaderErrorMsg Pointer to String to store error message (can be null) * @param bootloaderErrorMsg Pointer to String to store error message (must not be null)
* @return true if validation passed, false otherwise * @return true if validation passed, false otherwise
*/ */
bool verifyBootloaderImage(const uint8_t* buffer, size_t len, String* bootloaderErrorMsg); bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String* bootloaderErrorMsg);
/** /**
* Create a bootloader OTA context object on an AsyncWebServerRequest * Create a bootloader OTA context object on an AsyncWebServerRequest