Compare commits

...

2 Commits

Author SHA1 Message Date
J. Nick Koston
ac8964627b [ci] Block sprintf/vsprintf usage, suggest snprintf alternatives 2026-01-16 14:18:11 -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
4 changed files with 45 additions and 34 deletions

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

@@ -728,6 +728,26 @@ def lint_no_heap_allocating_helpers(fname, match):
)
@lint_re_check(
# Match sprintf/vsprintf but not snprintf/vsnprintf
# [^\w] ensures we don't match the safe variants
r"[^\w](v?sprintf)\s*\(" + CPP_RE_EOL,
include=cpp_include,
)
def lint_no_sprintf(fname, match):
func = match.group(1)
safe_func = func.replace("sprintf", "snprintf")
return (
f"{highlight(func + '()')} is not allowed in ESPHome. It has no buffer size limit "
f"and can cause buffer overflows.\n"
f"Please use one of these alternatives:\n"
f" - {highlight(safe_func + '(buf, sizeof(buf), fmt, ...)')} for general formatting\n"
f" - {highlight('buf_append_printf(buf, sizeof(buf), pos, fmt, ...)')} for "
f"offset-based formatting (also stores format strings in flash on ESP8266)\n"
f"(If strictly necessary, add `// NOLINT` to the end of the line)"
)
@lint_content_find_check(
"ESP_LOG",
include=["*.h", "*.tcc"],