[web_server] Reset OTA backend on new upload to avoid bricked device after interrupted OTA

If a captive_portal or web_server OTA upload is interrupted mid-stream (e.g. TCP
connection reset), the shared OTARequestHandler's ota_backend_ is left open. When
the browser resends the multipart POST, the guard 'index == 0 && !ota_backend_'
skipped re-initialization, so new bytes were written at the previous session's
offset. The Updater's end() then reports success with a concatenated image in
flash, bricking the device on reboot.

Abort and reset any in-progress backend whenever a new multipart upload starts
(index == 0) so the fresh upload begins a clean OTA session.
This commit is contained in:
J. Nick Koston
2026-04-13 12:41:12 -10:00
parent 8cdffef82a
commit 81a2caee36
@@ -114,7 +114,18 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
uint8_t *data, size_t len, bool final) {
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
if (index == 0 && !this->ota_backend_) {
if (index == 0) {
// A new multipart upload is starting. If a previous upload was interrupted
// (e.g. TCP reset) the backend from that session may still be open; tear it
// down so flash state doesn't get concatenated with the new image (which
// can produce a technically-valid-sized but corrupted firmware that
// bricks the device once it reboots).
if (this->ota_backend_) {
ESP_LOGW(TAG, "New OTA upload received while previous session was still open; aborting previous session");
this->ota_backend_->abort();
this->ota_backend_.reset();
}
// Initialize OTA on first call
this->ota_init_(filename.c_str());