ui: reduce stack usage during mnemonic verification
What changed, and why it matters
This commit refactors how a wallet's secret recovery phrase (mnemonic) is handled on screen during verification. Instead of keeping an array of direct memory pointers to each word, it now stores offsets into the original phrase buffer. The stated goal is to reduce stack memory usage. The change also makes the code treat the mnemonic as read-only ('const') in more places, which is a defensive improvement. There is no direct evidence in the commit of an exploitable vulnerability being fixed, but reducing stack pressure and avoiding extra pointers to secret data is a sensible hardening measure.
Treat as a routine hardening/refactoring change. Reviewers may optionally verify that the offset arithmetic cannot overflow and that the mnemonic buffer remains null-terminated after separator replacement, but the diff itself does not indicate an active vulnerability.
Security signals we found
Reduced stack-allocated pointer array for sensitive mnemonic data
Increased use of const-correctness for mnemonic buffer
Defensive assertion added: `len < 16384u` in `change_mnemonic_word_separator`
No explicit security bug or CVE referenced in commit or supplied materials
Evidence from the diff
The patch modifies main/process/mnemonic.c and main/ui/mnemonic.c. It replaces a char* words[MNEMONIC_MAXWORDS] array of pointers into a mutable mnemonic buffer with a uint16_t word_offs[MNEMONIC_MAXWORDS] array of offsets, and passes the original const char* mnemonic alongside the offsets to UI helpers. change_mnemonic_word_separator now writes offsets rather than pointers, and callers compute mnemonic + word_offs[i] at use sites. The mnemonic is also declared const in several UI functions. A small scope reduction for a local message array is included. The commit message frames this as a stack-usage reduction, not a security fix.
Changed components
main/process/mnemonic.cmain/ui/mnemonic.cMnemonic display and confirmation UI flowInspect captured patch +41 / −34
diff --git a/main/process/mnemonic.c b/main/process/mnemonic.c
index 7c72972..d665871 100644
--- a/main/process/mnemonic.c
+++ b/main/process/mnemonic.c
@@ -43,10 +43,10 @@ gui_activity_t* make_mnemonic_setup_method_activity(bool advanced);
gui_activity_t* make_new_mnemonic_activity(void);
gui_activity_t* make_restore_mnemonic_activity(bool temporary_restore);
-void make_show_mnemonic_activities(
- gui_activity_t** first_activity_ptr, gui_activity_t** last_activity_ptr, char* words[], size_t nwords);
+void make_show_mnemonic_activities(gui_activity_t** first_activity_ptr, gui_activity_t** last_activity_ptr,
+ const char* mnemonic, uint16_t word_offs[], size_t nwords);
gui_activity_t* make_confirm_mnemonic_word_activity(gui_view_node_t** text_box_ptr, uint8_t first_word_index,
- uint8_t offset_word_to_confirm, char* words[], size_t nwords);
+ uint8_t offset_word_to_confirm, const char* mnemonic, uint16_t word_offs[], size_t nwords);
gui_activity_t* make_enter_wordlist_word_activity(gui_view_node_t** titletext, bool show_enter_btn,
gui_view_node_t** textbox, gui_view_node_t** backspace, gui_view_node_t** enter, gui_view_node_t** keys,
@@ -239,17 +239,17 @@ cleanup:
}
#endif // CONFIG_HAS_CAMERA
-// Function to change the mnemonic word separator and provide pointers to
+// Function to change the mnemonic word separator and provide offsets to
// the start of the words. Used when confirming one word at a time.
static void change_mnemonic_word_separator(char* mnemonic, const size_t len, const char old_separator,
- const char new_separator, char* words[], const size_t nwords)
+ const char new_separator, uint16_t word_offs[], const size_t nwords)
{
- JADE_ASSERT(mnemonic);
- JADE_ASSERT(words);
+ JADE_ASSERT(mnemonic && len < 16384u);
+ JADE_ASSERT(word_offs);
size_t word = 0, i = 0;
for (/*nothing*/; i < len && word < nwords; ++i, ++word) {
- words[word] = mnemonic + i; // Pointer to the start of each word
+ word_offs[word] = i; // Offset of the start of each word
for (/*nothing*/; i < len; ++i) {
if (mnemonic[i] == old_separator) {
mnemonic[i] = new_separator;
@@ -270,21 +270,23 @@ static bool display_confirm_mnemonic(const size_t nwords, char* mnemonic, const
JADE_ASSERT(mnemonic);
// Show the warning banner screen, user to confirm
- const char* message[] = { "These words are your", "wallet. Keep them", "protected and offline." };
- if (!await_continueback_activity(NULL, message, 3, true, "blkstrm.com/phrase")) {
- // Abandon before we begin
- return false;
+ {
+ const char* message[] = { "These words are your", "wallet. Keep them", "protected and offline." };
+ if (!await_continueback_activity(NULL, message, 3, true, "blkstrm.com/phrase")) {
+ // Abandon before we begin
+ return false;
+ }
}
// Change the word separator to a null so we can treat each word as a terminated string.
- char* words[MNEMONIC_MAXWORDS]; // large enough for 12 and 24 word mnemonic
- change_mnemonic_word_separator(mnemonic, mnemonic_len, ' ', '\0', words, nwords);
+ uint16_t word_offs[MNEMONIC_MAXWORDS]; // large enough for 12 and 24 word mnemonic
+ change_mnemonic_word_separator(mnemonic, mnemonic_len, ' ', '\0', word_offs, nwords);
bool mnemonic_confirmed = false;
// create the "show mnemonic" activities only once and then reuse them
gui_activity_t* first_activity = NULL;
gui_activity_t* last_activity = NULL;
- make_show_mnemonic_activities(&first_activity, &last_activity, words, nwords);
+ make_show_mnemonic_activities(&first_activity, &last_activity, mnemonic, word_offs, nwords);
JADE_ASSERT(first_activity);
JADE_ASSERT(last_activity);
@@ -318,7 +320,7 @@ static bool display_confirm_mnemonic(const size_t nwords, char* mnemonic, const
const size_t selected = i + offset_word_to_confirm;
gui_view_node_t* textbox = NULL;
gui_activity_t* const confirm_act
- = make_confirm_mnemonic_word_activity(&textbox, i, offset_word_to_confirm, words, nwords);
+ = make_confirm_mnemonic_word_activity(&textbox, i, offset_word_to_confirm, mnemonic, word_offs, nwords);
JADE_LOGD("selected = %u", selected);
// Pick some other words from the mnemonic as options, but avoid
@@ -344,8 +346,9 @@ static bool display_confirm_mnemonic(const size_t nwords, char* mnemonic, const
random_words[j] = new_word;
}
+ // set the first word
uint8_t index = get_uniform_random_byte(num_words_options);
- gui_update_text(textbox, words[random_words[index]]); // set the first word
+ gui_update_text(textbox, mnemonic + word_offs[random_words[index]]);
gui_set_current_activity(confirm_act);
@@ -358,12 +361,12 @@ static bool display_confirm_mnemonic(const size_t nwords, char* mnemonic, const
switch (ev_id) {
case GUI_WHEEL_LEFT_EVENT:
index = (index + num_words_options - 1) % num_words_options;
- gui_update_text(textbox, words[random_words[index]]);
+ gui_update_text(textbox, mnemonic + word_offs[random_words[index]]);
break;
case GUI_WHEEL_RIGHT_EVENT:
index = (index + 1) % num_words_options;
- gui_update_text(textbox, words[random_words[index]]);
+ gui_update_text(textbox, mnemonic + word_offs[random_words[index]]);
break;
default:
@@ -1309,14 +1312,16 @@ void initialise_with_mnemonic(const bool temporary_restore, const bool force_qr_
}
} else {
// Initial welcome screen, or straight to 'recovery' screen if doing temporary restore
- const char* message[] = { "For setup instructions", "visit blockstream.com/", "jade" };
if (temporary_restore) {
act = make_restore_mnemonic_activity(temporary_restore);
- } else if (await_continueback_activity(NULL, message, 3, true, "blkstrm.com/jade")) {
- act = make_mnemonic_setup_type_activity();
} else {
- // User decided against it
- goto cleanup;
+ const char* message[] = { "For setup instructions", "visit blockstream.com/", "jade" };
+ if (await_continueback_activity(NULL, message, 3, true, "blkstrm.com/jade")) {
+ act = make_mnemonic_setup_type_activity();
+ } else {
+ // User decided against it
+ goto cleanup;
+ }
}
bool got_mnemonic = false;
diff --git a/main/ui/mnemonic.c b/main/ui/mnemonic.c
index ab4d32d..7e37a8e 100644
--- a/main/ui/mnemonic.c
+++ b/main/ui/mnemonic.c
@@ -105,7 +105,7 @@ gui_activity_t* make_bip85_mnemonic_words_activity(void)
}
static void make_show_new_mnemonic_page(link_activity_t* page_act, const size_t nwords, const size_t first_index,
- char* word1, char* word2, char* word3, char* word4)
+ const char* word1, const char* word2, const char* word3, const char* word4)
{
JADE_ASSERT(page_act);
JADE_ASSERT(word1);
@@ -138,7 +138,7 @@ static void make_show_new_mnemonic_page(link_activity_t* page_act, const size_t
gui_set_parent(vsplit, parent);
char prefixed_word[16];
- char* words[] = { word1, word2, word3, word4 };
+ const char* words[] = { word1, word2, word3, word4 };
for (int irow = 0; irow < 4; ++irow) {
// index-prefixed word, eg. "1: river"
const int ret = snprintf(prefixed_word, sizeof(prefixed_word), "%2u: %s", first_index + irow + 1, words[irow]);
@@ -159,12 +159,12 @@ static void make_show_new_mnemonic_page(link_activity_t* page_act, const size_t
page_act->next_button = last_page ? NULL : hdrbtns[1].btn;
}
-void make_show_mnemonic_activities(
- gui_activity_t** first_activity_ptr, gui_activity_t** last_activity_ptr, char* words[], const size_t nwords)
+void make_show_mnemonic_activities(gui_activity_t** first_activity_ptr, gui_activity_t** last_activity_ptr,
+ const char* mnemonic, uint16_t word_offs[], const size_t nwords)
{
JADE_INIT_OUT_PPTR(first_activity_ptr);
JADE_INIT_OUT_PPTR(last_activity_ptr);
- JADE_ASSERT(words);
+ JADE_ASSERT(word_offs);
// Support 12-word and 24-word mnemonics only
JADE_ASSERT(nwords == 12 || nwords == 24);
@@ -175,8 +175,9 @@ void make_show_mnemonic_activities(
const size_t npages = nwords / 4; // 4 words per page
for (size_t j = 0; j < npages; ++j) {
- make_show_new_mnemonic_page(
- &page_act, nwords, j * 4, words[j * 4], words[j * 4 + 1], words[j * 4 + 2], words[j * 4 + 3]);
+ uint16_t* offsets = word_offs + (j * 4);
+ make_show_new_mnemonic_page(&page_act, nwords, j * 4, mnemonic + offsets[0], mnemonic + offsets[1],
+ mnemonic + offsets[2], mnemonic + offsets[3]);
gui_chain_activities(&page_act, &act_info);
}
@@ -185,12 +186,13 @@ void make_show_mnemonic_activities(
}
gui_activity_t* make_confirm_mnemonic_word_activity(gui_view_node_t** text_box, const uint8_t first_word_index,
- const uint8_t offset_word_to_confirm, char* words[], const size_t nwords)
+ const uint8_t offset_word_to_confirm, const char* mnemonic, uint16_t word_offs[], const size_t nwords)
{
JADE_INIT_OUT_PPTR(text_box);
JADE_ASSERT(first_word_index <= nwords - 3);
JADE_ASSERT(offset_word_to_confirm < 3); // top (0), middle (1) or bottom (2)
- JADE_ASSERT(words);
+ JADE_ASSERT(mnemonic);
+ JADE_ASSERT(word_offs);
// Title/hint index (1-based index)
char str[32];
@@ -226,7 +228,7 @@ gui_activity_t* make_confirm_mnemonic_word_activity(gui_view_node_t** text_box,
gui_set_padding(node, GUI_MARGIN_ALL_DIFFERENT, 0, 10, 0, 0);
gui_set_parent(node, hsplit);
- gui_make_text(&node, words[index], TFT_WHITE);
+ gui_make_text(&node, mnemonic + word_offs[index], TFT_WHITE);
gui_set_text_noise(node, TFT_BLACK);
gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
gui_set_parent(node, hsplit);
Why this scored 29/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.