From 0e2225868a6b5719534b90a44bb61199269daaf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:53:23 +0000 Subject: [PATCH 1/4] Initial plan From b50b2a95e4dd5872ca219747dac65f2178374a12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:59:56 +0000 Subject: [PATCH 2/4] Fix bootloader SHA256 to only hash actual bootloader data, not full 32KB region - Add getActualBootloaderSize() helper to parse bootloader image structure - Calculate actual size by parsing segment headers, hash, and checksum - Only hash the actual bootloader data instead of full BOOTLOADER_SIZE - This prevents different hashes for same bootloader due to garbage data in unused memory Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com> --- wled00/ota_update.cpp | 109 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index 2f758b2c79..82a81865f8 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -289,11 +289,114 @@ void markOTAvalid() { // Cache for bootloader SHA256 digest as hex string static String bootloaderSHA256HexCache = ""; +// Helper function to calculate actual bootloader size from flash +// Returns the actual size of the bootloader image, or 0 if invalid +static size_t getActualBootloaderSize() { + const size_t MIN_IMAGE_HEADER_SIZE = 24; + + // We need to read enough data to parse all segment headers + // Typical bootloader has 4-6 segments, each header is 8 bytes + // Reading first 2KB should be sufficient for header parsing + const size_t PARSE_BUFFER_SIZE = 2048; + + uint8_t* parseBuffer = (uint8_t*)malloc(PARSE_BUFFER_SIZE); + if (!parseBuffer) { + DEBUG_PRINTLN(F("Failed to allocate parse buffer")); + return 0; + } + + // Read initial portion for parsing + if (esp_flash_read(NULL, parseBuffer, BOOTLOADER_OFFSET, PARSE_BUFFER_SIZE) != ESP_OK) { + DEBUG_PRINTLN(F("Failed to read bootloader header")); + free(parseBuffer); + return 0; + } + + // Validate magic byte + if (parseBuffer[0] != 0xE9) { + DEBUG_PRINTF_P(PSTR("Invalid bootloader magic byte: 0x%02X\n"), parseBuffer[0]); + free(parseBuffer); + return 0; + } + + // Get segment count + uint8_t segmentCount = parseBuffer[1]; + if (segmentCount == 0 || segmentCount > 16) { + DEBUG_PRINTF_P(PSTR("Invalid segment count: %d\n"), segmentCount); + free(parseBuffer); + return 0; + } + + // Parse segments to find actual bootloader size + size_t offset = MIN_IMAGE_HEADER_SIZE; + + for (uint8_t i = 0; i < segmentCount; i++) { + // Check if segment header is within our parse buffer + if (offset + 8 > PARSE_BUFFER_SIZE) { + DEBUG_PRINTF_P(PSTR("Segment %d header at offset %d beyond parse buffer\n"), i, offset); + free(parseBuffer); + // Fall back to full size if we can't parse all segments + return BOOTLOADER_SIZE; + } + + // Read segment size (little-endian uint32_t at offset+4) + uint32_t segmentSize = parseBuffer[offset + 4] | + (parseBuffer[offset + 5] << 8) | + (parseBuffer[offset + 6] << 16) | + (parseBuffer[offset + 7] << 24); + + // Sanity check segment size + if (segmentSize > 0x20000) { // 128KB max per segment + DEBUG_PRINTF_P(PSTR("Segment %d too large: %u bytes\n"), i, segmentSize); + free(parseBuffer); + return BOOTLOADER_SIZE; // Fall back to full size on error + } + + offset += 8 + segmentSize; // Skip segment header (8 bytes) and data + } + + size_t actualSize = offset; + + // Check for appended SHA256 hash (byte 23 in header) + uint8_t hashAppended = parseBuffer[23]; + if (hashAppended != 0) { + actualSize += 32; + DEBUG_PRINTLN(F("Bootloader has appended SHA256")); + } + + // Add checksum byte (typically present) + actualSize += 1; + + // Align to 16 bytes (ESP32 flash requirement) + if (actualSize % 16 != 0) { + actualSize = ((actualSize + 15) / 16) * 16; + } + + free(parseBuffer); + + // Sanity check - actual size should not exceed allocated bootloader space + if (actualSize > BOOTLOADER_SIZE) { + DEBUG_PRINTF_P(PSTR("Calculated size %d exceeds max %d, using max\n"), actualSize, BOOTLOADER_SIZE); + return BOOTLOADER_SIZE; + } + + DEBUG_PRINTF_P(PSTR("Actual bootloader size: %d bytes (from %d segments)\n"), actualSize, segmentCount); + return actualSize; +} + // Calculate and cache the bootloader SHA256 digest as hex string void calculateBootloaderSHA256() { if (!bootloaderSHA256HexCache.isEmpty()) return; - // Calculate SHA256 + // First, determine the actual bootloader size + size_t actualBootloaderSize = getActualBootloaderSize(); + if (actualBootloaderSize == 0) { + DEBUG_PRINTLN(F("Failed to determine bootloader size, using empty hash")); + bootloaderSHA256HexCache = ""; + return; + } + + // Calculate SHA256 only over the actual bootloader data uint8_t sha256[32]; mbedtls_sha256_context ctx; mbedtls_sha256_init(&ctx); @@ -302,8 +405,8 @@ void calculateBootloaderSHA256() { const size_t chunkSize = 256; uint8_t buffer[chunkSize]; - for (uint32_t offset = 0; offset < BOOTLOADER_SIZE; offset += chunkSize) { - size_t readSize = min((size_t)(BOOTLOADER_SIZE - offset), chunkSize); + for (uint32_t offset = 0; offset < actualBootloaderSize; offset += chunkSize) { + size_t readSize = min((size_t)(actualBootloaderSize - offset), chunkSize); if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) { mbedtls_sha256_update(&ctx, buffer, readSize); } From 1febf0bdd98f3609667b45405b46c0270278f769 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:02:13 +0000 Subject: [PATCH 3/4] Address code review feedback - Define MAX_SEGMENT_SIZE constant instead of magic number - Use bitwise alignment operation for better efficiency - Clarify that checksum byte is part of ESP-IDF image format Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com> --- wled00/ota_update.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index 82a81865f8..92e00d3c29 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -293,6 +293,7 @@ static String bootloaderSHA256HexCache = ""; // Returns the actual size of the bootloader image, or 0 if invalid static size_t getActualBootloaderSize() { const size_t MIN_IMAGE_HEADER_SIZE = 24; + const size_t MAX_SEGMENT_SIZE = 0x20000; // 128KB maximum per segment // We need to read enough data to parse all segment headers // Typical bootloader has 4-6 segments, each header is 8 bytes @@ -346,7 +347,7 @@ static size_t getActualBootloaderSize() { (parseBuffer[offset + 7] << 24); // Sanity check segment size - if (segmentSize > 0x20000) { // 128KB max per segment + if (segmentSize > MAX_SEGMENT_SIZE) { DEBUG_PRINTF_P(PSTR("Segment %d too large: %u bytes\n"), i, segmentSize); free(parseBuffer); return BOOTLOADER_SIZE; // Fall back to full size on error @@ -364,13 +365,13 @@ static size_t getActualBootloaderSize() { DEBUG_PRINTLN(F("Bootloader has appended SHA256")); } - // Add checksum byte (typically present) + // ESP32 bootloader images include a 1-byte checksum after segments/hash + // This is part of the standard ESP-IDF image format actualSize += 1; // Align to 16 bytes (ESP32 flash requirement) - if (actualSize % 16 != 0) { - actualSize = ((actualSize + 15) / 16) * 16; - } + // Use bitwise operation for efficient power-of-2 alignment + actualSize = (actualSize + 15) & ~15; free(parseBuffer); From b4ef18bc8d36804bd74a8659ea17b11ad14aadcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:02:59 +0000 Subject: [PATCH 4/4] Complete bootloader checksum fix - ready for review Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000000..945c9b46d6 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file