feat(core/prodtest): allow reading and resetting telemetry data in prodtest
What changed, and why it matters
This commit adds two new factory-test commands to Trezor's production-test firmware: one that reads battery telemetry (temperature extremes, error flags, cycle count) and one that resets that telemetry. The reset command is blocked on real production hardware unless the device is in manufacturing mode, but it is a no-op in the emulator. There is no direct evidence in the commit that this fixes a security vulnerability; it appears to be a feature addition for production testing.
Treat as a low-risk feature commit. If reviewing for security, verify that prodtest firmware is not deployable to end-user devices, that the manufacturing-mode check cannot be bypassed, and that telemetry-reset cannot be triggered through the normal device bootloader or firmware update flow. No immediate patching is indicated by the diff alone.
Security signals we found
New factory/debug CLI surface added to prodtest firmware
telemetry-reset can erase operational telemetry data
Reset is restricted by manufacturing-mode check on production builds, but no authentication or authorization beyond unit_properties()->locked is visible in the diff
No input validation beyond argument count; output formatting uses fixed-width hex for battery_errors
Telemetry data is read-only via this interface; no write path other than reset
Evidence from the diff
The change introduces telemetry-read and telemetry-reset CLI commands in the prodtest firmware image. telemetry-read calls telemetry_get() and prints min/max battery temperature, battery error flags, and cycle count. telemetry-reset calls telemetry_reset(), which reinitializes the telemetry record on STM32U5 and does nothing on the Unix emulator. The reset is gated by unit_properties()->locked only when PRODUCTION is defined. The telemetry subsystem is added to the prodtest build for both hardware and emulator targets.
Changed components
core/embed/projects/prodtest/cmd/prodtest_telemetry.ccore/embed/sec/telemetry/inc/sec/telemetry.hcore/embed/sec/telemetry/stm32u5/telemetry.ccore/embed/sec/telemetry/unix/telemetry.ccore/SConscript.prodtestcore/SConscript.prodtest_emucore/embed/projects/prodtest/README.mdInspect captured patch +127 / −0
diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest
index 0c0973da..315cfd7d 100644
--- a/core/SConscript.prodtest
+++ b/core/SConscript.prodtest
@@ -42,6 +42,7 @@ FEATURES_WANTED = [
"secure_mode",
"smp",
"suspend",
+ 'telemetry',
"tropic",
"usb",
"usb_iface_vcp",
@@ -220,6 +221,7 @@ SOURCE_PRODTEST = [
'embed/projects/prodtest/cmd/prodtest_tamper.c',
'embed/projects/prodtest/cmd/prodtest_sbu.c',
'embed/projects/prodtest/cmd/prodtest_secure_channel.c',
+ 'embed/projects/prodtest/cmd/prodtest_telemetry.c',
'embed/projects/prodtest/cmd/prodtest_touch.c',
'embed/projects/prodtest/cmd/prodtest_tropic.c',
'embed/projects/prodtest/cmd/prodtest_unit_test.c',
diff --git a/core/SConscript.prodtest_emu b/core/SConscript.prodtest_emu
index 8f9af6cc..158684b7 100644
--- a/core/SConscript.prodtest_emu
+++ b/core/SConscript.prodtest_emu
@@ -31,6 +31,7 @@ FEATURES_WANTED = [
"rgb_led",
"sd_card",
"secure_mode",
+ 'telemetry',
"tropic",
"usb",
"usb_iface_vcp",
@@ -191,6 +192,7 @@ SOURCE_PRODTEST = [
'embed/projects/prodtest/cmd/prodtest_tamper.c',
'embed/projects/prodtest/cmd/prodtest_sbu.c',
'embed/projects/prodtest/cmd/prodtest_secure_channel.c',
+ 'embed/projects/prodtest/cmd/prodtest_telemetry.c',
'embed/projects/prodtest/cmd/prodtest_touch.c',
'embed/projects/prodtest/cmd/prodtest_tropic.c',
'embed/projects/prodtest/cmd/secure_channel.c',
diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md
index 21d86dae..ea686e07 100644
--- a/core/embed/projects/prodtest/README.md
+++ b/core/embed/projects/prodtest/README.md
@@ -1580,3 +1580,31 @@ Example:
rtc-get
OK 2025 07 03 14 23 00 4
```
+
+### telemetry-read
+Retrieves stored telemetry data, including minimum and maximum recorded battery temperatures, battery error flags, and battery cycle count.
+
+Response format:
+`OK <min_temp_c> <max_temp_c> <battery_errors> <battery_cycles>`
+
+- `min_temp_c`: Minimum temperature in millidegrees Celsius (°C × 1000)
+- `max_temp_c`: Maximum temperature in millidegrees Celsius (°C × 1000)
+- `battery_errors`: Battery error flags as hexadecimal
+- `battery_cycles`: Battery cycles in millicycles (cycles × 1000)
+
+If telemetry data is not available (not yet initialized), the command returns an error.
+
+Example:
+```
+telemetry-read
+OK 18500 42300 0x00 12450
+```
+
+### telemetry-reset
+Resets all telemetry data to initial state. This clears all stored telemetry values and reinitializes them to default values.
+
+Example:
+```
+telemetry-reset
+OK
+```
diff --git a/core/embed/projects/prodtest/cmd/prodtest_telemetry.c b/core/embed/projects/prodtest/cmd/prodtest_telemetry.c
new file mode 100644
index 00000000..ebddacaa
--- /dev/null
+++ b/core/embed/projects/prodtest/cmd/prodtest_telemetry.c
@@ -0,0 +1,82 @@
+/*
+ * 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/>.
+ */
+
+#ifdef USE_TELEMETRY
+
+#include <trezor_rtl.h>
+
+#include <rtl/cli.h>
+#include <sec/telemetry.h>
+#include <sec/unit_properties.h>
+
+static void prodtest_telemetry(cli_t* cli) {
+ if (cli_arg_count(cli) > 0) {
+ cli_error_arg_count(cli);
+ return;
+ }
+
+ telemetry_data_t data;
+
+ if (!telemetry_get(&data)) {
+ cli_error(cli, CLI_ERROR_NODATA, "Telemetry data not available");
+ return;
+ }
+
+ int32_t min_temp = (int32_t)(data.min_temp_c * 1000.0f);
+ int32_t max_temp = (int32_t)(data.max_temp_c * 1000.0f);
+ int32_t battery_cycles = (int32_t)(data.battery_cycles * 1000.0f);
+
+ cli_ok(cli, "%d %d 0x%02X %d", min_temp, max_temp, data.battery_errors.all,
+ battery_cycles);
+}
+
+static void prodtest_telemetry_reset(cli_t* cli) {
+ if (cli_arg_count(cli) > 0) {
+ cli_error_arg_count(cli);
+ return;
+ }
+
+#if PRODUCTION
+ if (unit_properties()->locked) {
+ cli_error(cli, CLI_ERROR, "Device is not in manufacturing mode.");
+ return;
+ }
+#endif
+
+ telemetry_reset();
+ cli_ok(cli, "");
+}
+
+// clang-format off
+
+PRODTEST_CLI_CMD(
+ .name = "telemetry-read",
+ .func = prodtest_telemetry,
+ .info = "Read telemetry data",
+ .args = ""
+);
+
+PRODTEST_CLI_CMD(
+ .name = "telemetry-reset",
+ .func = prodtest_telemetry_reset,
+ .info = "Reset telemetry data",
+ .args = ""
+);
+
+#endif // USE_TELEMETRY
diff --git a/core/embed/sec/telemetry/inc/sec/telemetry.h b/core/embed/sec/telemetry/inc/sec/telemetry.h
index 50cff223..c5cd8c3d 100644
--- a/core/embed/sec/telemetry/inc/sec/telemetry.h
+++ b/core/embed/sec/telemetry/inc/sec/telemetry.h
@@ -87,3 +87,10 @@ void telemetry_update_battery_cycles(float battery_cycles_inc);
* @return true if values are available (initialized), false otherwise.
*/
bool telemetry_get(telemetry_data_t* out);
+
+/**
+ * @brief Reset telemetry data to initial state.
+ *
+ * Clears all stored telemetry data and reinitializes to default values.
+ */
+void telemetry_reset(void);
diff --git a/core/embed/sec/telemetry/stm32u5/telemetry.c b/core/embed/sec/telemetry/stm32u5/telemetry.c
index 102ec702..c846e4cd 100644
--- a/core/embed/sec/telemetry/stm32u5/telemetry.c
+++ b/core/embed/sec/telemetry/stm32u5/telemetry.c
@@ -129,4 +129,6 @@ bool telemetry_get(telemetry_data_t* out) {
return true;
}
+void telemetry_reset(void) { telemetry_init_record(); }
+
#endif
diff --git a/core/embed/sec/telemetry/unix/telemetry.c b/core/embed/sec/telemetry/unix/telemetry.c
index 57621fb7..f9bb8464 100644
--- a/core/embed/sec/telemetry/unix/telemetry.c
+++ b/core/embed/sec/telemetry/unix/telemetry.c
@@ -30,4 +30,8 @@ bool telemetry_get(telemetry_data_t* out) {
return true;
}
+void telemetry_reset(void) {
+ // No-op for emulator
+}
+
#endif
Why this scored 22/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.