bugfix: separate DFU container limit from firmware bound
What changed, and why it matters
This commit fixes a bug in the COLDCARD firmware update process. Previously, the maximum file size allowed for a firmware update file (.dfu) was set too small because it was based on the firmware's own size limit, not accounting for extra data that comes with 'factory' update images (which include a bootloader and a DFU wrapper). As a result, legitimate factory firmware files could not be selected from the MicroSD card picker. The fix separates the two limits: one for the actual firmware size, and a larger one for the .dfu container shown in the file picker. There is no direct evidence in the commit that this was a security vulnerability, but overly restrictive size checks can sometimes hide or complicate downgrade or supply-chain issues.
Treat as a bugfix. Verify that the new FW_MAX_DFU_SIZE_MK4 still rejects oversized or maliciously large .dfu files, and that downstream flashing code continues to use FW_MAX_LENGTH_MK4 (or equivalent) to enforce the actual firmware region. Review whether the file picker should also validate the DFU suffix/header before accepting files by size alone.
Security signals we found
Boundary/length constant changed in firmware update path
File picker size limit relaxed to allow multi-element DFU containers
Firmware flash region bound kept unchanged and 4k-aligned
No signature, checksum, or verification logic modified
Evidence from the diff
The change introduces FW_MAX_DFU_SIZE_MK4 alongside FW_MAX_LENGTH_MK4. FW_MAX_LENGTH_MK4 is now aligned to 4 KB and covers only the firmware region inside the world-checksum-covered flash area (2 MB - 128 KB bootrom - 512 KB LFS2). FW_MAX_DFU_SIZE_MK4 adds BL_FLASH_SIZE (0x1c000) and 0x400 for the DFU wrapper, and is used only by the MicroSD file picker in shared/actions.py. This prevents -factory.dfu images from being rejected by file_picker() due to size. The commit does not alter signature verification, checksum validation, or the actual flashing bounds.
Changed components
shared/actions.pystm32/sigheader.hstm32/sigheader.pyMicroSD firmware upgrade file pickerInspect captured patch +16 / −8
### shared/actions.py
@@ -211,7 +211,7 @@ async def microsd_upgrade(menu, label, item):
from glob import dis, PSRAM
from files import dfu_parse
from utils import check_firmware_hdr
- from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_MAX_LENGTH_MK4
+ from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_MAX_DFU_SIZE_MK4
if version.has_battery:
import battery
@@ -222,7 +222,7 @@ async def microsd_upgrade(menu, label, item):
return
force_vdisk = item.arg
- fn = await file_picker(suffix='.dfu', min_size=0x7800, max_size=FW_MAX_LENGTH_MK4,
+ fn = await file_picker(suffix='.dfu', min_size=0x7800, max_size=FW_MAX_DFU_SIZE_MK4,
force_vdisk=force_vdisk)
if not fn: return
### stm32/sigheader.h
@@ -49,9 +49,13 @@ typedef struct {
// - but practical limit for our-protocol USB upgrades: 786432 (or else settings damaged)
#define FW_MAX_LENGTH (0x100000 - 0x8000)
-// 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)
+// Mk4/Q1: 2MB less 128k bootrom and 512k LFS2, so firmware stays
+// inside the world-checksum-covered flash region. 4k aligned.
+#define FW_MAX_LENGTH_MK4 (0x200000 - 0x20000 - 0x80000)
+
+// Max .dfu file size for the MicroSD file picker: above limit, plus the
+// bootloader element (BL_FLASH_SIZE) and DFU wrapper of a -factory.dfu image.
+#define FW_MAX_DFU_SIZE_MK4 (FW_MAX_LENGTH_MK4 + 0x1c000 + 0x400)
// Arguments to be used w/ python's struct module.
#define FWH_PY_FORMAT "<I8s8sIIII8s20s64s"
### stm32/sigheader.py
@@ -33,9 +33,13 @@
# - but practical limit for our-protocol USB upgrades: 786432 (or else settings damaged)
FW_MAX_LENGTH = (0x100000 - 0x8000)
-# 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)
+# Mk4/Q1: 2MB less 128k bootrom and 512k LFS2, so firmware stays
+# inside the world-checksum-covered flash region. 4k aligned.
+FW_MAX_LENGTH_MK4 = (0x200000 - 0x20000 - 0x80000)
+
+# Max .dfu file size for the MicroSD file picker: above limit, plus the
+# bootloader element (BL_FLASH_SIZE) and DFU wrapper of a -factory.dfu image.
+FW_MAX_DFU_SIZE_MK4 = (FW_MAX_LENGTH_MK4 + 0x1c000 + 0x400)
# Arguments to be used w/ python's struct module.
FWH_PY_FORMAT = "<I8s8sIIII8s20s64s"Why this scored 34/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.