fix(core/eckhart): correct res confirm_with_menu
What changed, and why it matters
This commit fixes a UI flow bug in the Trezor hardware wallet's 'Eckhart' layout. Previously, when a user opened a menu during a confirmation screen and chose an item, the wrong internal signal could be sent: the first menu item was always treated as 'Info' and the second as 'Cancel', even if the menu only contained a cancel option. The fix tracks which menu items actually exist and maps the user's selection to the correct result (Info or Cancel).
Treat as a low-to-moderate UI correctness fix. Review related Eckhart confirmation flows for similar hardcoded index assumptions, and verify that the fixed behavior is covered by device tests before release.
Security signals we found
UI state-machine logic error
Incorrect mapping of user menu selection to flow result
Potential unintended confirmation path if cancel is misread as info
Fix hardcodes menu item semantics and tracks them dynamically
Evidence from the diff
The confirm_with_menu flow in core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs previously hardcoded FlowMsg::Choice(0) to mean INFO and FlowMsg::Choice(1) to mean CANCELLED. However, the optional extra_menu_label (which provides the INFO item) is not always present. When absent, the menu contains only the cancel item, so selecting it would emit Choice(0) and be misinterpreted as INFO instead of CANCELLED. The patch introduces a heapless::Vec to record the semantic identifiers (MENU_ITEM_INFO, MENU_ITEM_CANCEL) of the items actually added to the menu, and maps the selected index to the correct semantic choice before returning it.
Changed components
core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rsTrezor Safe hardware wallet UI flow (Eckhart layout)Inspect captured patch +18 / −7
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
index c3338764e..c45b4f589 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
@@ -1,3 +1,5 @@
+use heapless::Vec;
+
use crate::{
error,
maybe_trace::MaybeTrace,
@@ -23,6 +25,8 @@ use super::super::{
};
const TIMEOUT_MS: u32 = 2000;
+const MENU_ITEM_CANCEL: usize = 0;
+const MENU_ITEM_INFO: usize = 1;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmWithMenu {
@@ -44,8 +48,8 @@ impl FlowController for ConfirmWithMenu {
match (self, msg) {
(Self::Value, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
(Self::Value, FlowMsg::Info) => Self::Menu.goto(),
- (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Info),
- (Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Cancelled),
+ (Self::Menu, FlowMsg::Choice(MENU_ITEM_INFO)) => self.return_msg(FlowMsg::Info),
+ (Self::Menu, FlowMsg::Choice(MENU_ITEM_CANCEL)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Cancelled) => Self::Value.goto(),
_ => self.do_nothing(),
}
@@ -93,24 +97,31 @@ pub fn new_confirm_with_menu<T: AllowedTextContent + MaybeTrace + 'static>(
TextScreenMsg::Menu => Some(FlowMsg::Info),
});
- let mut menu_items = VerticalMenu::<ShortMenuVec>::empty();
+ // Menu
+ let mut menu = VerticalMenu::<ShortMenuVec>::empty();
+ let mut menu_items = Vec::<usize, 2>::new();
if let Some(extra_menu_label) = extra_menu_label {
- menu_items.item(Button::new_menu_item(
+ menu.item(Button::new_menu_item(
extra_menu_label,
theme::menu_item_title(),
));
+ unwrap!(menu_items.push(MENU_ITEM_INFO));
}
- menu_items.item(Button::new_menu_item(
+ menu.item(Button::new_menu_item(
cancel_menu_label,
theme::menu_item_title_orange(),
));
+ unwrap!(menu_items.push(MENU_ITEM_CANCEL));
- let content_menu = VerticalMenuScreen::new(menu_items)
+ let content_menu = VerticalMenuScreen::new(menu)
.with_header(Header::new(TString::empty()).with_close_button())
.map(move |msg| match msg {
- VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
+ VerticalMenuScreenMsg::Selected(i) => {
+ let selected_item = menu_items[i];
+ Some(FlowMsg::Choice(selected_item))
+ }
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
_ => None,
});
Why this scored 44/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.