Flash Bootloader Rollback Protection: Defending Against Downgrade Attacks

Think like the attacker for a minute. Then build the bootloader that makes their plan pointless.
Before designing a defense, it's worth spending five honest minutes thinking like the person trying to defeat it. So: if you wanted to compromise a modern ECU, and the current firmware had already closed off your usual entry points — patched vulnerabilities, tightened access control, fixed the memory corruption bug you were counting on — what would you actually do?
You wouldn't necessarily try to break the current firmware at all. You'd try to make the ECU run a different firmware instead — specifically, an older one, from before the vulnerability you know about was patched. If the ECU will accept that older image as a legitimate update, you haven't defeated any security mechanism in the current firmware. You've simply asked the system to stop running it.
The Attacker's Case for Downgrade
This is a genuinely attractive attack vector, and it's worth being honest about why, because underestimating its appeal is how it gets left unmitigated.
It doesn't require finding a new vulnerability. Every patched CVE in your firmware's history is, from a downgrade attacker's perspective, a fully-documented, publicly-known exploit — assuming they can get the ECU to run the version where it existed. You're not doing novel vulnerability research. You're doing archival research on your own target's changelog.
It looks, at the transport layer, exactly like a legitimate update. A downgrade attempt sent via the standard UDS flash programming sequence (Request Download, Transfer Data, Request Transfer Exit) is structurally identical to a legitimate field update — the same services, the same session types, potentially even valid cryptographic signatures if the old firmware image was legitimately signed at the time it was released and that signature was never revoked. Without an explicit version check, "old but authentically signed" and "current and authentically signed" are indistinguishable to a bootloader that only verifies signature validity, not version.
It's persistent in a way many attacks aren't. A memory corruption exploit might need to be re-triggered after every reboot or power cycle. A successful downgrade changes what code the ECU boots into, full stop, until someone flashes it forward again — which, if nobody's specifically monitoring firmware version drift across a fleet, might not happen for a long time.
If I'm the attacker, downgrade is one of the most efficient paths available: minimal novel research, transport-layer indistinguishable from legitimate traffic, and durable once achieved. That's the case for taking it seriously as a specific, named threat rather than an incidental side-effect of "we do signature verification."

The Defense: Rollback Protection at the Bootloader
Signature verification alone answers "is this firmware authentically from us?" It does not answer "is this firmware current enough to be safe to run?" Rollback protection is specifically about answering the second question, and it needs to live in the bootloader — the one piece of code that runs before any application firmware, and the one component an attacker cannot bypass by controlling the application layer, precisely because it executes first.
The core mechanism is a secure, monotonically increasing version counter, stored in protected non-volatile memory (commonly in a dedicated OTP — one-time-programmable — region, or a secured flash sector with write protection enforced at the hardware level, not just by software convention). Every signed firmware image carries a version number as part of its signed metadata. The bootloader's job, before accepting any new image, is simple to state and important to get exactly right: the incoming image's version number must be greater than or equal to the counter's current stored value. If it isn't, the flash operation is rejected before a single byte of the new image is written to the application region.
Critically, the counter itself only ever moves forward. A successful, legitimate update to version N advances the stored counter to N (or to N's minimum-acceptable-version field, in schemes that separate "current running version" from "minimum version ever accepted," which is useful when you want to allow limited rollback to a recent prior version for rollback-on-failure purposes, while still blocking rollback to anything before a known-vulnerable version). The counter update itself needs to be as protected as the version check — an attacker who can roll back the counter alongside the firmware has defeated the entire mechanism, so counter storage and update logic deserve the same scrutiny as the version check itself, not less.
Where UDS Fits
This all needs to be reachable and controllable through the vehicle's actual diagnostic and reprogramming flow, which in practice means UDS (ISO 14229).
Version reporting typically happens via a Read Data By Identifier (SID 0x22) request against a specific data identifier (DID) reserved for the active firmware version and, separately, one for the secure minimum-version counter — giving legitimate diagnostic tools and fleet monitoring systems visibility into both "what's running" and "what's the floor" without needing to attempt a flash operation just to find out.
The actual version check happens during the standard reprogramming sequence — Request Download (SID 0x34) initiates the flash sequence, and this is the point at which the bootloader, having already validated the incoming image's signature and parsed its embedded version metadata, compares that version against the stored counter before allowing the sequence to proceed to Transfer Data (SID 0x36). A version that fails the check gets rejected here, at the earliest possible point in the sequence — not after the image has already been written and the bootloader is deciding whether to boot it, which would leave a window where a rejected image has still consumed a flash write cycle and, depending on implementation, potentially left the ECU in a partially-flashed state.
Routine identifiers (RID, via SID 0x31) are commonly used for the explicit routines a secure flash process actually needs around the core transfer — signature verification checks, and, in schemes that support it, an explicit "commit new minimum version" routine that only executes after the new image has been fully validated and is being finalized as the new active version, keeping "verify" and "commit" as distinct, separately auditable steps rather than one implicit side effect of a successful transfer.

Test Cases You Actually Need
Thinking like the attacker again, here's what a test plan needs to specifically cover, beyond "does a normal update still work":
The legitimate-looking downgrade attempt. Take a validly signed older firmware image — one that was genuinely released and signed by your own build process at some point in the past — and attempt to flash it through the standard UDS sequence on an ECU currently running a newer version. This must be rejected, and rejected specifically because of the version check, not incidentally because of some other unrelated validation failure that happens to also catch it. Confirm the rejection reason in logs/diagnostic response is specifically attributable to version/rollback protection, since a generic failure here can mask a version-check bug that happens to be caught by something else in this specific test case but wouldn't be caught in a slightly different scenario.
The bypass-the-official-channel attempt. Simulate an attacker who isn't going through your intended tooling at all — sending raw UDS sequences directly, potentially with a valid signature but attempting to manipulate ordering (e.g., attempting to trigger a commit routine without a preceding full, validated transfer) or attempting to interact with the version counter storage directly if any interface to it exists outside the intended bootloader logic path. This test case exists specifically because "our official tool checks the version before sending" is not a security control — anything the official tool checks, an attacker bypassing the official tool skips entirely, and the bootloader itself is the only place this check actually counts.
The counter-tampering attempt. If your counter storage has any conceivable write path other than the bootloader's own controlled update logic (a debug interface, a manufacturing-mode command, an unintentionally exposed memory-write primitive elsewhere in the firmware), attempt to use it to roll the counter itself backward, then attempt a downgrade. This test case is what catches the difference between "we have rollback protection" and "we have rollback protection until someone finds the one other way to touch that memory region" — the second one is a false sense of security that only a specifically adversarial test, rather than a functional one, tends to surface.
The partial-failure recovery case. Interrupt a legitimate, in-progress, version-valid update partway through (power loss, communication interruption) and confirm the ECU's subsequent behavior — specifically, that the version counter has not been advanced to reflect a commit that never actually completed, and that the ECU either recovers to the prior known-good version or fails to a safe, documented state, rather than ending up in an inconsistent condition where the counter says one version is current but the flash contents say otherwise.

The Point of All This
None of this is about being paranoid for its own sake. It's about recognizing that a downgrade attack is cheap for an attacker specifically because it reuses vulnerabilities you already know about and already fixed — which means the cost of leaving this unmitigated isn't hypothetical the way an unknown zero-day is. It's the cost of every CVE you've ever patched, still fully exploitable, just one unauthorized flash operation away from being reachable again. A secure, monotonic version counter enforced at the bootloader level, checked before the transfer even begins, is what keeps "we fixed that" from quietly becoming "we fixed that, as long as nobody asks the ECU to forget."