fix(core/bootloader): fix battery drain when waiting on unofficial firmware confirmation
What changed, and why it matters
This commit fixes a battery-drain bug in the Trezor bootloader that occurs when the device is left waiting for the user to confirm unofficial firmware. The old code kept the processor awake in a tight loop polling the touchscreen or buttons. The new code lets the device sleep and only wakes it when the user actually touches the screen or presses buttons, and it hibernates after 40 seconds of inactivity. There is no direct security vulnerability being patched; it is a power-management and usability fix.
Treat as a routine bug fix rather than a security patch. Users on affected models should update to a bootloader containing this fix to avoid battery drain when installing unofficial firmware. No immediate incident response is required.
Security signals we found
Battery-denial / device-availability issue mitigated by adding hibernation after 40 s of inactivity
Busy-wait polling replaced with event-driven sysevents_poll to reduce power consumption
No input validation, buffer handling, or cryptographic changes present
Evidence from the diff
The patch moves ui_click() from bootui.c into a new ui_helpers.c and rewrites it to use sysevents_poll() with a 100 ms timeout instead of busy-spinning on touch_get_event()/button_is_down(). When no input event is signalled, the loop now checks a 40-second hibernation deadline and calls pm_hibernate() on supported devices. The change is gated behind USE_POWER_MANAGER and applies to the T3W1 model per the changelog fragment. It reduces power consumption while the bootloader is waiting for user confirmation of unofficial firmware.
Changed components
Trezor Core bootloaderTrezor T3W1 (per changelog fragment)bootloader UI input handling (touch/button confirmation)Inspect captured patch +166 / −44
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index 7f22a36e..9f04d211 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -138,6 +138,7 @@ ui.init_ui(TREZOR_MODEL, "bootloader", RUST_UI_FEATURES)
SOURCE_BOOTLOADER = [
'embed/projects/bootloader/bootui.c',
'embed/projects/bootloader/main.c',
+ 'embed/projects/bootloader/ui_helpers.c',
'embed/projects/bootloader/workflow/wf_firmware_update.c',
'embed/projects/bootloader/workflow/wf_wipe_device.c',
'embed/projects/bootloader/workflow/wf_get_features.c',
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index fe72c919..b047b02e 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -107,6 +107,7 @@ SOURCE_NANOPB = [
SOURCE_BOOTLOADER = [
'embed/projects/bootloader/bootui.c',
'embed/projects/bootloader/main.c',
+ 'embed/projects/bootloader/ui_helpers.c',
'embed/projects/bootloader/workflow/wf_firmware_update.c',
'embed/projects/bootloader/workflow/wf_wipe_device.c',
'embed/projects/bootloader/workflow/wf_get_features.c',
diff --git a/core/embed/projects/bootloader/.changelog.d/5993.fixed b/core/embed/projects/bootloader/.changelog.d/5993.fixed
new file mode 100644
index 00000000..9e1f65e0
--- /dev/null
+++ b/core/embed/projects/bootloader/.changelog.d/5993.fixed
@@ -0,0 +1 @@
+[T3W1] Fixed battery drain when waiting for unofficial firmware confirmation.
diff --git a/core/embed/projects/bootloader/bootui.c b/core/embed/projects/bootloader/bootui.c
index f9063b5a..5f182c15 100644
--- a/core/embed/projects/bootloader/bootui.c
+++ b/core/embed/projects/bootloader/bootui.c
@@ -47,44 +47,6 @@ void ui_set_initial_setup(bool initial) { initial_setup = initial; }
bool ui_get_initial_setup(void) { return initial_setup; }
-#if defined USE_TOUCH
-#include <io/touch.h>
-
-void ui_click(void) {
- // flush touch events if any
- while (touch_get_event()) {
- }
- // wait for TOUCH_START
- while ((touch_get_event() & TOUCH_START) == 0) {
- }
- // wait for TOUCH_END
- while ((touch_get_event() & TOUCH_END) == 0) {
- }
- // flush touch events if any
- while (touch_get_event()) {
- }
-}
-
-#elif defined USE_BUTTON
-#include <io/button.h>
-
-void ui_click(void) {
- for (;;) {
- if (button_is_down(BTN_LEFT) && button_is_down(BTN_RIGHT)) {
- break;
- }
- }
- for (;;) {
- if (!button_is_down(BTN_LEFT) && !button_is_down(BTN_RIGHT)) {
- break;
- }
- }
-}
-
-#else
-#error "No input method defined"
-#endif
-
void ui_screen_boot(const vendor_header *const vhdr,
const image_header *const hdr, int wait) {
bool show_string = ((vhdr->vtrust & VTRUST_NO_STRING) == 0);
diff --git a/core/embed/projects/bootloader/bootui.h b/core/embed/projects/bootloader/bootui.h
index b99bb937..bb6e98de 100644
--- a/core/embed/projects/bootloader/bootui.h
+++ b/core/embed/projects/bootloader/bootui.h
@@ -45,12 +45,6 @@
void ui_screen_boot(const vendor_header* const vhdr,
const image_header* const hdr, int wait);
-// Waits until the user confirms the untrusted firmware
-//
-// Implementation is device specific - it wait's until
-// the user presses a button, touches the display
-void ui_click(void);
-
uint32_t ui_screen_intro(const vendor_header* const vhdr,
const image_header* const hdr, bool fw_ok);
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index 8599df11..04cd5fb4 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -86,6 +86,7 @@
#endif
#include "bootui.h"
+#include "ui_helpers.h"
#include "version_check.h"
#include "wire/wire_iface_usb.h"
#include "workflow/workflow.h"
diff --git a/core/embed/projects/bootloader/ui_helpers.c b/core/embed/projects/bootloader/ui_helpers.c
new file mode 100644
index 00000000..ca5e5e5e
--- /dev/null
+++ b/core/embed/projects/bootloader/ui_helpers.c
@@ -0,0 +1,136 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <sys/sysevent.h>
+#include <sys/systick.h>
+
+#ifdef USE_POWER_MANAGER
+#include <sys/power_manager.h>
+#endif
+
+#if defined USE_TOUCH
+#include <io/touch.h>
+#elif defined USE_BUTTON
+#include "io/button.h"
+#else
+#error "No input method defined"
+#endif
+
+#define TIME_TO_HIBERNATE_MS 40000
+
+typedef enum {
+ RES_NONE = 0,
+ RES_CLICKED = 2,
+} result_t;
+
+#ifdef USE_TOUCH
+static result_t process_event(bool* layout_state, sysevents_t* signalled) {
+ if ((signalled->read_ready & 1 << SYSHANDLE_TOUCH) == 0) {
+ return RES_NONE;
+ }
+
+ uint32_t event = touch_get_event();
+
+ if (*layout_state && ((event & TOUCH_END) != 0)) {
+ return RES_CLICKED;
+ }
+
+ if (!*layout_state && ((event & TOUCH_START) != 0)) {
+ *layout_state = true;
+ return RES_NONE;
+ }
+
+ return RES_NONE;
+}
+#elif defined USE_BUTTON
+static result_t process_event(bool* layout_state, sysevents_t* signalled) {
+ if ((signalled->read_ready & 1 << SYSHANDLE_BUTTON) == 0) {
+ return RES_NONE;
+ }
+
+ button_event_t event = {0};
+
+ if (!button_get_event(&event)) {
+ return RES_NONE;
+ }
+
+ if (*layout_state && !button_is_down(BTN_LEFT) &&
+ !button_is_down(BTN_RIGHT)) {
+ return RES_CLICKED;
+ }
+
+ if (!*layout_state && button_is_down(BTN_LEFT) && button_is_down(BTN_RIGHT)) {
+ *layout_state = true;
+ return RES_NONE;
+ }
+
+ return RES_NONE;
+}
+#endif
+
+void ui_click(void) {
+ sysevents_t awaited = {0};
+ sysevents_t signalled = {0};
+
+#ifdef USE_TOUCH
+ awaited.read_ready |= 1 << SYSHANDLE_TOUCH;
+#elif defined USE_BUTTON
+ awaited.read_ready |= 1 << SYSHANDLE_BUTTON;
+#endif
+
+#ifdef USE_TOUCH
+ // flush touch events if any
+ while (touch_get_event() != 0) {
+ }
+#elif defined USE_BUTTON
+ button_event_t event = {0};
+ while (button_get_event(&event)) {
+ }
+#endif
+
+#ifdef USE_POWER_MANAGER
+ uint32_t deadline = ticks_timeout(TIME_TO_HIBERNATE_MS);
+#endif
+
+ bool layout_state = 0;
+
+ // wait for TOUCH_START
+ while (true) {
+ sysevents_poll(&awaited, &signalled, ticks_timeout(100));
+ if (signalled.read_ready != 0) {
+ switch (process_event(&layout_state, &signalled)) {
+ case RES_CLICKED:
+ return;
+ default:
+ break;
+ }
+
+#ifdef USE_POWER_MANAGER
+ deadline = ticks_timeout(TIME_TO_HIBERNATE_MS);
+#endif
+ }
+#ifdef USE_POWER_MANAGER
+ if (ticks_expired(deadline)) {
+ pm_hibernate();
+ }
+#endif
+ }
+}
diff --git a/core/embed/projects/bootloader/ui_helpers.h b/core/embed/projects/bootloader/ui_helpers.h
new file mode 100644
index 00000000..5c040318
--- /dev/null
+++ b/core/embed/projects/bootloader/ui_helpers.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+// Waits until the user confirms the untrusted firmware
+//
+// Implementation is device-specific - it wait's until
+// the user presses a button, touches the display
+void ui_click(void);
Why this scored 21/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.