GHSA-56cj-wgg3-x943

Suggest an improvement
Source
https://github.com/advisories/GHSA-56cj-wgg3-x943
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-56cj-wgg3-x943/GHSA-56cj-wgg3-x943.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-56cj-wgg3-x943
Aliases
Published
2026-03-10T18:30:58Z
Modified
2026-03-13T20:26:07.599912Z
Severity
  • 5.3 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L CVSS Calculator
Summary
Envoy affected by off-by-one write in JsonEscaper::escapeString()
Details

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/ jsonescapestring.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/jsonescapestring.h:69 (sprintf(...))

  • source/common/common/jsonescapestring.h:72 (result[position] = '\';)

    Potentially reachable from request-driven paths that escape untrusted data, e.g. invalid header reporting:

  • source/common/http/headerutility.cc:538 ~ source/common/http/ headerutility.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
    <h1>include <cstdint></h1>
    
    #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 the buggy
    path at the end
    
      const uint64_t required = extraSpace(input);
      std::string escaped = escapeString(input, required);
    
      std::printf("escaped.size=%zu\n", escaped.size());
      unsigned char terminator = static_cast<unsigned char>(escaped.c_str()
    [escaped.size()]);
      std::printf("escaped.c_str()[escaped.size()] = 0x%02x\n", terminator);
    
      // If NUL termination is corrupted, this can read past the logical end.
      std::printf("strlen(escaped.c_str()) = %zu\n",
    std::strlen(escaped.c_str()));
      return 0;
    
    
Database specific
{
    "nvd_published_at": "2026-03-10T20:16:35Z",
    "severity": "MODERATE",
    "cwe_ids": [
        "CWE-193"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-10T18:30:58Z"
}
References

Affected packages

Go
github.com/envoyproxy/envoy

Package

Name
github.com/envoyproxy/envoy
View open source insights on deps.dev
Purl
pkg:golang/github.com/envoyproxy/envoy

Affected ranges

Affected versions

1.*
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"
github.com/envoyproxy/envoy

Package

Name
github.com/envoyproxy/envoy
View open source insights on deps.dev
Purl
pkg:golang/github.com/envoyproxy/envoy

Affected 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"
github.com/envoyproxy/envoy

Package

Name
github.com/envoyproxy/envoy
View open source insights on deps.dev
Purl
pkg:golang/github.com/envoyproxy/envoy

Affected 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"
github.com/envoyproxy/envoy

Package

Name
github.com/envoyproxy/envoy
View open source insights on deps.dev
Purl
pkg:golang/github.com/envoyproxy/envoy

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
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"