fix(core): unify MAX_LENGTH for SimpleChoice
What changed, and why it matters
This commit tightens a safety check in the Trezor hardware wallet's user-interface code. It makes sure that a small on-screen menu component can hold at least as many items as the firmware might ever ask it to display, preventing a capacity mismatch that could crash the device when a user is choosing recovery-seed word counts or similar options. There is no direct evidence in the commit that this crash was exploitable to steal funds or bypass security.
Treat as a hardening/defensive fix. Review whether `MAX_MENU_ITEMS` is ever set to 6 in production firmware and confirm the assertion fires at compile time for all build configurations. No urgent user action is indicated by the commit itself.
Security signals we found
Capacity mismatch between menu-item source limit and UI component storage could cause a runtime panic (denial-of-service on device)
Compile-time assertion added to enforce invariant between MAX_MENU_ITEMS and SimpleChoice capacity
Magic number 5 replaced by exported named constant to reduce future drift
Change is marked [no changelog] and contains no security disclosure language
Evidence from the diff
The change unifies the MAX_LENGTH constant used by the Caesar layout’s SimpleChoice component and exposes it as SIMPLE_CHOICE_MAX_LENGTH. It raises the value from 5 to 6 and adds a compile-time assert!(MAX_LENGTH >= crate::ui::ui_firmware::MAX_MENU_ITEMS). Callers now use the exported constant instead of the bare literal 5. Similar compile-time assertions in Delizia and Eckhart vertical-menu code are now gated behind the micropython feature. The intent is defensive: prevent a future increase of MAX_MENU_ITEMS from causing a panic in Vec::from_iter or push operations that expect a fixed-capacity heapless vector.
Changed components
core/embed/rust/src/ui/layout_caesar/component/input_methods/simple_choice.rscore/embed/rust/src/ui/layout_caesar/component/mod.rscore/embed/rust/src/ui/layout_caesar/ui_firmware.rscore/embed/rust/src/ui/layout_delizia/component/vertical_menu.rscore/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rsInspect captured patch +15 / −6
### core/embed/rust/src/ui/layout_caesar/component/input_methods/simple_choice.rs
@@ -9,8 +9,15 @@ use crate::ui::geometry::Rect;
use crate::ui::shape::Renderer;
// So that there is only one implementation, and not multiple generic ones
-// as would be via `const N: usize` generics.
-const MAX_LENGTH: usize = 5;
+// as would be via `const N: usize` generics. Callers must size their `Vec`
+// with this constant rather than repeating the literal.
+pub const MAX_LENGTH: usize = 6;
+
+/// `select_menu()` renders through `SimpleChoice`, handing it a list already
+/// bounded by `MAX_MENU_ITEMS`. Keep the capacity at or above that bound so
+/// raising `MAX_MENU_ITEMS` alone cannot stop this model from building.
+#[cfg(feature = "micropython")]
+const _: () = assert!(MAX_LENGTH >= crate::ui::ui_firmware::MAX_MENU_ITEMS);
struct ChoiceFactorySimple {
choices: Vec<TString<'static>, MAX_LENGTH>,
### core/embed/rust/src/ui/layout_caesar/component/mod.rs
@@ -57,7 +57,7 @@ pub use input_methods::{
number_input::NumberInput,
passphrase::PassphraseEntry,
pin::PinEntry,
- simple_choice::SimpleChoice,
+ simple_choice::{SimpleChoice, MAX_LENGTH as SIMPLE_CHOICE_MAX_LENGTH},
wordlist::{WordlistEntry, WordlistType},
};
#[cfg(feature = "translations")]
### core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
@@ -6,7 +6,7 @@ use super::component::{
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, ChoiceControls,
CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame, Homescreen, Lockscreen,
NumberInput, Page, PassphraseEntry, PinEntry, Progress, ScrollableFrame, ShareWords, ShowMore,
- SimpleChoice, WordlistEntry, WordlistType,
+ SimpleChoice, WordlistEntry, WordlistType, SIMPLE_CHOICE_MAX_LENGTH,
};
use super::{constant, fonts, theme, UICaesar};
use crate::error::Error;
@@ -939,7 +939,7 @@ impl FirmwareUI for UICaesar {
description: TString<'static>,
words: [TString<'static>; MAX_WORD_QUIZ_ITEMS],
) -> Result<impl LayoutMaybeTrace, Error> {
- let words: Vec<TString<'static>, 5> = Vec::from_iter(words);
+ let words: Vec<TString<'static>, SIMPLE_CHOICE_MAX_LENGTH> = Vec::from_iter(words);
// Returning the index of the selected word, not the word itself
let layout = RootComponent::new(
Frame::new(
@@ -955,7 +955,7 @@ impl FirmwareUI for UICaesar {
fn select_word_count(recovery_type: RecoveryType) -> Result<impl LayoutMaybeTrace, Error> {
let title: TString = TR::word_count__title.into();
- let choices: Vec<TString<'static>, 5> = {
+ let choices: Vec<TString<'static>, SIMPLE_CHOICE_MAX_LENGTH> = {
let nums: &[&str] = if matches!(recovery_type, RecoveryType::UnlockRepeatedBackup) {
&["20", "33"]
} else {
### core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs
@@ -320,6 +320,7 @@ pub const VERTICAL_MENU_ITEMS: usize = 6;
/// `MAX_MENU_ITEMS`, pushing with `unwrap!`, which panics on overflow rather
/// than returning an error. Keep the capacity at or above that bound so raising
/// `MAX_MENU_ITEMS` alone cannot turn a rejected menu into a fatal error.
+#[cfg(feature = "micropython")]
const _: () = assert!(VERTICAL_MENU_ITEMS >= crate::ui::ui_firmware::MAX_MENU_ITEMS);
pub type VerticalMenuItems = Vec<VerticalMenuItem, VERTICAL_MENU_ITEMS>;
### core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs
@@ -21,6 +21,7 @@ pub const SHORT_MENU_ITEMS: usize = 6;
/// `MAX_MENU_ITEMS`, and `MenuItems::push` panics on overflow rather than
/// returning an error. Keep the capacity at or above that bound so raising
/// `MAX_MENU_ITEMS` alone cannot turn a rejected menu into a fatal error.
+#[cfg(feature = "micropython")]
const _: () = assert!(SHORT_MENU_ITEMS >= crate::ui::ui_firmware::MAX_MENU_ITEMS);
pub type LongMenuGc = GcBox<Vec<Button, LONG_MENU_ITEMS>>;Why this scored 35/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.