Compare commits

..

6 Commits

Author SHA1 Message Date
J. Nick Koston
befe5d3bd2 bot review 2026-01-16 13:32:31 -10:00
J. Nick Koston
bdabbdaaea bot review 2026-01-16 13:31:30 -10:00
J. Nick Koston
90989aa7cd bot review 2026-01-16 13:30:27 -10:00
J. Nick Koston
72ebee5267 bot review 2026-01-16 13:28:51 -10:00
J. Nick Koston
648a40de7b [mapping] Use stack buffers for numeric key error logging 2026-01-16 13:23:05 -10:00
J. Nick Koston
52ac9e1861 [remote_base] Replace unsafe sprintf with buf_append_printf; fix buffer overflow (#13257)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-16 16:56:47 -06:00
5 changed files with 39 additions and 54 deletions

View File

@@ -2,6 +2,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include <map>
#include <string>
@@ -43,8 +44,17 @@ template<typename K, typename V> class Mapping {
esph_log_e(TAG, "Key '%p' not found in mapping", key);
} else if constexpr (std::is_same_v<K, std::string>) {
esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str());
} else if constexpr (std::is_integral_v<K>) {
char buf[24]; // enough for 64-bit integer
if constexpr (std::is_unsigned_v<K>) {
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(key));
} else {
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, static_cast<int64_t>(key));
}
esph_log_e(TAG, "Key '%s' not found in mapping", buf);
} else {
esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str());
// All supported key types are handled above - this should never be reached
static_assert(sizeof(K) == 0, "Unsupported key type for Mapping error logging");
}
return {};
}

View File

@@ -85,8 +85,8 @@ optional<AEHAData> AEHAProtocol::decode(RemoteReceiveData src) {
std::string AEHAProtocol::format_data_(const std::vector<uint8_t> &data) {
std::string out;
for (uint8_t byte : data) {
char buf[6];
sprintf(buf, "0x%02X,", byte);
char buf[8]; // "0x%02X," = 5 chars + null + margin
snprintf(buf, sizeof(buf), "0x%02X,", byte);
out += buf;
}
out.pop_back();

View File

@@ -1,4 +1,5 @@
#include "raw_protocol.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -8,36 +9,30 @@ static const char *const TAG = "remote.raw";
bool RawDumper::dump(RemoteReceiveData src) {
char buffer[256];
uint32_t buffer_offset = 0;
buffer_offset += sprintf(buffer, "Received Raw: ");
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0, "Received Raw: ");
for (int32_t i = 0; i < src.size() - 1; i++) {
const int32_t value = src[i];
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
int written;
size_t prev_pos = pos;
if (i + 1 < src.size() - 1) {
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
} else {
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
}
if (written < 0 || written >= int(remaining_length)) {
// write failed, flush...
buffer[buffer_offset] = '\0';
if (pos >= sizeof(buffer) - 1) {
// buffer full, flush and continue
buffer[prev_pos] = '\0';
ESP_LOGI(TAG, "%s", buffer);
buffer_offset = 0;
written = sprintf(buffer, " ");
if (i + 1 < src.size() - 1) {
written += sprintf(buffer + written, "%" PRId32 ", ", value);
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
} else {
written += sprintf(buffer + written, "%" PRId32, value);
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
}
}
buffer_offset += written;
}
if (buffer_offset != 0) {
if (pos != 0) {
ESP_LOGI(TAG, "%s", buffer);
}
return true;

View File

@@ -1,4 +1,5 @@
#include "remote_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
@@ -169,36 +170,31 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
const auto &vec = this->temp_.get_data();
char buffer[256];
uint32_t buffer_offset = 0;
buffer_offset += sprintf(buffer, "Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0,
"Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
for (size_t i = 0; i < vec.size(); i++) {
const int32_t value = vec[i];
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
int written;
size_t prev_pos = pos;
if (i + 1 < vec.size()) {
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
} else {
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
}
if (written < 0 || written >= int(remaining_length)) {
// write failed, flush...
buffer[buffer_offset] = '\0';
if (pos >= sizeof(buffer) - 1) {
// buffer full, flush and continue
buffer[prev_pos] = '\0';
ESP_LOGVV(TAG, "%s", buffer);
buffer_offset = 0;
written = sprintf(buffer, " ");
if (i + 1 < vec.size()) {
written += sprintf(buffer + written, "%" PRId32 ", ", value);
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
} else {
written += sprintf(buffer + written, "%" PRId32, value);
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
}
}
buffer_offset += written;
}
if (buffer_offset != 0) {
if (pos != 0) {
ESP_LOGVV(TAG, "%s", buffer);
}
#endif

View File

@@ -55,7 +55,6 @@ enum MessageType : uint16_t {
COMMAND = 0x0106,
};
// Max string length: 7 ("Unknown"/"Command"). Update print() buffer sizes if adding longer strings.
inline const char *message_type_to_str(MessageType t) {
switch (t) {
case STATUS:
@@ -84,11 +83,7 @@ struct MessageHeader {
}
std::string print() {
// 64 bytes: "MessageHeader: seq " + uint16 + ", len " + uint32 + ", type " + type + safety margin
char buf[64];
buf_append_printf(buf, sizeof(buf), 0, "MessageHeader: seq %d, len %d, type %s", this->seq, this->len,
message_type_to_str(this->type));
return buf;
return str_sprintf("MessageHeader: seq %d, len %d, type %s", this->seq, this->len, message_type_to_str(this->type));
}
void byteswap() {
@@ -136,7 +131,6 @@ inline CoverOperation gate_status_to_cover_operation(GateStatus s) {
return COVER_OPERATION_IDLE;
}
// Max string length: 11 ("Ventilating"). Update print() buffer sizes if adding longer strings.
inline const char *gate_status_to_str(GateStatus s) {
switch (s) {
case PAUSED:
@@ -176,12 +170,7 @@ struct StatusReply {
GateStatus state;
uint8_t trailer = 0x0;
std::string print() {
// 48 bytes: "StatusReply: state " (19) + state (11) + safety margin
char buf[48];
buf_append_printf(buf, sizeof(buf), 0, "StatusReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("StatusReply: state %s", gate_status_to_str(this->state)); }
void byteswap(){};
} __attribute__((packed));
@@ -213,12 +202,7 @@ struct CommandRequestReply {
CommandRequestReply() = default;
CommandRequestReply(GateStatus state) { this->state = state; }
std::string print() {
// 56 bytes: "CommandRequestReply: state " (27) + state (11) + safety margin
char buf[56];
buf_append_printf(buf, sizeof(buf), 0, "CommandRequestReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("CommandRequestReply: state %s", gate_status_to_str(this->state)); }
void byteswap() { this->type = convert_big_endian(this->type); }
} __attribute__((packed));