From d55a3f078a6b80862cf3c0b568a498b784e3420f Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 8 Nov 2025 18:13:22 -0500 Subject: [PATCH 1/2] Add source version check to OTA update Add a field to the OTA metadata structure indicating the oldest base version it's safe to install this update /from/. This provides a clear path forward in case there are incompatibilities, eg. some case (bootloader compatibility) where 0.16.0 cannot be installed safely from 0.15.2, but a transitional 0.15.3 can arrange the groundwork. --- wled00/wled_metadata.cpp | 28 +++++++++++++++++++++++++--- wled00/wled_metadata.h | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 19c83dda1c..9ee19ade05 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -16,7 +16,7 @@ #endif constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453; // "WSTS" (WLED System Tag Structure) -constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 1; +constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 2; // v1 - original PR; v2 - "safe to update from" version // Compile-time validation that release name doesn't exceed maximum length static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN, @@ -59,6 +59,11 @@ const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUIL TOSTRING(WLED_VERSION), WLED_RELEASE_NAME, // release_name std::integral_constant::value, // hash - computed at compile time; integral_constant enforces this +#if defined(ESP32) && defined(CONFIG_IDF_TARGET_ESP32) + { 0, 15, 3 }, // Some older ESP32 might have bootloader issues; assume we'll have it sorted by 0.15.3 +#else + { 0, 15, 2 }, // All other platforms can update safely +#endif }; static const char repoString_s[] PROGMEM = WLED_REPO; @@ -96,7 +101,7 @@ bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_ memcpy(&candidate, binaryData + offset, sizeof(candidate)); // Found potential match, validate version - if (candidate.desc_version != WLED_CUSTOM_DESC_VERSION) { + if (candidate.desc_version > WLED_CUSTOM_DESC_VERSION) { DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"), offset, candidate.desc_version); continue; @@ -151,13 +156,30 @@ bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessa if (strncmp_P(safeFirmwareRelease, releaseString, WLED_RELEASE_NAME_MAX_LEN) != 0) { if (errorMessage && errorMessageLen > 0) { - snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware compatibility mismatch: current='%s', uploaded='%s'."), + snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware release name mismatch: current='%s', uploaded='%s'."), releaseString, safeFirmwareRelease); errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination } return false; } + if (firmwareDescription.desc_version > 1) { + // Add safe version check + // Parse our version (x.y.z) and compare it to the "safe version" array + char* our_version = const_cast(versionString); // rip off const for legacy strtol compatibility + for(unsigned v_index = 0; v_index < 3; ++v_index) { + long our_v_parsed = strtol(our_version, &our_version, 10); + ++our_version; // skip the decimal point + if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { + snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), + firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], + versionString); + errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + return false; + } + } + } + // TODO: additional checks go here return true; diff --git a/wled00/wled_metadata.h b/wled00/wled_metadata.h index 7ab4d09936..8c1dc0bb0f 100644 --- a/wled00/wled_metadata.h +++ b/wled00/wled_metadata.h @@ -26,6 +26,7 @@ typedef struct { char wled_version[WLED_VERSION_MAX_LEN]; char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated) uint32_t hash; // Structure sanity check + uint8_t safe_update_version[3]; // Indicates version it's known to be safe to install this update from: major, minor, patch } __attribute__((packed)) wled_metadata_t; From 5bf1fc38b1b15658d91158f990cc1a6494eb5c57 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 8 Nov 2025 19:18:27 -0500 Subject: [PATCH 2/2] Implement correct update version check --- wled00/wled_metadata.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 9ee19ade05..05616a7294 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -166,17 +166,30 @@ bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessa if (firmwareDescription.desc_version > 1) { // Add safe version check // Parse our version (x.y.z) and compare it to the "safe version" array - char* our_version = const_cast(versionString); // rip off const for legacy strtol compatibility + const char* our_version = versionString; for(unsigned v_index = 0; v_index < 3; ++v_index) { - long our_v_parsed = strtol(our_version, &our_version, 10); - ++our_version; // skip the decimal point - if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { - snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), - firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], - versionString); - errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + char* our_version_end = nullptr; + long our_v_parsed = strtol(our_version, &our_version_end, 10); + if (!our_version_end || (our_version_end == our_version)) { + // We were built with a malformed version string + // We blame the integrator and attempt the update anyways - nothing the user can do to fix this + break; + } + + if (firmwareDescription.safe_update_version[v_index] > our_v_parsed) { + if (errorMessage && errorMessageLen > 0) { + snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), + firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], + versionString); + errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + } return false; + } else if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { + break; // no need to check the other components } + + if (*our_version_end == '.') ++our_version_end; + our_version = our_version_end; } }