From 4464e464b6a7a23858fb199f62c2ab31997c2331 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 10:48:52 -1000 Subject: [PATCH 1/2] safer --- esphome/core/helpers.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 18cef6e0dc..64d313ab7b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -392,8 +392,12 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { size_t value_accuracy_to_buf(std::span buf, float value, int8_t accuracy_decimals) { normalize_accuracy_decimals(value, accuracy_decimals); + // snprintf returns chars that would be written (excluding null), or negative on error int len = snprintf(buf.data(), buf.size(), "%.*f", accuracy_decimals, value); - return len > 0 ? std::min(static_cast(len), buf.size() - 1) : 0; + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); } size_t value_accuracy_with_uom_to_buf(std::span buf, float value, @@ -402,8 +406,12 @@ size_t value_accuracy_with_uom_to_buf(std::span bu return value_accuracy_to_buf(buf, value, accuracy_decimals); } normalize_accuracy_decimals(value, accuracy_decimals); + // snprintf returns chars that would be written (excluding null), or negative on error int len = snprintf(buf.data(), buf.size(), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str()); - return len > 0 ? std::min(static_cast(len), buf.size() - 1) : 0; + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); } int8_t step_to_accuracy_decimals(float step) { From 04eb64f361e11da90af25a429d51c7ac12b351eb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Dec 2025 10:49:19 -1000 Subject: [PATCH 2/2] safer --- esphome/components/web_server/web_server.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d0a00d7598..1a1f1c0269 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -434,7 +434,10 @@ static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const c root[ESPHOME_F("state")] = state; } -// Macros for stack-based value formatting (avoid heap allocation) +// Macros for stack-based value formatting (avoid heap allocation). +// Usage: Declare VALUE_BUF once per scope, then use VALUE_OR_NA/VALUE_UOM_OR_NA. +// Safe because ArduinoJson copies the string immediately on assignment. +// Note: Do NOT use multiple macros in the same expression - use separate statements. #define VALUE_BUF char _vbuf_[VALUE_ACCURACY_MAX_LEN] #define VALUE_OR_NA(value, decimals) \ (std::isnan(value) ? "NA" : (value_accuracy_to_buf(_vbuf_, value, decimals), _vbuf_))