feat(core): implement touch event emulation on driver level
What changed, and why it matters
This commit adds a debug-only feature to the Trezor hardware wallet firmware that lets developers inject fake touch-screen events (taps, swipes, clicks) into the touch driver. It is gated behind the DEBUGLINK build flag, which is used only in development/testing builds, not in production firmware shipped to users. The change itself is a feature implementation; there is no direct evidence in the commit that it fixes a security bug or that it introduces an exploitable vulnerability in production devices.
Review whether DEBUGLINK builds are adequately restricted to authorized development/test devices and cannot be enabled or triggered on production hardware. Verify that the debug touch queue cannot be reached from production firmware or via the debug link interface by an untrusted host. Consider whether additional access controls or compile-time assertions are needed to prevent accidental inclusion in release builds.
Security signals we found
New debug-only code path that can override real touch input state
Synthetic touch events are enqueued and consumed by the driver
DEBUGLINK preprocessor gating suggests test/development use only
No production-enabling mechanism visible in the diff
No changelog entry; commit marked [no changelog]
Evidence from the diff
The patch introduces a new touch_debug module (touch_debug.c/h) that maintains a small queue of synthetic touch events (TOUCH_START/TOUCH_END packed x/y coordinates). In touch_poll.c, when DEBUGLINK is defined, the normal touch state machine can be overridden by these synthetic events via touch_poll_get_state() and on_event_poll(). A reinstate_state mechanism prevents spurious events when transitioning back from debug to real touch state. syslog_config.h adds a logging category for the new module. The feature is strictly conditional on DEBUGLINK, a debug/test build configuration.
Changed components
core/embed/io/touch/touch_poll.ccore/embed/io/touch/touch_debug.ccore/embed/io/touch/touch_debug.hcore/embed/sys/dbg/inc/sys/syslog_config.hInspect captured patch +230 / −2
diff --git a/core/embed/io/touch/touch_debug.c b/core/embed/io/touch/touch_debug.c
new file mode 100644
index 00000000..b4659ba3
--- /dev/null
+++ b/core/embed/io/touch/touch_debug.c
@@ -0,0 +1,95 @@
+/*
+ * 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 "touch_debug.h"
+
+#include <trezor_rtl.h>
+
+#include <io/touch.h>
+#include <io/tsqueue.h>
+#include <sys/logging.h>
+
+#define TOUCH_DEBUG_QUEUE_SIZE 8
+
+LOG_DECLARE(touch_debug)
+
+typedef struct {
+ uint32_t queue_items[TOUCH_DEBUG_QUEUE_SIZE];
+ tsqueue_entry_t queue_entries[TOUCH_DEBUG_QUEUE_SIZE];
+ tsqueue_t queue;
+ uint32_t state;
+ bool state_active;
+} touch_debug_t;
+
+static touch_debug_t touch_debug;
+
+void touch_debug_init(void) {
+ memset(&touch_debug, 0, sizeof(touch_debug_t));
+ tsqueue_init(&touch_debug.queue, touch_debug.queue_entries,
+ (uint8_t*)touch_debug.queue_items, sizeof(uint32_t),
+ TOUCH_DEBUG_QUEUE_SIZE);
+}
+
+void touch_debug_deinit(void) {
+ memset(&touch_debug, 0, sizeof(touch_debug_t));
+}
+
+void touch_debug_start(uint32_t x, uint32_t y) {
+ uint32_t event = TOUCH_START | touch_pack_xy(x, y);
+
+ if (!tsqueue_enqueue(&touch_debug.queue, (uint8_t*)&event, sizeof(event),
+ NULL)) {
+ LOG_WARN("touch debug queue full");
+ }
+}
+
+void touch_debug_end(uint32_t x, uint32_t y) {
+ uint32_t event = TOUCH_END | touch_pack_xy(x, y);
+
+ if (!tsqueue_enqueue(&touch_debug.queue, (uint8_t*)&event, sizeof(event),
+ NULL)) {
+ LOG_WARN("touch debug queue full");
+ }
+}
+
+void touch_debug_click(uint32_t x, uint32_t y) {
+ touch_debug_start(x, y);
+ touch_debug_end(x, y);
+}
+
+bool touch_debug_active(void) { return touch_debug.state_active; }
+
+uint32_t touch_debug_get_state(void) { return touch_debug.state; }
+
+void touch_debug_next(void) {
+ uint32_t state = 0;
+
+ if (!tsqueue_dequeue(&touch_debug.queue, (uint8_t*)&state, sizeof(state),
+ NULL, NULL)) {
+ return;
+ }
+
+ touch_debug.state = state;
+
+ if (TOUCH_END & state) {
+ touch_debug.state_active = false;
+ } else {
+ touch_debug.state_active = true;
+ }
+}
diff --git a/core/embed/io/touch/touch_debug.h b/core/embed/io/touch/touch_debug.h
new file mode 100644
index 00000000..316917dc
--- /dev/null
+++ b/core/embed/io/touch/touch_debug.h
@@ -0,0 +1,75 @@
+/*
+ * 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
+
+#include <trezor_types.h>
+
+/**
+ * @brief Initialize the touch debug module.
+ */
+void touch_debug_init(void);
+
+/**
+ * @brief Deinitialize the touch debug module.
+ */
+void touch_debug_deinit(void);
+
+/**
+ * @brief Signal the start of a touch event at the given coordinates.
+ *
+ * @param x The x-coordinate of the touch event.
+ * @param y The y-coordinate of the touch event.
+ */
+void touch_debug_start(uint32_t x, uint32_t y);
+
+/**
+ * @brief Signal the end of a touch event at the given coordinates.
+ *
+ * @param x The x-coordinate of the touch event.
+ * @param y The y-coordinate of the touch event.
+ */
+void touch_debug_end(uint32_t x, uint32_t y);
+
+/**
+ * @brief Signal a click event at the given coordinates.
+ *
+ * @param x The x-coordinate of the click event.
+ * @param y The y-coordinate of the click event.
+ */
+void touch_debug_click(uint32_t x, uint32_t y);
+
+/**
+ * @brief Advance to the next touch debug state.
+ */
+void touch_debug_next(void);
+
+/**
+ * @brief Check if touch debug is currently active.
+ *
+ * @return True if active, false otherwise.
+ */
+bool touch_debug_active(void);
+
+/**
+ * @brief Get the current touch debug state.
+ *
+ * @return The current state as a 32-bit unsigned integer.
+ */
+uint32_t touch_debug_get_state(void);
diff --git a/core/embed/io/touch/touch_poll.c b/core/embed/io/touch/touch_poll.c
index 68c1bb83..c34e8b4e 100644
--- a/core/embed/io/touch/touch_poll.c
+++ b/core/embed/io/touch/touch_poll.c
@@ -29,6 +29,10 @@
#include "touch_poll.h"
+#ifdef DEBUGLINK
+#include "touch_debug.h"
+#endif
+
LOG_DECLARE(touch_driver);
typedef struct {
@@ -43,6 +47,8 @@ typedef struct {
uint16_t last_x;
// Previously reported y-coordinate
uint16_t last_y;
+ // Debug state currently overrides the state
+ bool debug_active;
} touch_fsm_t;
// Touch state machine for each task
@@ -52,10 +58,20 @@ static touch_fsm_t g_touch_tls[SYSTASK_MAX_TASKS];
static const syshandle_vmt_t g_touch_handle_vmt;
bool touch_poll_init(void) {
+#ifdef DEBUGLINK
+ touch_debug_init();
+#endif
+
return syshandle_register(SYSHANDLE_TOUCH, &g_touch_handle_vmt, NULL);
}
-void touch_poll_deinit(void) { syshandle_unregister(SYSHANDLE_TOUCH); }
+void touch_poll_deinit(void) {
+ syshandle_unregister(SYSHANDLE_TOUCH);
+
+#ifdef DEBUGLINK
+ touch_debug_deinit();
+#endif
+}
static void touch_fsm_clear(touch_fsm_t* fsm) {
memset(fsm, 0, sizeof(touch_fsm_t));
@@ -153,13 +169,40 @@ static inline char event_type_char(uint32_t event) {
: '-';
}
+static uint32_t touch_poll_get_state(touch_fsm_t* fsm, bool* reinstate) {
+#ifdef DEBUGLINK
+ if (touch_debug_active()) {
+ fsm->debug_active = true;
+ return touch_debug_get_state();
+ }
+
+ if (fsm->debug_active) {
+ fsm->debug_active = false;
+ *reinstate = true;
+ return touch_debug_get_state();
+ }
+#else
+ UNUSED(fsm);
+ UNUSED(reinstate);
+#endif
+
+ return touch_get_state();
+}
+
uint32_t touch_get_event(void) {
touch_fsm_t* fsm = &g_touch_tls[systask_id(systask_active())];
- uint32_t touch_state = touch_get_state();
+ bool reinstate_state = false;
+ uint32_t touch_state = touch_poll_get_state(fsm, &reinstate_state);
uint32_t event = touch_fsm_get_event(fsm, touch_state);
+ // when leaving debug state, we force the state so that change from debug to
+ // standard does not produce events
+ if (reinstate_state) {
+ fsm->state = touch_get_state();
+ }
+
if (event != 0) {
LOG_DBG("touch_event: ev=%c, x=%d, y=%d", event_type_char(event),
touch_unpack_x(event), touch_unpack_y(event));
@@ -179,6 +222,17 @@ static void on_event_poll(void* context, bool read_awaited,
if (read_awaited) {
uint32_t touch_state = touch_get_state();
+
+#ifdef DEBUGLINK
+ touch_debug_next();
+#endif
+
+#ifdef DEBUGLINK
+ if (touch_debug_active()) {
+ touch_state = touch_debug_get_state();
+ }
+#endif
+
syshandle_signal_read_ready(SYSHANDLE_TOUCH, &touch_state);
}
}
diff --git a/core/embed/sys/dbg/inc/sys/syslog_config.h b/core/embed/sys/dbg/inc/sys/syslog_config.h
index 98356cb3..baa4f544 100644
--- a/core/embed/sys/dbg/inc/sys/syslog_config.h
+++ b/core/embed/sys/dbg/inc/sys/syslog_config.h
@@ -48,6 +48,10 @@
#define SYSLOG_touch_driver_MAX_LOG_LEVEL SYSLOG_DEFAULT_LOG_LEVEL
#endif
+#ifndef SYSLOG_touch_debug_MAX_LOG_LEVEL
+#define SYSLOG_touch_debug_MAX_LOG_LEVEL SYSLOG_DEFAULT_LOG_LEVEL
+#endif
+
#ifndef SYSLOG_display_driver_MAX_LOG_LEVEL
#define SYSLOG_display_driver_MAX_LOG_LEVEL SYSLOG_DEFAULT_LOG_LEVEL
#endif
Why this scored 32/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.