1 line
6.8 KiB
JSON
1 line
6.8 KiB
JSON
{"schema_version":"1.9.0","id":"GHSA-56cj-wgg3-x943","published":"2026-03-10T18:30:58Z","modified":"2026-03-13T20:26:07.599912Z","aliases":["BIT-envoy-2026-26309","CVE-2026-26309"],"summary":"Envoy affected by off-by-one write in JsonEscaper::escapeString()","details":"### Summary\n\n An off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt\n std::string null-termination, causing undefined behavior and potentially\n leading to crashes or out-of-bounds reads when the resulting string is later\n treated as a C-string.\n\n ### Details\n\n The bug is in the control-character escaping path in source/common/common/\n json_escape_string.h:67.\n\n - The function pre-sizes result to the final length: std::string\n result(input.size() + required_size, '\\\\');\n - For control characters (0x00..0x1f), it emits a JSON escape sequence of\n length 6: \\u00XX.\n - It uses sprintf(&result[position + 1], \"u%04x\", ...), which writes 5 chars +\n a trailing NUL (\\0) starting at result[position + 1].\n - Then it does position += 6; and writes result[position] = '\\\\'; to overwrite\n the NUL.\n - If the control character occurs at the end of the output (e.g., the input\n ends with \\x01), then after position += 6, position == result.size(), so\n result[position] is one past the end (off-by-one), violating std::string\n bounds/contract.\n\n Concretely, the problematic lines are:\n\n - source/common/common/json_escape_string.h:69 (sprintf(...))\n - source/common/common/json_escape_string.h:72 (result[position] = '\\\\';)\n\n Potentially reachable from request-driven paths that escape untrusted data,\n e.g. invalid header reporting:\n\n - source/common/http/header_utility.cc:538 ~ source/common/http/\n header_utility.cc:546 (escapes invalid header key for error text)\n\n Even when this doesn’t immediately crash, it can break the std::string\n requirement that c_str()[size()] == '\\0', which can later trigger UB (e.g., if\n passed to strlen, printf(\"%s\"), or any C API that expects NUL termination).\n \n \n ```cpp\n//clang++ -std=c++20 -O0 -g -fsanitize=address -fno-omit-frame-pointer\n repro_json_escape_asan.cc -o repro_json_escape_asan\n ASAN_OPTIONS=abort_on_error=1 ./repro_json_escape_asan\n#include <cstdint>\n #include <cstdio>\n #include <cstring>\n #include <string>\n #include <string_view>\n\n static uint64_t extraSpace(std::string_view input) {\n uint64_t result = 0;\n for (unsigned char c : input) {\n switch (c) {\n case '\\\"':\n case '\\\\':\n case '\\b':\n case '\\f':\n case '\\n':\n case '\\r':\n case '\\t':\n result += 1;\n break;\n default:\n if (c == 0x00 || (c > 0x00 && c <= 0x1f)) {\n result += 5;\n }\n break;\n }\n }\n return result;\n }\n\n static std::string escapeString(std::string_view input, uint64_t\n required_size) {\n std::string result(input.size() + required_size, '\\\\');\n uint64_t position = 0;\n\n for (unsigned char character : input) {\n switch (character) {\n case '\\\"':\n result[position + 1] = '\\\"';\n position += 2;\n break;\n case '\\\\':\n position += 2;\n break;\n case '\\b':\n result[position + 1] = 'b';\n position += 2;\n break;\n case '\\f':\n result[position + 1] = 'f';\n position += 2;\n break;\n case '\\n':\n result[position + 1] = 'n';\n position += 2;\n break;\n case '\\r':\n result[position + 1] = 'r';\n position += 2;\n break;\n case '\\t':\n result[position + 1] = 't';\n position += 2;\n break;\n default:\n if (character == 0x00 || (character > 0x00 && character <= 0x1f)) {\n std::sprintf(&result[position + 1], \"u%04x\",\n static_cast<int>(character));\n position += 6;\n // Off-by-one when this escape is the last output chunk:\n // position can become result.size(), so result[position] is out of\n bounds.\n result[position] = '\\\\';\n } else {\n result[position++] = static_cast<char>(character);\n }\n break;\n }\n }\n\n return result;\n }\n\n int main() {\n std::string input(4096, 'A');\n input.push_back('\\x01'); // ends with a control char -> triggers the buggy\n path at the end\n\n const uint64_t required = extraSpace(input);\n std::string escaped = escapeString(input, required);\n\n std::printf(\"escaped.size=%zu\\n\", escaped.size());\n unsigned char terminator = static_cast<unsigned char>(escaped.c_str()\n [escaped.size()]);\n std::printf(\"escaped.c_str()[escaped.size()] = 0x%02x\\n\", terminator);\n\n // If NUL termination is corrupted, this can read past the logical end.\n std::printf(\"strlen(escaped.c_str()) = %zu\\n\",\n std::strlen(escaped.c_str()));\n return 0;\n }```","affected":[{"package":{"name":"github.com/envoyproxy/envoy","ecosystem":"Go","purl":"pkg:golang/github.com/envoyproxy/envoy"},"versions":["1.37.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-56cj-wgg3-x943/GHSA-56cj-wgg3-x943.json"}},{"package":{"name":"github.com/envoyproxy/envoy","ecosystem":"Go","purl":"pkg:golang/github.com/envoyproxy/envoy"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.36.0"},{"last_affected":"1.36.4"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-56cj-wgg3-x943/GHSA-56cj-wgg3-x943.json"}},{"package":{"name":"github.com/envoyproxy/envoy","ecosystem":"Go","purl":"pkg:golang/github.com/envoyproxy/envoy"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.35.0"},{"last_affected":"1.35.8"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-56cj-wgg3-x943/GHSA-56cj-wgg3-x943.json"}},{"package":{"name":"github.com/envoyproxy/envoy","ecosystem":"Go","purl":"pkg:golang/github.com/envoyproxy/envoy"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"1.34.12"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-56cj-wgg3-x943/GHSA-56cj-wgg3-x943.json"}}],"references":[{"type":"WEB","url":"https://github.com/envoyproxy/envoy/security/advisories/GHSA-56cj-wgg3-x943"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-26309"},{"type":"PACKAGE","url":"https://github.com/envoyproxy/envoy"}],"database_specific":{"cwe_ids":["CWE-193"],"github_reviewed":true,"github_reviewed_at":"2026-03-10T18:30:58Z","nvd_published_at":"2026-03-10T20:16:35Z","severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"}]} |