bugfix: bound firmware to world checksum
What changed, and why it matters
This commit fixes a bug in the COLDCARD firmware signing tools and headers. Previously, the maximum allowed firmware size for Mk4/Q1 devices was set too large: it did not account for a 512 KB filesystem region and a small DFU wrapper block at the end of flash. As a result, a firmware image could be signed and accepted even though part of it would extend beyond the region protected by the device's 'world checksum' integrity check. The patch shrinks the official maximum firmware length so that the entire image stays within the integrity-checked region. The changelog explicitly calls this a bugfix that 'reject[s] firmware images that extend past the world-checksum-covered flash region.'
Treat this as a security-relevant firmware signing fix. Ensure all firmware releases for Mk4/Q1 are re-signed with the corrected maximum length, and verify that bootloader/runtime enforcement of FW_MAX_LENGTH_MK4 matches the new value. Review whether any previously signed firmware images approached the old, larger limit and could have spanned past the world-checksum region.
Security signals we found
Integrity-boundary mismatch between signed firmware length and world-checksum coverage
Hard-coded size limit replaced with derived integrity-aware limit
Vendor changelog explicitly describes security-relevant bugfix
Firmware signing tool (signit.py) enforces the corrected bound
C header and Python tooling kept in sync to prevent desynchronization attacks
Evidence from the diff
The change updates FW_MAX_LENGTH_MK4 in both stm32/sigheader.h and stm32/sigheader.py from (0x200000 - 0x20000) to (0x200000 - 0x20000 - 0x80000 - 512). It also changes cli/signit.py so that the USB upgrade length cap uses FW_MAX_LENGTH_MK4 instead of a hard-coded 1472 KiB. The new bound subtracts the 512 KB LFS2 filesystem region and a 512-byte DFU wrapper from the 2 MB flash minus 128 KB bootrom. This ensures the firmware region covered by the signed header’s length field is fully inside the world-checksum-protected area. If a firmware image were longer than this bound, the portion beyond the checksum region could be modified without invalidating the integrity check, potentially allowing persistent malicious code to be installed while the signature still appears valid.
Changed components
cli/signit.pystm32/sigheader.hstm32/sigheader.pyMk4/Q1 firmware upgrade pathDFU/USB firmware update validationInspect captured patch +9 / −5
### cli/signit.py
@@ -329,7 +329,7 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
else:
# new value for Mk4 and later: limited only by final binary size, not SPI flash
assert FW_MIN_LENGTH <= hdr.firmware_length <= FW_MAX_LENGTH_MK4, hdr.firmware_length
- USB_MAX_LEN = 1472 * 1024
+ USB_MAX_LEN = FW_MAX_LENGTH_MK4
assert hdr.firmware_length <= USB_MAX_LEN, \
"too big for our USB upgrades: %d = %d bytes too big" % (
### releases/Next-ChangeLog.md
@@ -23,6 +23,8 @@ This lists the new changes that have not yet been published in a normal release.
`psram_copy_file`/`psram_mmap_file` that allowed out-of-bounds PSRAM writes, reads,
and mappings from a compromised USB host.
- Bugfix: Hide Change Main PIN while a temporary seed or BIP-39 passphrase wallet is active.
+- Bugfix: Reject firmware images that extend past the world-checksum-covered
+ flash region.
# Mk Specific Changes
### stm32/sigheader.h
@@ -49,8 +49,9 @@ typedef struct {
// - but practical limit for our-protocol USB upgrades: 786432 (or else settings damaged)
#define FW_MAX_LENGTH (0x100000 - 0x8000)
-// .. for Mk4: 2Mbytes, less bootrom of 128k.
-#define FW_MAX_LENGTH_MK4 (0x200000 - 0x20000)
+// Mk4/Q1: 2MB less 128k bootrom and 512k LFS2.
+// Leave one 512-byte block for the DFU wrapper.
+#define FW_MAX_LENGTH_MK4 (0x200000 - 0x20000 - 0x80000 - 512)
// Arguments to be used w/ python's struct module.
#define FWH_PY_FORMAT "<I8s8sIIII8s20s64s"
### stm32/sigheader.py
@@ -33,8 +33,9 @@
# - but practical limit for our-protocol USB upgrades: 786432 (or else settings damaged)
FW_MAX_LENGTH = (0x100000 - 0x8000)
-# .. for Mk4: 2Mbytes, less bootrom of 128k.
-FW_MAX_LENGTH_MK4 = (0x200000 - 0x20000)
+# Mk4/Q1: 2MB less 128k bootrom and 512k LFS2.
+# Leave one 512-byte block for the DFU wrapper.
+FW_MAX_LENGTH_MK4 = (0x200000 - 0x20000 - 0x80000 - 512)
# Arguments to be used w/ python's struct module.
FWH_PY_FORMAT = "<I8s8sIIII8s20s64s"Why this scored 71/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.