fix(core): fix logging with BLOCK_ON_VCP enabled
What changed, and why it matters
This commit fixes a debug-only logging path used when a special compile-time flag (BLOCK_ON_VCP) is enabled. The old code called a blocking write helper that is not safe from interrupt context, which could have caused the device to freeze or misbehave while printing debug messages. The new code uses a non-blocking loop with a timeout and small delays, and adds documentation warning that the blocking helpers must not be called from interrupt context. This is a hardening/bug-fix change in a debug feature, not a direct fix for an exploitable security vulnerability in normal user-facing operation.
Treat as a defensive hardening fix. No urgent user action is required because the change affects a non-default debug build configuration. If running custom firmware with BLOCK_ON_VCP enabled, ensure this patch is included to avoid device hangs during debug logging.
Security signals we found
Blocking operation in interrupt context replaced with non-blocking retry loop
New documentation warnings that blocking syshandle helpers are unsafe from interrupt context
Debug-only code path (BLOCK_ON_VCP) affected, not default firmware behavior
Potential denial-of-service/hang scenario mitigated for debug builds
Evidence from the diff
In dbg_console_backend.c, the usb_vcp_write() function under BLOCK_ON_VCP previously called syshandle_write_blocking() with a timeout chosen based on whether it was running in thread/SVCall mode or interrupt context. The blocking write helper is documented as unsafe from interrupt context, and the previous timeout logic did not prevent the call. The patch replaces the blocking call with a loop around syshandle_write() (non-blocking), a deadline computed with ticks_timeout(), and systick_delay_ms(1) between retries. It also adds comments in sysevent.h stating that syshandle_read_blocking() and syshandle_write_blocking() must not be called from interrupt context. The change reduces the risk of hangs or undefined behavior when debug logging occurs in interrupt context, but it is gated behind BLOCK_ON_VCP, a non-default debug configuration.
Changed components
core/embed/sys/dbg/stm32/dbg_console_backend.ccore/embed/sys/task/inc/sys/sysevent.hInspect captured patch +28 / −2
diff --git a/core/embed/sys/dbg/stm32/dbg_console_backend.c b/core/embed/sys/dbg/stm32/dbg_console_backend.c
index 2a6fe6d5..0884c282 100644
--- a/core/embed/sys/dbg/stm32/dbg_console_backend.c
+++ b/core/embed/sys/dbg/stm32/dbg_console_backend.c
@@ -25,6 +25,7 @@
#include <sys/dbg_console.h>
#include <sys/sysevent.h>
+#include <sys/systick.h>
#ifdef USE_DBG_CONSOLE_SYSTEM_VIEW
#include "SEGGER_RTT.h"
@@ -79,8 +80,29 @@ static ssize_t usb_vcp_write(const void *data, size_t data_size) {
// In interrupt context, we must not block.
uint32_t ipsr = __get_IPSR();
bool thread_mode = (ipsr == 0 || ipsr == 11); // Thread mode or SVCall
- uint32_t timeout = thread_mode ? 1000 : 0;
- return syshandle_write_blocking(SYSHANDLE_USB_VCP, data, data_size, timeout);
+ uint32_t deadline = ticks_timeout(thread_mode ? 1000 : 0);
+
+ const uint8_t *ptr = (const uint8_t *)data;
+ size_t remaining = data_size;
+
+ while (remaining > 0) {
+ ssize_t written = syshandle_write(SYSHANDLE_USB_VCP, ptr, remaining);
+
+ if (written < 0) {
+ break;
+ }
+
+ ptr += written;
+ remaining -= written;
+
+ if (ticks_expired(deadline)) {
+ break;
+ } else if (remaining > 0) {
+ systick_delay_ms(1);
+ }
+ }
+
+ return data_size - remaining;
#else
return syshandle_write(SYSHANDLE_USB_VCP, data, data_size);
#endif
diff --git a/core/embed/sys/task/inc/sys/sysevent.h b/core/embed/sys/task/inc/sys/sysevent.h
index df74bae0..ce1b7f4a 100644
--- a/core/embed/sys/task/inc/sys/sysevent.h
+++ b/core/embed/sys/task/inc/sys/sysevent.h
@@ -78,6 +78,8 @@ ssize_t syshandle_write(syshandle_t handle, const void* data, size_t data_size);
*
* If the timeout is 0, the function behaves like `syshandle_read`.
*
+ * This function must not be called from an interrupt context
+ *
* @param handle Handle of the device to read from
* @param buffer Pointer to the buffer where the read data will be stored
* @param buffer_size Size of the buffer in bytes
@@ -94,6 +96,8 @@ ssize_t syshandle_read_blocking(syshandle_t handle, void* buffer,
*
* If the timeout is 0, the function behaves like `syshandle_write`.
*
+ * This function must not be called from an interrupt context
+ *
* @param handle Handle of the device to write to
* @param data Pointer to the data to write
* @param data_size Size of the data in bytes
Why this scored 24/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.