From 3361d031de3f47741503ecfcd49202f2027c0e13 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Aug 2026 19:46:13 -0500 Subject: [PATCH] [api] Drop connection instead of crashing when overflow buffer allocation fails (#18802) --- esphome/components/api/api_frame_helper.cpp | 2 +- esphome/components/api/api_overflow_buffer.cpp | 16 ++++++++++++++-- esphome/components/api/api_overflow_buffer.h | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 7425304766..38da444a18 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -172,7 +172,7 @@ APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uin // Queue unsent data into overflow buffer if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, static_cast(sent))) { - HELPER_LOG("Overflow buffer full, dropping connection"); + HELPER_LOG("Overflow buffer full or out of memory, dropping connection"); this->state_ = State::FAILED; return APIError::SOCKET_WRITE_FAILED; } diff --git a/esphome/components/api/api_overflow_buffer.cpp b/esphome/components/api/api_overflow_buffer.cpp index a57a2fb1bb..48d8fe18ba 100644 --- a/esphome/components/api/api_overflow_buffer.cpp +++ b/esphome/components/api/api_overflow_buffer.cpp @@ -1,6 +1,7 @@ #include "api_overflow_buffer.h" #ifdef USE_API #include +#include namespace esphome::api { @@ -61,9 +62,18 @@ bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_ return false; uint16_t buffer_size = total_len - skip; + // nothrow: a failed allocation returns nullptr so the connection is dropped + // cleanly instead of plain new's crash or abort on OOM // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto *entry = new Entry{new uint8_t[buffer_size], buffer_size, 0}; - this->queue_[this->tail_] = entry; + auto *data = new (std::nothrow) uint8_t[buffer_size]; + if (data == nullptr) + return false; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto *entry = new (std::nothrow) Entry{data, buffer_size, 0}; + if (entry == nullptr) { + delete[] data; + return false; + } uint16_t to_skip = skip; uint16_t write_pos = 0; @@ -80,6 +90,8 @@ bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_ } } + // Publish only after the copy completes so a half-built entry is never reachable + this->queue_[this->tail_] = entry; this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE; this->count_++; return true; diff --git a/esphome/components/api/api_overflow_buffer.h b/esphome/components/api/api_overflow_buffer.h index 1227e83126..03a334b281 100644 --- a/esphome/components/api/api_overflow_buffer.h +++ b/esphome/components/api/api_overflow_buffer.h @@ -61,7 +61,7 @@ class APIOverflowBuffer { /// Enqueue unsent IOV data into the backlog. /// Copies iov data starting at byte offset `skip` into a new entry. - /// Returns false if the queue is full (caller should fail the connection). + /// Returns false if the queue is full or allocation fails (caller should fail the connection). bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip); protected: