Address final review feedback: revert unnecessary files, use F-strings, add length validation, implement deferred ESP8266 validation

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-17 18:19:52 +00:00
parent 71ad0d99aa
commit 6676705428
5 changed files with 113 additions and 8 deletions

View File

@@ -13,7 +13,23 @@ else:
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m') print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
env.Execute("npm ci") env.Execute("npm ci")
print('Building web UI') # Extract the release name from build flags
release_name = None # Let cdata.js provide the default if not found
build_flags = env.get("BUILD_FLAGS", [])
for flag in build_flags:
if 'WLED_RELEASE_NAME=' in flag:
# Extract the release name, remove quotes and handle different formats
parts = flag.split('WLED_RELEASE_NAME=')
if len(parts) > 1:
release_name = parts[1].split()[0].strip('\"\\')
break
# Set environment variable for cdata.js to use (only if found)
if release_name:
os.environ['WLED_RELEASE_NAME'] = release_name
print(f'Building web UI with release name: {release_name}')
else:
print('Building web UI with default release name (from cdata.js)')
# Call the bundling script # Call the bundling script
exitCode = env.Execute("npm run build") exitCode = env.Execute("npm run build")

View File

@@ -96,6 +96,10 @@ function adoptVersionAndRepo(html) {
html = html.replaceAll("##VERSION##", version); html = html.replaceAll("##VERSION##", version);
} }
// Replace ##RELEASE## with the actual release name from build environment
const releaseName = process.env.WLED_RELEASE_NAME || 'Custom';
html = html.replaceAll("##RELEASE##", releaseName);
return html; return html;
} }

View File

