CVE-2026-26309
Envoy has an off-by-one write in JsonEscaper::escapeString()
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
5.3
Envoy has an off-by-one write in JsonEscaper::escapeString()
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
- Record
CVE-2026-26309- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-193
- Updated
- 2026-11-02
EUVDEUVD-2026-10800
5.3
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
- Record
EUVD-2026-10800- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-56cj-wgg3-x943
5.3
Envoy affected by off-by-one write in JsonEscaper::escapeString()
Summary
An off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string.
Details
The bug is in the control-character escaping path in source/common/common/ json_escape_string.h:67.
- The function pre-sizes result to the final length: std::string result(input.size() + required_size, '\');
- For control characters (0x00..0x1f), it emits a JSON escape sequence of length 6: \u00XX.
- It uses sprintf(&result[position + 1], "u%04x", ...), which writes 5 chars + a trailing NUL (\0) starting at result[position + 1].
- Then it does position += 6; and writes result[position] = '\'; to overwrite the NUL.
- If the control character occurs at the end of the output (e.g., the input ends with \x01), then after position += 6, position == result.size(), so result[position] is one past the end (off-by-one), violating std::string bounds/contract.
Concretely, the problematic lines are:
- source/common/common/json_escape_string.h:69 (sprintf(...))
- source/common/common/json_escape_string.h:72 (result[position] = '\';)
Potentially reachable from request-driven paths that escape untrusted data, e.g. invalid header reporting:
- source/common/http/header_utility.cc:538 ~ source/common/http/ header_utility.cc:546 (escapes invalid header key for error text)
Even when this doesn’t immediately crash, it can break the std::string requirement that c_str()[size()] == '\0', which can later trigger UB (e.g., if passed to strlen, printf("%s"), or any C API that expects NUL termination).
//clang++ -std=c++20 -O0 -g -fsanitize=address -fno-omit-frame-pointer
repro_json_escape_asan.cc -o repro_json_escape_asan
ASAN_OPTIONS=abort_on_error=1 ./repro_json_escape_asan
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
static uint64_t extraSpace(std::string_view input) {
uint64_t result = 0;
for (unsigned char c : input) {
switch (c) {
case '\"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
result += 1;
break;
default:
if (c == 0x00 || (c > 0x00 && c <= 0x1f)) {
result += 5;
}
break;
}
}
return result;
}
static std::string escapeString(std::string_view input, uint64_t
required_size) {
std::string result(input.size() + required_size, '\\');
uint64_t position = 0;
for (unsigned char character : input) {
switch (character) {
case '\"':
result[position + 1] = '\"';
position += 2;
break;
case '\\':
position += 2;
break;
case '\b':
result[position + 1] = 'b';
position += 2;
break;
case '\f':
result[position + 1] = 'f';
position += 2;
break;
case '\n':
result[position + 1] = 'n';
position += 2;
break;
case '\r':
result[position + 1] = 'r';
position += 2;
break;
case '\t':
result[position + 1] = 't';
position += 2;
break;
default:
if (character == 0x00 || (character > 0x00 && character <= 0x1f)) {
std::sprintf(&result[position + 1], "u%04x",
static_cast<int>(character));
position += 6;
// Off-by-one when this escape is the last output chunk:
// position can become result.size(), so result[position] is out of
bounds.
result[position] = '\\';
} else {
result[position++] = static_cast<char>(character);
}
break;
}
}
return result;
}
int main() {
std::string input(4096, 'A');
input.push_back('\x01'); // ends with a control char -> triggers…
- Record
GHSA-56cj-wgg3-x943- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-193
- Updated
- 2026-11-02
NVD
5.3
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
- Record
CVE-2026-26309- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-193
- Updated
- 2026-11-02
OSV
5.3
Envoy has an off-by-one write in JsonEscaper::escapeString()
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
- Record
CVE-2026-26309- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-193
- Updated
- 2026-11-02
OSVGHSA-56cj-wgg3-x943
5.3
Envoy affected by off-by-one write in JsonEscaper::escapeString()
Summary
An off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string.
Details
The bug is in the control-character escaping path in source/common/common/ json_escape_string.h:67.
- The function pre-sizes result to the final length: std::string result(input.size() + required_size, '\');
- For control characters (0x00..0x1f), it emits a JSON escape sequence of length 6: \u00XX.
- It uses sprintf(&result[position + 1], "u%04x", ...), which writes 5 chars + a trailing NUL (\0) starting at result[position + 1].
- Then it does position += 6; and writes result[position] = '\'; to overwrite the NUL.
- If the control character occurs at the end of the output (e.g., the input ends with \x01), then after position += 6, position == result.size(), so result[position] is one past the end (off-by-one), violating std::string bounds/contract.
Concretely, the problematic lines are:
- source/common/common/json_escape_string.h:69 (sprintf(...))
- source/common/common/json_escape_string.h:72 (result[position] = '\';)
Potentially reachable from request-driven paths that escape untrusted data, e.g. invalid header reporting:
- source/common/http/header_utility.cc:538 ~ source/common/http/ header_utility.cc:546 (escapes invalid header key for error text)
Even when this doesn’t immediately crash, it can break the std::string requirement that c_str()[size()] == '\0', which can later trigger UB (e.g., if passed to strlen, printf("%s"), or any C API that expects NUL termination).
//clang++ -std=c++20 -O0 -g -fsanitize=address -fno-omit-frame-pointer
repro_json_escape_asan.cc -o repro_json_escape_asan
ASAN_OPTIONS=abort_on_error=1 ./repro_json_escape_asan
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
static uint64_t extraSpace(std::string_view input) {
uint64_t result = 0;
for (unsigned char c : input) {
switch (c) {
case '\"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
result += 1;
break;
default:
if (c == 0x00 || (c > 0x00 && c <= 0x1f)) {
result += 5;
}
break;
}
}
return result;
}
static std::string escapeString(std::string_view input, uint64_t
required_size) {
std::string result(input.size() + required_size, '\\');
uint64_t position = 0;
for (unsigned char character : input) {
switch (character) {
case '\"':
result[position + 1] = '\"';
position += 2;
break;
case '\\':
position += 2;
break;
case '\b':
result[position + 1] = 'b';
position += 2;
break;
case '\f':
result[position + 1] = 'f';
position += 2;
break;
case '\n':
result[position + 1] = 'n';
position += 2;
break;
case '\r':
result[position + 1] = 'r';
position += 2;
break;
case '\t':
result[position + 1] = 't';
position += 2;
break;
default:
if (character == 0x00 || (character > 0x00 && character <= 0x1f)) {
std::sprintf(&result[position + 1], "u%04x",
static_cast<int>(character));
position += 6;
// Off-by-one when this escape is the last output chunk:
// position can become result.size(), so result[position] is out of
bounds.
result[position] = '\\';
} else {
result[position++] = static_cast<char>(character);
}
break;
}
}
return result;
}
int main() {
std::string input(4096, 'A');
input.push_back('\x01'); // ends with a control char -> triggers…
- Record
GHSA-56cj-wgg3-x943- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-193
- Updated
- 2026-11-02
OSVBIT-envoy-2026-26309 No score
Envoy has an off-by-one write in JsonEscaper::escapeString()
Envoy is a high-performance edge/middle/service proxy. Prior to 1.37.1, 1.36.5, 1.35.8, and 1.34.13, an off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. This vulnerability is fixed in 1.37.1, 1.36.5, 1.35.8, and 1.34.13.
- Record
BIT-envoy-2026-26309- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/envoyproxy/envoyGo |
1.37.0 to 1.37.0 | None known |
github.com/envoyproxy/envoyGo |
1.36.0 to 1.36.4 | None known |
github.com/envoyproxy/envoyGo |
1.35.0 to 1.35.8 | None known |
github.com/envoyproxy/envoyGo |
through 1.34.12 | None known |
envoyBitnami |
before 1.34.13, 1.35.0 up to 1.35.9, 1.36.0 up to 1.36.5, 1.37.0 up to 1.37.1 | 1.34.131.35.91.36.51.37.1 |
github.com/envoyproxy/envoyGo |
Listed versions 1.37.0 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-26309 is a medium vulnerability (CVSS 5.3) in Envoyproxy Envoy, published March 10, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.41%, the 36th percentile. Fixed in envoy 1.34.13.
References
Left out here: the rest of 2 source descriptions. The JSON record has them.