From 78b76045ce6fc3ebadb54740b75c23fa54678e2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Dec 2025 04:20:23 +0100 Subject: [PATCH] [api] Fix potential buffer overflow in noise PSK base64 decode (#12395) --- esphome/components/api/api_connection.cpp | 2 +- esphome/core/helpers.cpp | 44 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 09b311c1e..5186e5afd 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1669,7 +1669,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption } else { ESP_LOGW(TAG, "Failed to clear encryption key"); } - } else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { + } else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 77102c8db..732e8b6f8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -480,22 +480,13 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { } size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { - std::vector decoded = base64_decode(encoded_string); - if (decoded.size() > buf_len) { - ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); - decoded.resize(buf_len); - } - memcpy(buf, decoded.data(), decoded.size()); - return decoded.size(); -} - -std::vector base64_decode(const std::string &encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in = 0; + size_t out = 0; uint8_t char_array_4[4], char_array_3[3]; - std::vector ret; + bool truncated = false; // SAFETY: The loop condition checks is_base64() before processing each character. // This ensures base64_find_char() is only called on valid base64 characters, @@ -511,8 +502,13 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++) - ret.push_back(char_array_3[i]); + for (i = 0; i < 3; i++) { + if (out < buf_len) { + buf[out++] = char_array_3[i]; + } else { + truncated = true; + } + } i = 0; } } @@ -528,10 +524,28 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) - ret.push_back(char_array_3[j]); + for (j = 0; j < i - 1; j++) { + if (out < buf_len) { + buf[out++] = char_array_3[j]; + } else { + truncated = true; + } + } } + if (truncated) { + ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); + } + + return out; +} + +std::vector base64_decode(const std::string &encoded_string) { + // Calculate maximum decoded size: every 4 base64 chars = 3 bytes + size_t max_len = ((encoded_string.size() + 3) / 4) * 3; + std::vector ret(max_len); + size_t actual_len = base64_decode(encoded_string, ret.data(), max_len); + ret.resize(actual_len); return ret; }