@@ -91,8 +91,7 @@ bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessa
if (!hasCustomDesc) { if (!hasCustomDesc) {
// No custom description - this could be a legacy binary // No custom description - this could be a legacy binary
if (errorMessage && errorMessageLen > 0) { if (errorMessage && errorMessageLen > 0) {
const char* msg = "This firmware file is missing compatibility metadata. Enable 'Ignore firmware validation' to proceed anyway."; strncpy_P(errorMessage, PSTR("This firmware file is missing compatibility metadata. Enable 'Ignore firmware validation' to proceed anyway."), errorMessageLen - 1);
strncpy(errorMessage, msg, errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0'; errorMessage[errorMessageLen - 1] = '\0';
} }
return false; return false;
@@ -101,7 +100,7 @@ bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessa
// Validate compatibility using extracted release name // Validate compatibility using extracted release name
if (!validateReleaseCompatibility(extractedDesc.release_name)) { if (!validateReleaseCompatibility(extractedDesc.release_name)) {
if (errorMessage && errorMessageLen > 0) { if (errorMessage && errorMessageLen > 0) {
snprintf(errorMessage, errorMessageLen, "Firmware compatibility mismatch: current='%s', uploaded='%s'. Enable 'Ignore firmware validation' to proceed anyway.", snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware compatibility mismatch: current='%s', uploaded='%s'. Enable 'Ignore firmware validation' to proceed anyway."),
releaseString, extractedDesc.release_name); releaseString, extractedDesc.release_name);
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
} }

View File

@@ -16,6 +16,10 @@ const wled_custom_desc_t __attribute__((section(WLED_CUSTOM_DESC_SECTION))) wled
djb2_hash_constexpr(WLED_RELEASE_NAME) // crc32 - computed at compile time djb2_hash_constexpr(WLED_RELEASE_NAME) // crc32 - computed at compile time
}; };
// Compile-time validation that release name doesn't exceed maximum length
static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
"WLED_RELEASE_NAME exceeds maximum length of WLED_RELEASE_NAME_MAX_LEN characters");
// Single reference to ensure it's not optimized away // Single reference to ensure it's not optimized away
const wled_custom_desc_t* __attribute__((used)) wled_custom_desc_ref = &wled_custom_description; const wled_custom_desc_t* __attribute__((used)) wled_custom_desc_ref = &wled_custom_description;

View File

@@ -426,12 +426,28 @@ void initServer()
} }
if (!correctPIN || otaLock) return; if (!correctPIN || otaLock) return;
// Static variable to track release check status across chunks // Static variables to track release check status and data accumulation across chunks
static bool releaseCheckPassed = false; static bool releaseCheckPassed = false;
static bool releaseCheckPending = true;
static size_t totalBytesReceived = 0;
static uint8_t* validationBuffer = nullptr;
static const size_t ESP8266_VALIDATION_OFFSET = 0x1000; // 4KB offset for ESP8266
static const size_t VALIDATION_BUFFER_SIZE = 8192; // Buffer size for validation
if(!index){ if(!index){
DEBUG_PRINTLN(F("OTA Update Start")); DEBUG_PRINTLN(F("OTA Update Start"));
// Reset validation state for new update
releaseCheckPassed = false;
releaseCheckPending = true;
totalBytesReceived = 0;
// Free any existing validation buffer
if (validationBuffer) {
free(validationBuffer);
validationBuffer = nullptr;
}
// Check if user wants to skip validation // Check if user wants to skip validation
bool skipValidation = request->hasParam("skipValidation", true); bool skipValidation = request->hasParam("skipValidation", true);
@@ -439,10 +455,13 @@ void initServer()
if (skipValidation) { if (skipValidation) {
DEBUG_PRINTLN(F("OTA validation skipped by user")); DEBUG_PRINTLN(F("OTA validation skipped by user"));
releaseCheckPassed = true; releaseCheckPassed = true;
releaseCheckPending = false;
} else { } else {
// Validate OTA release compatibility using the first chunk data directly #ifdef ESP32
// ESP32: metadata appears at offset 0, validate immediately with first chunk
char errorMessage[128]; char errorMessage[128];
releaseCheckPassed = shouldAllowOTA(data, len, errorMessage, sizeof(errorMessage)); releaseCheckPassed = shouldAllowOTA(data, len, errorMessage, sizeof(errorMessage));
releaseCheckPending = false;
if (!releaseCheckPassed) { if (!releaseCheckPassed) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage); DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
@@ -451,6 +470,16 @@ void initServer()
} else { } else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed")); DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
} }
#elif defined(ESP8266)
// ESP8266: metadata appears around offset 0x1000, need to buffer data until then
validationBuffer = (uint8_t*)malloc(VALIDATION_BUFFER_SIZE);
if (!validationBuffer) {
DEBUG_PRINTLN(F("OTA failed: Could not allocate validation buffer"));
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Out of memory for validation"));
return;
}
DEBUG_PRINTLN(F("ESP8266: Deferring validation until offset 0x1000"));
#endif
} }
DEBUG_PRINTLN(F("Release check passed, starting OTA update")); DEBUG_PRINTLN(F("Release check passed, starting OTA update"));
@@ -487,8 +516,46 @@ void initServer()
} }
} }
// Write chunk data to OTA update (only if release check passed) // Handle ESP8266 deferred validation - accumulate data until validation offset is reached
if (releaseCheckPassed && !Update.hasError()) { #ifdef ESP8266
if (releaseCheckPending && validationBuffer) {
// Copy data to validation buffer
size_t bytesToCopy = min((size_t)len, VALIDATION_BUFFER_SIZE - totalBytesReceived);
if (bytesToCopy > 0) {
memcpy(validationBuffer + totalBytesReceived, data, bytesToCopy);
}
totalBytesReceived += len;
// Check if we've reached the validation offset
if (totalBytesReceived >= ESP8266_VALIDATION_OFFSET + sizeof(wled_custom_desc_t)) {
DEBUG_PRINTLN(F("ESP8266: Performing deferred validation"));
char errorMessage[128];
// Validate using buffered data starting from the expected offset
releaseCheckPassed = shouldAllowOTA(validationBuffer + ESP8266_VALIDATION_OFFSET,
totalBytesReceived - ESP8266_VALIDATION_OFFSET,
errorMessage, sizeof(errorMessage));
releaseCheckPending = false;
if (!releaseCheckPassed) {
DEBUG_PRINTF_P(PSTR("OTA declined (deferred): %s\n"), errorMessage);
free(validationBuffer);
validationBuffer = nullptr;
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Deferred release compatibility check passed"));
}
// Free validation buffer as it's no longer needed
free(validationBuffer);
validationBuffer = nullptr;
}
}
#endif
// Write chunk data to OTA update (only if release check passed or still pending)
if ((releaseCheckPassed || releaseCheckPending) && !Update.hasError()) {
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.getErrorString().c_str()); DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.getErrorString().c_str());
} }
@@ -497,6 +564,21 @@ void initServer()
if(isFinal){ if(isFinal){
DEBUG_PRINTLN(F("OTA Update End")); DEBUG_PRINTLN(F("OTA Update End"));
// Clean up validation buffer if still allocated
#ifdef ESP8266
if (validationBuffer) {
free(validationBuffer);
validationBuffer = nullptr;
}
#endif
// Check if validation was still pending (shouldn't happen normally)
if (releaseCheckPending) {
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Firmware validation incomplete"));
return;
}
if (releaseCheckPassed) { if (releaseCheckPassed) {
if(Update.end(true)){ if(Update.end(true)){
DEBUG_PRINTLN(F("Update Success")); DEBUG_PRINTLN(F("Update Success"));