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:07Z
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/ 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 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
{
    "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"
}
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
0 Unknown 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"