What changed, and why it matters
This commit reorganizes how a COLDCARD device decides it is 'bricked' and what it does next. It moves the brick check from the main boot code into the PIN-handling code, and slightly cleans up a calculator/login helper. The change appears intended to make bricked devices stop earlier in boot and still offer a calculator-only recovery screen on QWERTY models. There is no clear vulnerability being fixed; it reads more like a defensive hardening or refactor.
Treat as a routine refactor/hardening commit. Reviewers should verify that moving enforce_brick() into pincodes.py does not change the order of security-critical initialization (e.g., that secrets are not exposed before the brick check) and that the allow_login=False path still prevents PIN authentication on bricked devices. No urgent action required.
Security signals we found
Refactor of bricking/anti-tamper boot path
PIN login code touched but logic preserved
Removal of unused import in GPU module
No new input validation or bounds checks added
No explicit bug fix or CVE reference in commit message
Evidence from the diff
The patch refactors bricked-device handling. Previously main.py checked callgate.get_is_bricked() directly and, if bricked, launched a login_repl with allow_login=False before forcing DFU mode. Now main.py calls pa.enforce_brick() inside the pincodes module, which performs the same get_is_bricked() check and the same allow_login=False calculator loop before enter_dfu(3). The calc.py change is a pure structural simplification: it folds the allow_login guard into the elif conditions instead of nesting. gpu.py removes an unused B2A import. The functional behavior is essentially unchanged, but the brick check now happens after the pincodes module is imported and inside the PIN-attempt object context.
Changed components
shared/main.pyshared/pincodes.pyshared/calc.pyshared/gpu.pyInspect captured patch +42 / −41
diff --git a/shared/calc.py b/shared/calc.py
index 7755afa..4c1e10b 100644
--- a/shared/calc.py
+++ b/shared/calc.py
@@ -64,31 +64,29 @@ Example Commands:
elif ln in ('help', 'cls', 'rand'):
# no need for () for these commands
ans = state[ln]()
- elif allow_login:
- # without this flag, PIN codes ignored
- if re_pin.match(ln) and (len(ln) <= 13):
- # try login
- m = re_pin.match(ln)
- ln = m.group(1)+ '-' + m.group(2)
- print(ln)
- from pincodes import pa
- try:
- pa.setup(ln)
- ok = pa.login()
- if ok: return
- except RuntimeError as exc:
- # I'm a brick and other stuff can happen here
- # - especially AUTH_FAIL when pin is just wrong.
- if exc.args[0] == 'AUTH_FAIL':
- pa.attempts_left -= 1
- ans = '%-7d # %d tries remain' % (eval(ln), pa.attempts_left)
- else:
- ans = 'Error: ' + repr(exc.args)
-
- elif re_prefix.match(ln) and len(ln) <= 7:
- # show words
- from pincodes import pa
- ans = pa.prefix_words(ln[:-1].encode())
+ elif allow_login and re_pin.match(ln) and (len(ln) <= 13):
+ # try login
+ m = re_pin.match(ln)
+ ln = m.group(1)+ '-' + m.group(2)
+ print(ln)
+ from pincodes import pa
+ try:
+ pa.setup(ln)
+ ok = pa.login()
+ if ok: return
+ except RuntimeError as exc:
+ # I'm a brick and other stuff can happen here
+ # - especially AUTH_FAIL when pin is just wrong.
+ if exc.args[0] == 'AUTH_FAIL':
+ pa.attempts_left -= 1
+ ans = '%-7d # %d tries remain' % (eval(ln), pa.attempts_left)
+ else:
+ ans = 'Error: ' + repr(exc.args)
+
+ elif allow_login and re_prefix.match(ln) and (len(ln) <= 7):
+ # show words
+ from pincodes import pa
+ ans = pa.prefix_words(ln[:-1].encode())
else:
if any((b in ln) for b in blacklist):
ans = None
diff --git a/shared/gpu.py b/shared/gpu.py
index 4e09246..35da5b1 100644
--- a/shared/gpu.py
+++ b/shared/gpu.py
@@ -8,7 +8,6 @@
#
import utime, struct
import uasyncio as asyncio
-from utils import B2A
from machine import Pin
from ustruct import pack
diff --git a/shared/main.py b/shared/main.py
index 6ddc972..87c5822 100644
--- a/shared/main.py
+++ b/shared/main.py
@@ -81,27 +81,17 @@ glob.settings = settings
async def more_setup():
# Boot up code; splash screen is being shown
-
try:
from files import CardSlot
CardSlot.setup()
- # check for bricked system early
- import callgate
- if callgate.get_is_bricked():
- print("SE bricked")
- try:
- # regardless of settings.calc forever calculator after brickage
- if version.has_qwerty:
- from calc import login_repl
- await login_repl(allow_login=False)
- finally:
- # die right away if it's not going to work
- callgate.enter_dfu(3)
-
# This "pa" object holds some state shared w/ bootloader about the PIN
try:
from pincodes import pa
+ # check for bricked system early
+ # bricked CC not going past this point
+ pa.enforce_brick()
+
pa.setup(b'') # just to see where we stand.
is_blank = pa.is_blank()
except RuntimeError as e:
diff --git a/shared/pincodes.py b/shared/pincodes.py
index e951452..863b860 100644
--- a/shared/pincodes.py
+++ b/shared/pincodes.py
@@ -3,7 +3,7 @@
# pincodes.py - manage PIN code (which map to wallet seeds)
#
import ustruct, ckcc, version, chains, stash
-from callgate import enter_dfu
+from callgate import enter_dfu, get_is_bricked
from bip39 import wordlist_en
# See ../stm32/bootloader/pins.h for source of these constants.
@@ -529,6 +529,20 @@ class PinAttempt:
# Mk4 only
# return (tc_flags, tc_arg)
return self.delay_required, self.delay_achieved
+
+ @staticmethod
+ async def enforce_brick():
+ # check for bricked system early
+ if get_is_bricked():
+ try:
+ # regardless of settings.calc forever calculator after brickage
+ # for Q models fom version 5.X.X
+ if version.has_qwerty:
+ from calc import login_repl
+ await login_repl(allow_login=False)
+ finally:
+ # die right away if it's not going to work
+ enter_dfu(3)
# singleton
Why this scored 42/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.