What changed, and why it matters
This commit adds a new internal telemetry feature to the Trezor firmware that records the minimum and maximum battery temperature in a small, protected memory area (backup RAM). It only stores a float value for the coldest and hottest battery readings and does not send data anywhere. There is no obvious security bug in the code, but it is a new subsystem that touches low-level firmware components and could become relevant if future changes connect it to untrusted inputs or expose it externally.
Treat as a routine feature commit with no immediate security action. Reviewers should verify in follow-up commits that telemetry data is not exposed to untrusted host interfaces, that backup RAM access controls are appropriate for public items, and that the SMCALL surface cannot be abused to corrupt the backup RAM region or leak sensitive data.
Security signals we found
New secure-monitor call surface added (two SMCALL numbers)
Pointer arguments to SMCALL_TELEMETRY_GET_BATT_TEMP_MIN_MAX are validated with probe_write_access() before dereference
Telemetry data is stored in backup RAM as BACKUP_RAM_ITEM_PUBLIC
No changelog entry despite new feature ([no changelog])
Data is local only; no network or USB exfiltration code is present in this commit
Evidence from the diff
The patch introduces a telemetry subsystem gated by USE_TELEMETRY. It adds telemetry.h/telemetry.c, which persist a telemetry_data_t structure (min/max battery temperature) in backup RAM under BACKUP_RAM_KEY_TELEMETRY. The power manager calls telemetry_update_battery_temp() when PMIC data is ready. Two new SMCALLs (SMCALL_TELEMETRY_UPDATE_BATT_TEMP and SMCALL_TELEMETRY_GET_BATT_TEMP_MIN_MAX) allow the non-secure world to update/read the telemetry via the secure monitor; the read path verifies write access to the output pointers with probe_write_access(). The feature is enabled for bootloader, kernel, and secmon builds.
Changed components
core/embed/util/telemetry/telemetry.ccore/embed/util/telemetry/inc/util/telemetry.hcore/embed/sys/power_manager/stm32u5/power_monitoring.ccore/embed/sys/smcall/stm32/smcall_dispatch.ccore/embed/sys/smcall/stm32/smcall_stubs.ccore/embed/sys/smcall/stm32/smcall_verifiers.ccore/embed/sys/smcall/stm32/smcall_verifiers.hcore/embed/sys/smcall/stm32/smcall_numbers.hcore/embed/sys/backup_ram/inc/sys/backup_ram.hcore/SConscript.bootloadercore/SConscript.kernelcore/SConscript.secmoncore/site_scons/models/T3W1/trezor_t3w1_revC.pyInspect captured patch +254 / −0
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index cbc70211..b85086ee 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -27,6 +27,7 @@ FEATURES_WANTED = [
"secure_domain",
"secure_mode",
"suspend",
+ "telemetry",
"usb",
"usb_iface_wire",
]
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index a310a10c..2e2ebe2d 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -60,6 +60,7 @@ FEATURES_WANTED = [
"smp",
"storage",
"suspend",
+ "telemetry",
"tropic",
"usb",
"usb_iface_wire",
diff --git a/core/SConscript.secmon b/core/SConscript.secmon
index 130ce7b4..ee99f936 100644
--- a/core/SConscript.secmon
+++ b/core/SConscript.secmon
@@ -43,6 +43,7 @@ FEATURES_WANTED = [
"secure_mode",
"storage",
"suspend",
+ "telemetry",
"tropic",
"nrf_auth"
]
diff --git a/core/embed/sys/backup_ram/inc/sys/backup_ram.h b/core/embed/sys/backup_ram/inc/sys/backup_ram.h
index af0eadbb..33683afe 100644
--- a/core/embed/sys/backup_ram/inc/sys/backup_ram.h
+++ b/core/embed/sys/backup_ram/inc/sys/backup_ram.h
@@ -24,6 +24,7 @@
/** Global keys for items stored in the backup RAM */
#define BACKUP_RAM_KEY_PM_RECOVERY 0x0001 // Power management recovery data
#define BACKUP_RAM_KEY_BLE_SETTINGS 0x0002 // BLE settings
+#define BACKUP_RAM_KEY_TELEMETRY 0x0003 // Telemetry data (min/max temps etc.)
/** Maximum size of data stored under a single key in backup RAM */
#define BACKUP_RAM_MAX_KEY_DATA_SIZE 512
diff --git a/core/embed/sys/power_manager/stm32u5/power_monitoring.c b/core/embed/sys/power_manager/stm32u5/power_monitoring.c
index f708c80e..34a92828 100644
--- a/core/embed/sys/power_manager/stm32u5/power_monitoring.c
+++ b/core/embed/sys/power_manager/stm32u5/power_monitoring.c
@@ -25,6 +25,10 @@
#include <sys/systick.h>
#include <trezor_rtl.h>
+#ifdef USE_TELEMETRY
+#include <util/telemetry.h>
+#endif
+
#include "../fuel_gauge/battery_model.h"
#include "../fuel_gauge/fuel_gauge.h"
#include "../stwlc38/stwlc38.h"
@@ -77,6 +81,11 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
// Get wireless charger data
stwlc38_get_report(&drv->wireless_data);
+#ifdef USE_TELEMETRY
+ // Update telemetry with current battery temperature
+ telemetry_update_battery_temp(drv->pmic_data.ntc_temp);
+#endif
+
pm_parse_power_source_state(drv);
// Run battery charging controller
diff --git a/core/embed/sys/smcall/stm32/smcall_dispatch.c b/core/embed/sys/smcall/stm32/smcall_dispatch.c
index b5f5d728..22c1bf5a 100644
--- a/core/embed/sys/smcall/stm32/smcall_dispatch.c
+++ b/core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -403,6 +403,27 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
} break;
#endif // USE_BACKUP_RAM
+#ifdef USE_TELEMETRY
+ // ------------------------------------------------------------------
+ // Telemetry
+ case SMCALL_TELEMETRY_UPDATE_BATT_TEMP: {
+ union {
+ float f;
+ uint32_t u;
+ } u32_to_float = {.u = args[0]};
+
+ float temp = u32_to_float.f;
+ telemetry_update_battery_temp__verified(temp);
+ } break;
+
+ case SMCALL_TELEMETRY_GET_BATT_TEMP_MIN_MAX: {
+ float *out_min_c = (float *)args[0];
+ float *out_max_c = (float *)args[1];
+ args[0] =
+ telemetry_get_battery_temp_min_max__verified(out_min_c, out_max_c);
+ } break;
+#endif // USE_TELEMETRY
+
default:
system_exit_fatal("Invalid smcall", __FILE__, __LINE__);
break;
diff --git a/core/embed/sys/smcall/stm32/smcall_numbers.h b/core/embed/sys/smcall/stm32/smcall_numbers.h
index 6312b4c8..5fc3039a 100644
--- a/core/embed/sys/smcall/stm32/smcall_numbers.h
+++ b/core/embed/sys/smcall/stm32/smcall_numbers.h
@@ -104,4 +104,7 @@ typedef enum {
SMCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY,
+ SMCALL_TELEMETRY_UPDATE_BATT_TEMP,
+ SMCALL_TELEMETRY_GET_BATT_TEMP_MIN_MAX,
+
} smcall_number_t;
diff --git a/core/embed/sys/smcall/stm32/smcall_stubs.c b/core/embed/sys/smcall/stm32/smcall_stubs.c
index 2f6a9460..2f854eed 100644
--- a/core/embed/sys/smcall/stm32/smcall_stubs.c
+++ b/core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -407,4 +407,28 @@ secbool secret_validate_nrf_pairing(const uint8_t *message, size_t msg_len,
#endif
+#ifdef USE_TELEMETRY
+
+// =============================================================================
+// telemetry.h
+// =============================================================================
+
+#include <util/telemetry.h>
+
+void telemetry_update_battery_temp(float temp_c) {
+ union {
+ float f;
+ uint32_t u;
+ } float_to_u32 = {.f = temp_c};
+
+ smcall_invoke1(float_to_u32.u, SMCALL_TELEMETRY_UPDATE_BATT_TEMP);
+}
+
+bool telemetry_get_battery_temp_min_max(float *out_min_c, float *out_max_c) {
+ return (bool)smcall_invoke2((uint32_t)out_min_c, (uint32_t)out_max_c,
+ SMCALL_TELEMETRY_GET_BATT_TEMP_MIN_MAX);
+}
+
+#endif
+
#endif // defined(KERNEL) && defined(USE_SECMON_LAYOUT)
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.c b/core/embed/sys/smcall/stm32/smcall_verifiers.c
index ab777c51..96872a5c 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.c
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -574,4 +574,30 @@ access_violation:
#endif
+#ifdef USE_TELEMETRY
+#include <util/telemetry.h>
+
+// Telemetry verifiers
+void telemetry_update_battery_temp__verified(float temp_c) {
+ // No pointers to verify; simple value pass-through
+ telemetry_update_battery_temp(temp_c);
+}
+
+bool telemetry_get_battery_temp_min_max__verified(float *out_min_c,
+ float *out_max_c) {
+ if (out_min_c && !probe_write_access(out_min_c, sizeof(*out_min_c))) {
+ goto access_violation;
+ }
+ if (out_max_c && !probe_write_access(out_max_c, sizeof(*out_max_c))) {
+ goto access_violation;
+ }
+
+ return telemetry_get_battery_temp_min_max(out_min_c, out_max_c);
+
+access_violation:
+ apptask_access_violation();
+ return false;
+}
+#endif // USE_TELEMETRY
+
#endif // SECMON
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.h b/core/embed/sys/smcall/stm32/smcall_verifiers.h
index eca1c646..bda3552d 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.h
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.h
@@ -164,4 +164,17 @@ secbool secret_validate_nrf_pairing__verified(const uint8_t *message,
size_t mac_len);
#endif
+#ifdef USE_TELEMETRY
+// ---------------------------------------------------------------------
+// Telemetry
+// ---------------------------------------------------------------------
+
+#include <util/telemetry.h>
+
+void telemetry_update_battery_temp__verified(float temp_c);
+
+bool telemetry_get_battery_temp_min_max__verified(float *out_min_c,
+ float *out_max_c);
+#endif // USE_TELEMETRY
+
#endif // SECMON
diff --git a/core/embed/util/telemetry/inc/util/telemetry.h b/core/embed/util/telemetry/inc/util/telemetry.h
new file mode 100644
index 00000000..aec62fb9
--- /dev/null
+++ b/core/embed/util/telemetry/inc/util/telemetry.h
@@ -0,0 +1,52 @@
+/*
+ * 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 Telemetry data structure.
+ */
+typedef struct {
+ float min_temp_c; /**< Minimum recorded battery temperature in Celsius. */
+ float max_temp_c; /**< Maximum recorded battery temperature in Celsius. */
+} telemetry_data_t;
+
+/**
+ * @brief Record current battery temperature (in Celsius) into telemetry
+ * storage.
+ *
+ * Updates persisted min/max values:
+ * - minimum can only decrease
+ * - maximum can only increase
+ *
+ * @param temp_c Current battery temperature in Celsius.
+ */
+void telemetry_update_battery_temp(float temp_c);
+
+/**
+ * @brief Retrieve stored min/max battery temperature (in Celsius).
+ *
+ * @param[out] out Pointer to where the telemetry data will be stored (may be
+ * NULL).
+ *
+ * @return true if values are available (initialized), false otherwise.
+ */
+bool telemetry_get(telemetry_data_t* out);
diff --git a/core/embed/util/telemetry/telemetry.c b/core/embed/util/telemetry/telemetry.c
new file mode 100644
index 00000000..842a9a60
--- /dev/null
+++ b/core/embed/util/telemetry/telemetry.c
@@ -0,0 +1,97 @@
+/*
+ * 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 SECURE_MODE
+
+#include <trezor_types.h>
+
+#include <sys/backup_ram.h>
+
+#include <util/telemetry.h>
+
+// Versioning for persisted telemetry structure
+#define TELEMETRY_DATA_VERSION 0x0001
+
+typedef struct {
+ uint16_t version;
+ uint8_t initialized; // 0 = not set, 1 = valid data present
+ uint8_t reserved; // alignment/padding
+ telemetry_data_t data;
+} telemetry_t;
+
+static bool telemetry_read(telemetry_t* out) {
+ size_t size = 0;
+ if (!backup_ram_read(BACKUP_RAM_KEY_TELEMETRY, out, sizeof(*out), &size)) {
+ return false;
+ }
+ if (size != sizeof(*out)) {
+ return false;
+ }
+ if (out->version != TELEMETRY_DATA_VERSION) {
+ return false;
+ }
+ return true;
+}
+
+static bool telemetry_write(const telemetry_t* data) {
+ return backup_ram_write(BACKUP_RAM_KEY_TELEMETRY, BACKUP_RAM_ITEM_PUBLIC,
+ data, sizeof(*data));
+}
+
+void telemetry_update_battery_temp(float temp_c) {
+ telemetry_t telemetry;
+ bool have = telemetry_read(&telemetry) && telemetry.initialized == 1;
+
+ if (!have) {
+ telemetry.version = TELEMETRY_DATA_VERSION;
+ telemetry.initialized = 1;
+ telemetry.reserved = 0;
+ telemetry.data.min_temp_c = temp_c;
+ telemetry.data.max_temp_c = temp_c;
+ telemetry_write(&telemetry);
+ return;
+ }
+
+ bool changed = false;
+ if (temp_c < telemetry.data.min_temp_c) {
+ telemetry.data.min_temp_c = temp_c; // min can only decrease
+ changed = true;
+ }
+ if (temp_c > telemetry.data.max_temp_c) {
+ telemetry.data.max_temp_c = temp_c; // max can only increase
+ changed = true;
+ }
+
+ if (changed) {
+ telemetry_write(&telemetry);
+ }
+}
+
+bool telemetry_get(telemetry_data_t* out) {
+ telemetry_t telemetry;
+ if (!telemetry_read(&telemetry) || telemetry.initialized != 1) {
+ return false;
+ }
+ if (out != NULL) {
+ *out = telemetry.data;
+ }
+ return true;
+}
+
+#endif
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index 0aed85e3..3c7e1caa 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -237,6 +237,11 @@ def configure(
paths += ["embed/util/hw_revision/inc"]
sources += ["embed/util/hw_revision/stm32/hw_revision.c"]
+ if "telemetry" in features_wanted:
+ sources += ["embed/util/telemetry/telemetry.c"]
+ paths += ["embed/util/telemetry/inc"]
+ defines += [("USE_TELEMETRY", "1")]
+
defines += [
"FRAMEBUFFER",
"DISPLAY_RGBA8888",
Why this scored 23/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.