Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions components/esp_modem/src/esp_modem_uart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "esp_modem_config.h"
#include "exception_stub.hpp"
#include "cxx_include/esp_modem_dte.hpp"
#include "cxx_include/esp_modem_primitives.hpp"
#include "uart_resource.hpp"

static const char *TAG = "uart_terminal";
Expand Down Expand Up @@ -64,6 +65,7 @@ class UartTerminal : public Terminal {

void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f) override
{
Scoped<Lock> lock(on_read_lock);
on_read = std::move(f);
}

Expand Down Expand Up @@ -95,6 +97,10 @@ class UartTerminal : public Terminal {
QueueHandle_t event_queue;
uart_resource uart;
SignalGroup signal;
/// Mutex to protect on_read callback from being updated while being called in \c task.
/// Must be recursive as the on_read callback may call set_read_cb to change the callback while being called.
/// Declared before task_handle so it outlives the UART task.
Lock on_read_lock;
Comment thread
cursor[bot] marked this conversation as resolved.
uart_task task_handle;
};

Expand All @@ -121,8 +127,13 @@ void UartTerminal::task()
switch (event.type) {
case UART_DATA:
uart_get_buffered_data_len(uart.port, &len);
if (len && on_read) {
on_read(nullptr, len);
{
// Lock on_read_lock while checking and calling on_read to
// prevent data-race between check and call
Scoped<Lock> lock(on_read_lock);
if (len && on_read) {
on_read(nullptr, len);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callback destroyed during nested set

Medium Severity

The new recursive on_read_lock is documented to allow on_read to call set_read_cb and replace the callback while it runs, but the task still invokes on_read directly. A nested set_read_cb assigns over that same std::function, destroying the callable mid-invocation. That path is used by DTE::on_read when a reply completes, and can cause heap corruption or crashes.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cebfab0. Configure here.

}
break;
case UART_FIFO_OVF:
Expand Down Expand Up @@ -163,6 +174,8 @@ void UartTerminal::task()
}
} else {
uart_get_buffered_data_len(uart.port, &len);

Scoped<Lock> lock(on_read_lock);
if (len && on_read) {
on_read(nullptr, len);
}
Expand Down