Fix BBQr share of Unicode text: encode str to UTF-8 before sizing/splitting
What changed, and why it matters
This commit fixes a bug in the COLDCARD Q1 hardware wallet where displaying certain QR codes containing non-English or special characters could cause the device to crash with an assertion failure. The fix ensures text is converted to UTF-8 bytes before calculating QR code sizes, preventing a mismatch between expected and actual data length. There is no direct evidence this bug is exploitable for malicious purposes.
Apply the patch. Consider adding regression tests for multi-byte UTF-8 strings and large JSON payloads in the BBQr rendering path. No immediate incident response is indicated unless crashes have been observed in the field.
Security signals we found
Assertion failure/crash in QR display path
Length mismatch between codepoints and UTF-8 bytes
Denial-of-service-like symptom (device crash on user data display)
No input validation/sanitization for multi-byte text before sizing
Evidence from the diff
In shared/ux_q1.py, the show_bbqr_codes function prepares BBQr (Bitcoin QR) codes. For ‘U’ (Unicode text) and ‘J’ (JSON) payloads, data is a Python str. The code planned QR version sizing by counting codepoints (characters), but base32 encoding consumes UTF-8 bytes. For multi-byte characters (e.g., emoji, CJK, or paper-wallet QR art), the byte length exceeds the codepoint count, causing target_vers to overflow and an assert in show_bbqr_codes to trip. The fix encodes str to UTF-8 bytes before length measurement and slicing, aligning the planning with what b32encode actually processes.
Changed components
shared/ux_q1.pyshow_bbqr_codes()BBQr 'U' (Unicode text) and 'J' (JSON) payload renderingInspect captured patch +5 / −0
diff --git a/shared/ux_q1.py b/shared/ux_q1.py
index 2224b93..c36018f 100644
--- a/shared/ux_q1.py
+++ b/shared/ux_q1.py
@@ -1215,6 +1215,11 @@ async def show_bbqr_codes(type_code, data, msg, already_hex=False):
else:
# default to Base32, because always best option
encoding = '2'
+ if isinstance(data, str):
+ # 'U'/'J' payloads are UTF-8 text; b32encode consumes the UTF-8
+ # bytes, so convert now to keep length/slicing consistent (else
+ # multi-byte chars overflow target_vers -> assert below trips)
+ data = data.encode()
data_len = len(data)
# try a few select resolutions (sizes) in order such that we use either single QR
Why this scored 33/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.