Lab 02 — Secure Firmware Update with Anti-Rollback
Difficulty: 4/5 | Runs locally: yes, standard library only
Pairs with the Phase 13 WARMUP Chapter 8 (secure firmware update and anti-rollback).
Why This Lab Exists (Purpose & Goal)
The firmware-update path is the most dangerous mechanism a device fleet operates: it is designed to replace the most privileged code on the device, so a single accepted malicious "update" is persistent, invisible, kernel-or-below compromise of every device that takes it. The goal of this lab is to build the verifier a real secure-OTA / Verified-Boot update path must run — enforcing authenticity, integrity, device-model binding, and (the subtle one) anti-rollback — and to commit the monotonic version counter only after a fully verified apply.
The Concept, In the Weeds
A signature proves authenticity, but not freshness — and freshness is where firmware updates are
attacked. The five controls (this is verify_update / apply_update):
- Authenticity — signed by a key chained to the device's root of trust (untrusted signer → reject outright).
- Integrity — the delivered payload hashes to the value the signature covers (no bit-flips after signing).
- Model binding — the image is for this device model, so an attacker can't flash a weaker model's validly-signed image (a cross-model downgrade).
- Anti-rollback — the crux. An attacker takes a validly signed but old, vulnerable image and tries to flash it to re-open a patched bug. The device refuses any image whose rollback index is ≤ a monotonic counter stored in hardware (eFuses/RPMB) that can only ever increase.
- Commit safety — advance the counter (and A/B-swap the active slot) only after a successful verified apply, so an interrupted/failed update can't brick the device or silently roll back.
Why This Matters for Protecting the Company
Downgrade attacks are how attackers defeat your patch program without finding a new bug — they just re-install the old, vulnerable, still-validly-signed firmware. Android Verified Boot, ChromeOS, and automotive TUF/Uptane all enforce hardware anti-rollback precisely because signing alone is insufficient. When you can design and verify a secure-update path with a hardware-backed monotonic counter, model binding, and commit-on-success, you protect every device the company ships from the most persistent class of compromise — and you keep your patches stuck, so a fixed vulnerability stays fixed.
Secure Implementation Patterns
The anti-pattern. Accept any validly-signed image:
if verify_signature(image): flash(image) # a signed OLD image re-opens a patched CVE (downgrade)
The secure pattern — five controls, commit only on success (mirrors solution.py):
def verify_update(image, device):
if image.signer_key not in device.trusted_keys: # 1. authenticity (root of trust)
return False, ("untrusted-signer",)
reasons = []
if not hmac.compare_digest(_sign(image.signer_key, _manifest(image)), image.signature):
reasons.append("invalid-signature")
if not hmac.compare_digest(sha256(image.payload), image.payload_hash):
reasons.append("payload-hash-mismatch") # 2. integrity
if image.device_model != device.model:
reasons.append("model-mismatch") # 3. no cross-model flash
if image.version <= device.rollback_index: # 4. ANTI-ROLLBACK (monotonic, hardware)
reasons.append("rollback-blocked")
return (not reasons), tuple(reasons)
def apply_update(image, device):
ok, reasons = verify_update(image, device)
if not ok:
raise ValueError(",".join(reasons)) # never advance on failure
device.rollback_index = image.version # 5. commit the counter on success
return device.rollback_index
Production practices to carry forward:
- Store the rollback index in hardware (eFuses/RPMB), so software can't decrement it — the same "append-only in hardware" property as TPM PCRs.
- Use A/B slots + a recovery path so a bad update falls back to the known-good slot, not a brick.
- Bind to model and (ideally) device class/region to stop cross-image attacks.
- Sign the whole manifest (model, version, payload hash), not just the payload, so version and binding are authenticated too.
Validation — What You Should Be Able to Do Now
- Explain why a signed image is not a safe image, and what anti-rollback adds.
- Implement the five controls and block an untrusted signer, a tampered payload, a forged signature, a cross-model flash, and a downgrade.
- Explain why the monotonic counter must be hardware-backed and why you commit only on success.
The Broader Perspective
This lab teaches freshness and monotonicity as security primitives — the recognition that valid and current are different, and that defeating replay/downgrade requires an append-only, hardware-rooted notion of "newest." It is the firmware twin of the attestation nonce (Lab 01), the OIDC single-use transaction (Phase 02), the agent replay-id check (Phase 11), and the FedRAMP scan-freshness clock (Phase 15). Across the whole curriculum, a recurring attacker move is "reuse something that was valid"; the recurring defense is a monotonic, append-only anchor — here, burned into silicon.
Interview Q&A
- "Why isn't a signed firmware image enough?" — A signature proves authenticity, not freshness; an attacker flashes a validly-signed old image to re-open a patched bug. Anti-rollback enforces a hardware monotonic counter (refuse version ≤ stored, advance only on success), with the counter in fuses/RPMB so software can't decrement it.
- "How do you avoid bricking a device on a bad update?" — A/B slots and a recovery path: verify into the inactive slot, commit/swap only on a successful verified apply, fall back to the known-good slot on failure.
Extension (Stretch)
Model A/B slot swapping and a recovery slot; add TUF/Uptane-style role separation (root, targets,
snapshot, timestamp) for the update repository; explore fwupd/LVFS for real firmware delivery.
References
- Android Verified Boot (rollback protection); ChromeOS verified boot; Uptane/TUF specifications.
- Phase 13 WARMUP Chapter 8;
fwupd/LVFS documentation.