Merge pull request #660 from Foundation-Devices/fix/legacy-settings-overflow
What changed, and why it matters
This commit fixes a bug in how the Passport hardware wallet saves its settings to internal flash memory. Previously, the code checked whether the settings data was too large only after it had already picked and erased a flash storage slot. If the data was too big, the code hit a broken 'assert false' statement that would crash the device. The fix moves the size check earlier, before any flash slot is touched, and replaces the crash with a proper error. A new unit test confirms that oversized settings are rejected before any flash operation happens.
Treat as a low-to-moderate reliability and potential security fix. Review whether any user-facing path can produce settings near DATA_SIZE, and ensure the new ValueError is handled gracefully by callers rather than leaving settings unsaved. Consider backporting to firmware branches that still use the old assert-false ordering.
Security signals we found
Buffer size validation moved before flash write/erase operations
Replaced broken 'assert false' crash path with explicit ValueError
Added unit test for oversized settings rejection
Potential flash corruption / wear due to erase-before-validation
Device availability impact from unhandled assertion/crash on oversized settings
Evidence from the diff
In ports/stm32/boards/Passport/modules/settings.py, Settings.save() previously called next_addr() and erased a flash sector before encoding and sizing the JSON buffer. The oversized check used ‘assert false’ (a misspelling of False), which is always a syntax error in MicroPython and would raise an AssertionError rather than a clean failure. The patch moves ujson.dumps().encode(‘utf8’) and the len(json_buf) > DATA_SIZE check to the top of save(), raising ValueError before next_addr() or flash erase. It also adds a unit test that monkey-patches an OversizedSettings object and verifies next_addr() is never reached.
Changed components
ports/stm32/boards/Passport/modules/settings.pyports/stm32/boards/Passport/modules/tests/test_unit.pyports/stm32/boards/Passport/modules/tests/unit/settings.pyInspect captured patch +34 / −11
### ports/stm32/boards/Passport/modules/settings.py
@@ -316,6 +316,11 @@ def save(self):
# Render as JSON, encrypt and write it
self.curr_dict['_revision'] = self.curr_dict.get('_revision', 0) + 1
+ # Validate the encoded size before selecting or erasing a flash slot.
+ json_buf = ujson.dumps(self.curr_dict).encode('utf8')
+ if len(json_buf) > DATA_SIZE:
+ raise ValueError('JSON data is larger than {} bytes.'.format(DATA_SIZE))
+
addr = self.next_addr()
# print('===============================================================')
@@ -328,17 +333,6 @@ def save(self):
chk = trezorcrypto.sha256()
- # Create the JSON string as bytes
- json_buf = ujson.dumps(self.curr_dict).encode('utf8')
-
- # Ensure data is not too big
- if len(json_buf) > DATA_SIZE:
- # print('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
- # print(' JSON TOO BIG!')
- # print('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
- assert false, 'JSON data is larger than {}.'.format(DATA_SIZE)
- return
-
# Create a zero-filled byte buf
padded_buf = bytearray(DATA_SIZE)
### ports/stm32/boards/Passport/modules/tests/test_unit.py
@@ -28,6 +28,10 @@ def test_seedqr_codec(test):
assert test('seedqr_codec.py') == b'OK'
+def test_settings(test):
+ assert test('settings.py') == b'OK'
+
+
def test_ui(test):
assert test('ui.py') == b'OK'
### ports/stm32/boards/Passport/modules/tests/unit/settings.py
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: 2026 Foundation Devices, Inc. <hello@foundation.xyz>
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from settings import DATA_SIZE, Settings
+
+
+class OversizedSettings:
+ def __init__(self):
+ self.curr_dict = {'value': 'x' * DATA_SIZE}
+
+ def next_addr(self):
+ raise RuntimeError('Oversized settings reached flash slot selection')
+
+
+settings = OversizedSettings()
+
+try:
+ Settings.save(settings)
+except ValueError as exc:
+ assert str(DATA_SIZE) in str(exc)
+else:
+ raise RuntimeError('Oversized settings should fail before selecting a flash slot')
+
+return_value.write(b'OK')Why this scored 59/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.