ADR 0003: the bootloader does roll back; Arduino had validated the image

Verified on the device: a crashing update pushed over Wi-Fi died once,
and the next boot was the previous firmware, with the "Update ... failed"
Toast. bootGuard() stays as a second line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-04 02:15:38 +02:00
co-authored by Claude Opus 5.5
parent 7afe6b7d17
commit 8ec4e9e449
@@ -9,4 +9,4 @@ We chose this over the ESP32's hardware Secure Boot. Secure Boot is enforced by
- Someone with physical USB access can still flash anything. Only Wi-Fi and SD card updates are guarded.
- **Losing the private key** means the next update has to go over USB, carrying a new public key.
- P-256 rather than Ed25519, because the firmware's TLS library (mbedTLS) already verifies it, so it costs no extra code.
- **Rollback is done by the firmware, not the bootloader.** On the device, the prebuilt bootloader that PlatformIO flashes did not roll back a crashing update, even though the app's configuration enables it. So the firmware counts its own boots on Probation, very first thing in `setup()`, and reverts itself on the second unconfirmed start. A crash before that counter is written (the first few milliseconds) would not be caught: recovery is then over USB.
- **Rollback: the bootloader first, the firmware as a second line.** Arduino-ESP32 marks a new image valid before `setup()` unless the sketch overrides `verifyRollbackLater()`, which once made every update look good and hid the bootloader's rollback (it had looked like the prebuilt bootloader ignored it). With the override, an image stays pending until Probation confirms it, and the bootloader reverts one that restarts unconfirmed, however early it crashes. The firmware also counts its own boots on Probation, very first thing in `setup()`, and reverts itself on the second unconfirmed start.