CVE-2024-23327 (C++, published 2024-02-09T23:15Z)
Description: value: Envoy is a high-performance edge/middle/service proxy. When PPv2 is enabled both on a listener and subsequent cluster, the Envoy instance will segfault when attempting to craft the upstream PPv2 header. This occurs when the downstream request has a command type of LOCAL and does not have the protocol block. This issue has been addressed in releases 1.29.1, 1.28.1, 1.27.3, and 1.26.7. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-476 \u2014 NULL Pointer Dereference
The product dereferences a pointer that it expects to be valid but is NULL.
Repository: https://github.com/envoyproxy/envoy
Files changed:
changelogs/current.yaml
source/extensions/common/proxy_protocol/proxy_protocol_header.cc
source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc
test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc
test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc
test/per_file_coverage.sh
tools/spelling/spelling_dictionary.txt
Commit message: Fix crash from AWS NLB healthchecks when proxy protocol is enabled

Fix: [CVE-2024-23327](https://github.com/envoyproxy/envoy/security/advisories/GHSA-4h5x-x9vh-m29j)

Signed-off-by: Jacob Neil Taylor <me@jacobtaylor.id.au>
Signed-off-by: Greg Greenway <ggreenway@apple.com>
Signed-off-by: Ryan Northey <ryan@synca.io>


Vulnerable code:
    if (!proxy_protocol_header_.value().local_command_) {
      auto buf = reinterpret_cast<const uint8_t*>(buffer.rawSlice().mem_);
          "Parsed proxy protocol header, length: {}, buffer: {}, TLV length: {}, TLV buffer: {}",

    cb_->filterState().setData(
        Network::ProxyProtocolFilterState::key(),
        std::make_unique<Network::ProxyProtocolFilterState>(Network::ProxyProtocolData{
            proxy_protocol_header_.value().remote_address_,
            proxy_protocol_header_.value().local_address_, parsed_tlvs_}),
        StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection);

Fixed code:
- area: proxy protocol
  change: |
    Fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives
    a PROXY protocol header with address type LOCAL (typically used for health checks).
  if (proxy_proto_data.src_addr_ == nullptr || proxy_proto_data.src_addr_->ip() == nullptr) {
    IS_ENVOY_BUG("Missing or incorrect source IP in proxy_proto_data_");
    return false;
  }
  if (proxy_proto_data.dst_addr_ == nullptr || proxy_proto_data.dst_addr_->ip() == nullptr) {
    IS_ENVOY_BUG("Missing or incorrect dest IP in proxy_proto_data_");
    return false;
  }

    auto buf = reinterpret_cast<const uint8_t*>(buffer.rawSlice().mem_);
    if (proxy_protocol_header_.value().local_command_) {
      ENVOY_LOG(trace, "Parsed proxy protocol header, cmd: LOCAL, length: {}, buffer: {}",
                proxy_protocol_header_.value().wholeHeaderLength(),
                Envoy::Hex::encode(buf, proxy_protocol_header_.value().wholeHeaderLength()));

      cb_->filterState().setData(
          Network::ProxyProtocolFilterState::key(),
          std::make_unique<Network::ProxyProtocolFilterState>(Network::ProxyProtocolData{
              socket.connectionInfoProvider().remoteAddress(),
              socket.connectionInfoProvider().localAddress(), parsed_tlvs_}),
          StreamInfo::FilterState::StateType::Mutable,
          StreamInfo::FilterState::LifeSpan::Connection);
    } else {
          "Parsed proxy protocol header, cmd: PROXY, length: {}, buffer: {}, TLV length: {}, TLV "
          "buffer: {}",
      cb_->filterState().setData(
          Network::ProxyProtocolFilterState::key(),
          std::make_unique<Network::ProxyProtocolFilterState>(Network::ProxyProtocolData{
              proxy_protocol_header_.value().remote_address_,
              proxy_protocol_header_.value().local_address_, parsed_tlvs_}),
          StreamInfo::FilterState::StateType::Mutable,
          StreamInfo::FilterState::LifeSpan::Connection);
TEST_P(ProxyProtocolTest, V2LocalConnectionFilterState) {
  // A well-formed local proxy protocol v2 header sampled from an AWS NLB healthcheck request,
  // no address, 1 TLV is present.
  constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54,
                                0x0a, 0x20, 0x00, 0x00, 0x07, 0x00, 0x00, 0x04, 0x0a, 0x0b, 0x0c,
                                0x0d, 'm',  'o',  'r',  'e',  'd',  'a',  't',  'a'};
  envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol proto_config;
  connect(true, &proto_config);
  write(buffer, sizeof(buffer));
  expectData("moredata");

  auto& filter_state = server_connection_->streamInfo().filterState();
  const auto& proxy_proto_data = filter_state
                                     ->getDataReadOnly<Network::ProxyProtocolFilterState>(
                                         Network::ProxyProtocolFilterState::key())
                                     ->value();

  if (server_connection_->connectionInfoProvider().remoteAddress()->ip()->version() ==
      Envoy::Network::Address::IpVersion::v6) {
    EXPECT_EQ(proxy_proto_data.dst_addr_->ip()->addressAsString(), "::1");
  } else if (server_connection_->connectionInfoProvider().remoteAddress()->ip()->version() ==
             Envoy::Network::Address::IpVersion::v4) {
    EXPECT_EQ(proxy_proto_data.dst_addr_->ip()->addressAsString(), "127.0.0.1");
  }
  EXPECT_FALSE(server_connection_->connectionInfoProvider().localAddressRestored());
  disconnect();
}

TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) {
  setup(true, {}, {});
  initialize();

  IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));

  // A well-formed proxy protocol v2 header sampled from an AWS NLB healthcheck request, with
  // command type 'LOCAL' (0 for the low 4 bits of the 13th octet).
  constexpr uint8_t v2_protocol[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51,
                                     0x55, 0x49, 0x54, 0x0a, 0x20, 0x00, 0x00, 0x00,
                                     'm',  'o',  'r',  'e',  'd',  'a',  't',  'a'};
  Buffer::OwnedImpl buffer(v2_protocol, sizeof(v2_protocol));
  ASSERT_TRUE(tcp_client->write(buffer.toString()));
  ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_));
  std::string header_start;
  // - signature
  // - version and command type, address family and protocol, length of addresses
  // - src address, dest address
  if (GetParam() == Envoy::Network::Address::IpVersion::v4) {
    const char data[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a,
                         0x21, 0x11, 0x00, 0x0c, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01};
    header_start = std::string(data, sizeof(data));
  } else {
    const char data[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a,
                         0x21, 0x21, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
    header_start = std::string(data, sizeof(data));
  }

  constexpr absl::string_view more_data("moredata");
  const size_t offset = header_start.length() + (2 * sizeof(uint16_t)); // Skip over the ports
  std::string observed_data;
  ASSERT_TRUE(fake_upstream_connection_->waitForData(offset + more_data.length(), &observed_data));
  EXPECT_THAT(observed_data, testing::StartsWith(header_start));
  EXPECT_EQ(more_data, absl::string_view(&observed_data[offset], more_data.length()));

  tcp_client->close();
  ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect());
}

"source/extensions/common/proxy_protocol:93.8" # Adjusted for security patch
NLB