chore(core/delizia): drop header if `show_warning()` title is empty
What changed, and why it matters
This is a small user-interface cleanup change for the Trezor hardware wallet's Delizia layout. It makes warning screens hide their top title bar when the title is empty, and explicitly rejects 'danger' warnings that have no title. There is no direct evidence this fixes a security vulnerability; it appears to be a UI consistency improvement.
No immediate action required. Treat as routine UI maintenance. If auditing, verify that other warning/danger flows cannot reach show_warning() with an empty title in a way that would bypass the new error check.
Security signals we found
Defensive check added: danger warnings with empty titles are rejected via Error::ValueError
UI layout change only; no cryptographic, parsing, or privileged-operation changes observed
Commit message frames change as a chore/UI consistency task, not a security fix
Evidence from the diff
The commit refactors Frame to hold an optional Header and adds a Frame::content constructor for headerless frames. In ui_firmware.rs, show_warning() now returns an error if a ‘danger’ warning is requested with an empty title, while normal warnings simply omit the header. The change parallels an earlier commit (af8bfeb308) and is marked [no changelog].
Changed components
core/embed/rust/src/ui/layout_delizia/component/frame.rscore/embed/rust/src/ui/layout_delizia/ui_firmware.rsInspect captured patch +49 / −24
diff --git a/core/embed/rust/src/ui/layout_delizia/component/frame.rs b/core/embed/rust/src/ui/layout_delizia/component/frame.rs
index e1645b09..e406b85e 100644
--- a/core/embed/rust/src/ui/layout_delizia/component/frame.rs
+++ b/core/embed/rust/src/ui/layout_delizia/component/frame.rs
@@ -86,7 +86,7 @@ impl HorizontalSwipe {
pub struct Frame<T> {
bounds: Rect,
content: T,
- header: Header,
+ header: Option<Header>,
header_update_fn: Option<fn(&T, &mut EventCtx, &mut Header)>,
footer: Option<Footer<'static>>,
footer_update_fn: Option<fn(&T, &mut EventCtx, &mut Footer<'static>)>,
@@ -108,8 +108,16 @@ impl<T> Frame<T>
where
T: Component + Paginate,
{
- #[inline(never)]
pub const fn with_header(header: Header, content: T) -> Self {
+ Self::new(Some(header), content)
+ }
+
+ pub const fn content(content: T) -> Self {
+ Self::new(None, content)
+ }
+
+ #[inline(never)]
+ const fn new(header: Option<Header>, content: T) -> Self {
Self {
bounds: Rect::zero(),
content,
@@ -222,7 +230,10 @@ where
}
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: TString<'static>) {
- self.header.update_title(ctx, new_title);
+ debug_assert!(self.header.is_some());
+ if let Some(header) = &mut self.header {
+ header.update_title(ctx, new_title)
+ }
}
pub fn update_content<F, R>(&mut self, ctx: &mut EventCtx, update_fn: F) -> R
@@ -331,7 +342,9 @@ where
};
if let Some(header_update_fn) = self.header_update_fn {
- header_update_fn(&self.content, ctx, &mut self.header);
+ if let Some(header) = &mut self.header {
+ header_update_fn(&self.content, ctx, header);
+ }
}
if let Some(footer_update_fn) = self.footer_update_fn {
@@ -356,30 +369,30 @@ where
fn frame_event(
horizontal_swipe: &mut HorizontalSwipe,
swipe_config: SwipeConfig,
- header: &mut Header,
+ header: &mut Option<Header>,
ctx: &mut EventCtx,
event: Event,
) -> Option<FlowMsg> {
// horizontal_swipe does not return any message
horizontal_swipe.event(event, swipe_config);
// msg type of header is FlowMsg, which will be the return value
- header.event(ctx, event)
+ header.as_mut()?.event(ctx, event)
}
fn frame_place(
- header: &mut Header,
+ header: &mut Option<Header>,
footer: &mut Option<Footer>,
bounds: Rect,
margin: u8,
) -> Rect {
let margin: i16 = margin.into();
- let header_area = header.place(bounds);
- let mut content_area = bounds
- .inset(Insets::top(header_area.height().max(TITLE_HEIGHT)))
- .inset(Insets::top(theme::SPACING))
- .inset(Insets::top(margin));
-
+ let mut content_area = if let Some(header) = header.as_mut() {
+ let header_height = header.place(bounds).height().max(TITLE_HEIGHT);
+ bounds.inset(Insets::top(header_height + theme::SPACING + margin))
+ } else {
+ bounds
+ };
if let Some(footer) = footer {
// FIXME: spacer at the bottom might be applied also for usage without footer
// but not for VerticalMenu
@@ -409,7 +422,9 @@ where
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Frame");
- t.child("header", &self.header);
+ if let Some(header) = &self.header {
+ t.child("header", header);
+ }
t.child("content", &self.content);
if let Some(footer) = &self.footer {
diff --git a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
index cb773259..d4acf9d8 100644
--- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
@@ -1245,19 +1245,29 @@ impl FirmwareUI for UIDelizia {
} else {
Some(button)
};
- let content = ParagraphVecShort::from_iter([
- Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
- Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value),
- ])
- .into_paragraphs();
+ let content = SwipeContent::new(
+ ParagraphVecShort::from_iter([
+ Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
+ Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, value),
+ ])
+ .into_paragraphs(),
+ );
- let header = Header::left_aligned(title);
- let header = if danger {
- header.with_danger_icon()
+ let frame = if title.is_empty() {
+ if danger {
+ // Disallow showing "dangerous" warning with no header.
+ return Err(Error::ValueError(c"Non-empty title is required"));
+ }
+ Frame::content(content)
} else {
- header.with_warning_low_icon()
+ let header = Header::left_aligned(title);
+ let header = if danger {
+ header.with_danger_icon()
+ } else {
+ header.with_warning_low_icon()
+ };
+ Frame::with_header(header, content)
};
- let frame = Frame::with_header(header, SwipeContent::new(content));
let frame = if danger {
frame.with_tap_footer(action)
} else {
Why this scored 17/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.