Skip to content

Fix multipart parser truncating binary uploads ending in a whitespace byte (Image hash failed)#10

Open
HansStrupat wants to merge 1 commit into
SensorsIot:mainfrom
HansStrupat:fix/multipart-binary-truncation
Open

Fix multipart parser truncating binary uploads ending in a whitespace byte (Image hash failed)#10
HansStrupat wants to merge 1 commit into
SensorsIot:mainfrom
HansStrupat:fix/multipart-binary-truncation

Conversation

@HansStrupat

Copy link
Copy Markdown

Problem

/api/flash and /api/firmware/upload use a hand-rolled multipart parser that calls
part.strip() on each raw part. bytes.strip() removes trailing ASCII-whitespace bytes
(09 0a 0b 0c 0d 20), so any uploaded binary whose final byte is whitespace is silently
truncated
. The flash then "succeeds" (esptool reports Hash of data verified against the
truncated temp file), but the device boot-loops with:

E esp_image: Image hash failed - image is corrupt
E boot: Factory app partition is not bootable

because the appended SHA-256 no longer matches. It hits ~2–3% of builds at random and is
independent of chip / board / baud / esptool version — which makes it look board- or chip-specific
and very confusing to diagnose.

Reproduction

Same ESP32, same portal, only the app's last byte differs:

app image last byte /api/flash
original 0x20 Image hash failed
original + trailing 0xFF (after the SHA-256, ignored by the ROM) 0xFF ✅ boots

Flashing the original file with esptool directly on the Pi against the same /dev/ttyUSB0
boots fine at any baud, stub or --no-stub — confirming the corruption is introduced by the
parser, not esptool/serial.

Fix

Strip only the leading CRLF framing; never .strip() the part body. The existing
content.endswith(b"\r\n") check already removes the trailing framing CRLF, so the old
part.strip() was both redundant and destructive. Applied at both parsers. (The
part = part.strip() calls inside for part in content_type.split(";") are unrelated
Content-Type header parsing and are left untouched.)

Verified

Applied to a running workbench (esptool v5.2.0, classic ESP32-D0WD over a CP2102, /dev/ttyUSB0):
the exact image that previously bricked on every /api/flash now boots, with no other changes.

Fixes #9

🤖 Generated with Claude Code

The /api/flash and /api/firmware/upload multipart parsers call
part.strip() on each raw part. bytes.strip() drops trailing
ASCII-whitespace bytes (09 0a 0b 0c 0d 20), so any uploaded image
whose final byte is whitespace is silently truncated -> its appended
SHA-256 no longer matches -> the device boot-loops with
"esp_image: Image hash failed - image is corrupt". It is
content-dependent (~2-3% of builds at random) and independent of
chip/board/baud/esptool, which makes it very hard to diagnose.

Strip only the leading CRLF framing instead; the existing
content.endswith(b"\r\n") check already removes the trailing framing
CRLF. Applied at both multipart parsers.

Fixes SensorsIot#9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

multipart parser truncates binary uploads ending in a whitespace byte → bricked flashes (Image hash failed)

1 participant