CVE-2024-32475 (C++, published 2024-04-18T15:15Z)
Description: value: Envoy is a cloud-native, open source edge and service proxy. When an upstream TLS cluster is used with `auto_sni` enabled, a request containing a `host`/`:authority` header longer than 255 characters triggers an abnormal termination of Envoy process. Envoy does not gracefully handle an error when setting SNI for outbound TLS connection. The error can occur when Envoy attempts to use the `host`/`:authority` header value longer than 255 characters as SNI for outbound TLS connection. SNI length is limited to 255 characters per the standard. Envoy always expects this operation to succeed and abnormally aborts the process when it fails. This vulnerability is fixed in 1.30.1, 1.29.4, 1.28.3, and 1.27.5.

Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/envoyproxy/envoy
Files changed:
changelogs/current.yaml
source/common/tls/context_impl.cc
source/common/tls/context_impl.h
source/common/tls/ssl_socket.cc
source/common/tls/ssl_socket.h
test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc
Commit message: tls: fix RELEASE_ASSERT when using `auto_sni` (#33637)

* tls: fix RELEASE_ASSERT when using `auto_sni`

If the `:authority` was longer than 255 characters, Envoy would
RELEASE_ASSERT when creating an upstream TLS connection when
`auto_sni` (https://www.envoyproxy.io/docs/envoy/v1.30.0/api-v3/config/core/v3/protocol.proto.html#config-core-v3-upstreamhttpprotocoloptions)
was used.

Signed-off-by: Greg Greenway <ggreenway@apple.com>

Vulnerable code:
bssl::UniquePtr<SSL>
bssl::UniquePtr<SSL>
  bssl::UniquePtr<SSL> ssl_con(ContextImpl::newSsl(options));
    RELEASE_ASSERT(rc, Utility::getLastCryptoError().value_or(""));
  virtual bssl::UniquePtr<SSL> newSsl(const Network::TransportSocketOptionsConstSharedPtr& options);
  bssl::UniquePtr<SSL>
// This SslSocket will be used when SSL secret is not fetched from SDS server.
class NotReadySslSocket : public Network::TransportSocket {
  absl::string_view failureReason() const override { return NotReadyReason; }
SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state,
                     const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
                     Ssl::HandshakerFactoryCb handshaker_factory_cb)
      ctx_(std::dynamic_pointer_cast<ContextImpl>(ctx)),
      info_(std::dynamic_pointer_cast<SslHandshakerImpl>(handshaker_factory_cb(
          ctx_->newSsl(transport_socket_options_), ctx_->sslExtendedSocketInfoIndex(), this))) {
    return std::make_unique<SslSocket>(std::move(ssl_ctx), InitialState::Client,
                                       transport_socket_options, config_->createHandshaker());
    return std::make_unique<SslSocket>(std::move(ssl_ctx), InitialState::Server, nullptr,
                                       config_->createHandshaker());
  SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state,
            const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
            Ssl::HandshakerFactoryCb handshaker_factory_cb);

Fixed code:
- area: tls
  change: |
    Fix a RELEASE_ASSERT when using :ref:`auto_sni <envoy_v3_api_field_config.core.v3.UpstreamHttpProtocolOptions.auto_sni>`
    if the downstream request ``:authority`` was longer than 255 characters.

absl::StatusOr<bssl::UniquePtr<SSL>>
absl::StatusOr<bssl::UniquePtr<SSL>>
  absl::StatusOr<bssl::UniquePtr<SSL>> ssl_con_or_status(ContextImpl::newSsl(options));
  if (!ssl_con_or_status.ok()) {
    return ssl_con_or_status;
  }

  bssl::UniquePtr<SSL> ssl_con = std::move(ssl_con_or_status.value());
    if (rc != 1) {
      return absl::InvalidArgumentError(
          absl::StrCat("Failed to create upstream TLS due to failure setting SNI: ",
                       Utility::getLastCryptoError().value_or("unknown")));
    }
  virtual absl::StatusOr<bssl::UniquePtr<SSL>>
  newSsl(const Network::TransportSocketOptionsConstSharedPtr& options);
  absl::StatusOr<bssl::UniquePtr<SSL>>
class InvalidSslSocket : public Network::TransportSocket {
// This SslSocket will be used when SSL secret is not fetched from SDS server.
class NotReadySslSocket : public InvalidSslSocket {
public:
  // Network::TransportSocket
  absl::string_view failureReason() const override { return NotReadyReason; }
};

class ErrorSslSocket : public InvalidSslSocket {
public:
  ErrorSslSocket(absl::string_view error) : error_(error) {}

  // Network::TransportSocket
  absl::string_view failureReason() const override { return error_; }

private:
  std::string error_;
};

absl::StatusOr<std::unique_ptr<SslSocket>>
SslSocket::create(Envoy::Ssl::ContextSharedPtr ctx, InitialState state,
                  const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
                  Ssl::HandshakerFactoryCb handshaker_factory_cb) {
  std::unique_ptr<SslSocket> socket(new SslSocket(ctx, transport_socket_options));
  auto status = socket->initialize(state, handshaker_factory_cb);
  if (status.ok()) {
    return socket;
  } else {
    return status;
  }
}

SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx,
                     const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options)
      ctx_(std::dynamic_pointer_cast<ContextImpl>(ctx)) {}

absl::Status SslSocket::initialize(InitialState state,
                                   Ssl::HandshakerFactoryCb handshaker_factory_cb) {
  auto status_or_ssl = ctx_->newSsl(transport_socket_options_);
  if (!status_or_ssl.ok()) {
    return status_or_ssl.status();
  }

  info_ = std::dynamic_pointer_cast<SslHandshakerImpl>(handshaker_factory_cb(
      std::move(status_or_ssl.value()), ctx_->sslExtendedSocketInfoIndex(), this));


  return absl::OkStatus();
    auto status_or_socket =
        SslSocket::create(std::move(ssl_ctx), InitialState::Client, transport_socket_options,
                          config_->createHandshaker());
    if (status_or_socket.ok()) {
      return std::move(status_or_socket.value());
    }
    return std::make_unique<ErrorSslSocket>(status_or_socket.status().message());
    auto status_or_socket = SslSocket::create(std::move(ssl_ctx), InitialState::Server, nullptr,
                                              config_->createHandshaker());
    if (status_or_socket.ok()) {
      return std::move(status_or_socket.value());
    }
    return std::make_unique<ErrorSslSocket>(status_or_socket.status().message());
  static absl::StatusOr<std::unique_ptr<SslSocket>>
  create(Envoy::Ssl::ContextSharedPtr ctx, InitialState state,
         const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
         Ssl::HandshakerFactoryCb handshaker_factory_cb);
  SslSocket(Envoy::Ssl::ContextSharedPtr ctx,
            const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options);
  absl::Status initialize(InitialState state, Ssl::HandshakerFactoryCb handshaker_factory_cb);

TEST_P(ProxyFilterIntegrationTest, UpstreamTlsWithTooLongSni) {
  upstream_tls_ = true;
  initializeWithArgs(1024, 1024, "x-host");
  std::string too_long_sni(300, 'a');
  ASSERT_EQ(too_long_sni.size(), 300); // Validate that the expected constructor was run.
  codec_client_ = makeHttpConnection(lookupPort("http"));
  const Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"},
                                                       {":path", "/test/long/url"},
                                                       {":scheme", "http"},
                                                       {":authority", "localhost"},
                                                       {"x-host", too_long_sni}};

  auto response = codec_client_->makeHeaderOnlyRequest(request_headers);
  ASSERT_TRUE(response->waitForEndStream());
  EXPECT_EQ("503", response->headers().getStatusValue());
  // TODO(ggreenway): validate (in access logs probably) that failure reason is set appropriately.
}