Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,12 @@ common:debug --config=debug-sandbox
common:debug --config=debug-coverage
common:debug --config=debug-tests

#############################################################################
# compat: Compatibility with main branch repo settings
#############################################################################
common:bes --config=bes-envoy-engflow
common:rbe --config=remote-envoy-engflow

try-import %workspace%/repo.bazelrc
try-import %workspace%/clang.bazelrc
try-import %workspace%/user.bazelrc
Expand Down
2 changes: 1 addition & 1 deletion .bazelversion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.6.2
7.7.1
5 changes: 1 addition & 4 deletions .github/workflows/request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ concurrency:
jobs:
request:
permissions:
actions: read
actions: write
contents: read
packages: read
# required to fetch merge commit
Expand All @@ -36,9 +36,6 @@ jobs:
app-id: ${{ secrets.ENVOY_CI_APP_ID }}
lock-app-key: ${{ secrets.ENVOY_CI_MUTEX_APP_KEY }}
lock-app-id: ${{ secrets.ENVOY_CI_MUTEX_APP_ID }}
gcs-cache-key: ${{ secrets.GCS_CACHE_WRITE_KEY }}
with:
gcs-cache-bucket: ${{ vars.ENVOY_CACHE_BUCKET }}
# For branches this can be pinned to a specific version if required
# NB: `uses` cannot be dynamic so it _must_ be hardcoded anywhere it is read
uses: envoyproxy/envoy/.github/workflows/_request.yml@main
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.34.11-dev
1.34.12-dev
17 changes: 17 additions & 0 deletions changelogs/1.33.13.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
date: December 3, 2025

behavior_changes:
- area: http
change: |
Added runtime flag ``envoy.reloadable_features.reject_early_connect_data`` to reject ``CONNECT`` requests
that receive data before Envoy sent a ``200`` response to the client. While this is not a strictly compliant behavior
it is very common as a latency reducing measure. As such the option is disabled by default.
bug_fixes:
- area: tls
change: |
Fixed an issue where SANs of type ``OTHERNAME`` in a TLS cert were truncated if there was
an embedded null octet, leading to incorrect SAN validation.
- area: http
change: |
Fixed a remote ``jwt_auth`` token fetch crash with two or more auth headers when ``allow_missing_or_failed`` is set.
28 changes: 28 additions & 0 deletions changelogs/1.34.11.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
date: December 3, 2025

behavior_changes:
- area: dynamic modules
change: |
The dynamic module ABI has been updated to support streaming body manipulation. This change also
fixed potential incorrect behavior when access or modify the request or response body. See
https://github.com/envoyproxy/envoy/issues/40918 for more details.
- area: http
change: |
Added runtime flag ``envoy.reloadable_features.reject_early_connect_data`` to reject ``CONNECT`` requests
that receive data before Envoy sent a ``200`` response to the client. While this is not a strictly compliant behavior
it is very common as a latency reducing measure. As such the option is disabled by default.

bug_fixes:
- area: tcp_proxy
change: |
Fixed a connection leak in the TCP proxy when the ``receive_before_connect`` feature is enabled and the
downstream connection closes before the upstream connection is established.

deprecated:
- area: tls
change: |
Fixed an issue where SANs of type ``OTHERNAME`` in a TLS cert were truncated if there was
an embedded null octet, leading to incorrect SAN validation.
- area: http
change: |
Fixed a remote ``jwt_auth`` token fetch crash with two or more auth headers when ``allow_missing_or_failed`` is set.
5 changes: 0 additions & 5 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ date: Pending

behavior_changes:
# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required*
- area: dynamic modules
change: |
The dynamic module ABI has been updated to support streaming body manipulation. This change also
fixed potential incorrect behavior when access or modify the request or response body. See
https://github.com/envoyproxy/envoy/issues/40918 for more details.

minor_behavior_changes:
# *Changes that may cause incompatibilities for some users, but should not for most*
Expand Down
4 changes: 2 additions & 2 deletions distribution/docker/Dockerfile-envoy
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG BUILD_OS=ubuntu
ARG BUILD_TAG=22.04
ARG BUILD_SHA=09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3
ARG BUILD_SHA=104ae83764a5119017b8e8d6218fa0832b09df65aae7d5a6de29a85d813da2fb
ARG ENVOY_VRP_BASE_IMAGE=envoy-base


Expand Down Expand Up @@ -29,7 +29,7 @@ RUN --mount=type=tmpfs,target=/var/cache/apt \
--mount=type=tmpfs,target=/var/lib/apt/lists \
apt-get -qq update \
&& apt-get -qq upgrade -y \
&& apt-get -qq install --no-install-recommends -y ca-certificates \
&& apt-get -qq install --no-install-recommends -y ca-certificates tzdata \
&& apt-get -qq autoremove -y


Expand Down
Binary file modified docs/inventories/v1.33/objects.inv
Binary file not shown.
Binary file modified docs/inventories/v1.34/objects.inv
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Below are the list of reasons the HttpConnectionManager or Router filter may sen
downstream_remote_disconnect, The client disconnected unexpectedly.
duration_timeout, The max connection duration was exceeded.
direct_response, A direct response was generated by the router filter.
early_connect_data, Data was received for a CONNECT request before 200 response headers were sent.
filter_added_invalid_request_data, A filter added request data at the wrong stage in the filter chain.
filter_added_invalid_response_data, A filter added response data at the wrong stage in the filter chain.
filter_chain_not_found, The request was rejected due to no matching filter chain.
Expand Down
4 changes: 2 additions & 2 deletions docs/versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@
"1.30": 1.30.11
"1.31": 1.31.10
"1.32": 1.32.13
"1.33": 1.33.12
"1.34": 1.34.9
"1.33": 1.33.13
"1.34": 1.34.10
2 changes: 2 additions & 0 deletions envoy/stream_info/stream_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ struct ResponseCodeDetailValues {
const std::string FilterAddedInvalidRequestData = "filter_added_invalid_request_data";
// A filter called addDecodedData at the wrong point in the filter chain.
const std::string FilterAddedInvalidResponseData = "filter_added_invalid_response_data";
// Data was received for a CONNECT request before 200 response headers were sent.
const std::string EarlyConnectData = "early_connect_data";
// Changes or additions to details should be reflected in
// docs/root/configuration/http/http_conn_man/response_code_details.rst
};
Expand Down
12 changes: 12 additions & 0 deletions source/common/router/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,19 @@ void Filter::sendNoHealthyUpstreamResponse(absl::optional<std::string> optional_
absl::nullopt, details);
}

bool Filter::isEarlyConnectData() {
return downstream_headers_ != nullptr && Http::HeaderUtility::isConnect(*downstream_headers_) &&
!downstream_response_started_ &&
Runtime::runtimeFeatureEnabled("envoy.reloadable_features.reject_early_connect_data");
}

Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) {
ENVOY_STREAM_LOG(debug, "router decoding data: {}", *callbacks_, data.length());
if (data.length() > 0 && isEarlyConnectData()) {
callbacks_->sendLocalReply(Http::Code::BadRequest, "", nullptr, absl::nullopt,
StreamInfo::ResponseCodeDetails::get().EarlyConnectData);
return Http::FilterDataStatus::StopIterationNoBuffer;
}
// upstream_requests_.size() cannot be > 1 because that only happens when a per
// try timeout occurs with hedge_on_per_try_timeout enabled but the per
// try timeout timer is not started until onRequestComplete(). It could be zero
Expand Down
1 change: 1 addition & 0 deletions source/common/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ class Filter : Logger::Loggable<Logger::Id::router>,
// Process Orca Load Report if necessary (e.g. cluster has lrsReportMetricNames).
void maybeProcessOrcaLoadReport(const Envoy::Http::HeaderMap& headers_or_trailers,
UpstreamRequest& upstream_request);
bool isEarlyConnectData();

RetryStatePtr retry_state_;
const FilterConfigSharedPtr config_;
Expand Down
4 changes: 4 additions & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_disable_client_early_data);

FALSE_RUNTIME_GUARD(envoy_reloadable_features_ext_proc_graceful_grpc_close);

// TODO(yavlasov): Enabling by default will be hugely disruptive to existing traffic.
// Replace with a config option (default off) post CVE release.
FALSE_RUNTIME_GUARD(envoy_reloadable_features_reject_early_connect_data);

// Block of non-boolean flags. Use of int flags is deprecated. Do not add more.
ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT
ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT
Expand Down
8 changes: 4 additions & 4 deletions source/common/tcp_proxy/tcp_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1002,9 +1002,9 @@ void Filter::onConnectMaxAttempts() {
void Filter::onUpstreamConnection() {
connecting_ = false;

// If we have received any data before upstream connection is established, send it to
// the upstream connection.
if (early_data_buffer_.length() > 0) {
// If we have received any data before upstream connection is established, or if the downstream
// has indicated end of stream, send the data and/or end_stream to the upstream connection.
if (early_data_buffer_.length() > 0 || early_data_end_stream_) {
// Early data should only happen when receive_before_connect is enabled.
ASSERT(receive_before_connect_);

Expand All @@ -1017,7 +1017,7 @@ void Filter::onUpstreamConnection() {
// Re-enable downstream reads now that the early data buffer is flushed.
read_callbacks_->connection().readDisable(false);
} else if (!receive_before_connect_) {
// Re-enable downstream reads now that the upstream connection is established
// Re-enable downstream reads now that the upstream connection is established.
read_callbacks_->connection().readDisable(false);
}

Expand Down
12 changes: 7 additions & 5 deletions source/common/tls/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -336,22 +336,24 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) {
break;
}
case V_ASN1_BMPSTRING: {
// `ASN1_BMPSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8.
// `ASN1_BMPSTRING` is encoded using `UTF-16`, which needs conversion to UTF-8.
unsigned char* tmp = nullptr;
if (ASN1_STRING_to_UTF8(&tmp, value->value.bmpstring) < 0) {
int length = ASN1_STRING_to_UTF8(&tmp, value->value.bmpstring);
if (length < 0) {
break;
}
san.assign(reinterpret_cast<const char*>(tmp));
san.assign(reinterpret_cast<const char*>(tmp), length);
OPENSSL_free(tmp);
break;
}
case V_ASN1_UNIVERSALSTRING: {
// `ASN1_UNIVERSALSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8.
unsigned char* tmp = nullptr;
if (ASN1_STRING_to_UTF8(&tmp, value->value.universalstring) < 0) {
int length = ASN1_STRING_to_UTF8(&tmp, value->value.universalstring);
if (length < 0) {
break;
}
san.assign(reinterpret_cast<const char*>(tmp));
san.assign(reinterpret_cast<const char*>(tmp), length);
OPENSSL_free(tmp);
break;
}
Expand Down
7 changes: 5 additions & 2 deletions source/extensions/filters/http/common/jwks_fetcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class JwksFetcherImpl : public JwksFetcher,
request_->cancel();
ENVOY_LOG(debug, "fetch pubkey [uri = {}]: canceled", remote_jwks_.http_uri().uri());
}
complete_ = true;
reset();
}

Expand Down Expand Up @@ -129,8 +130,10 @@ class JwksFetcherImpl : public JwksFetcher,
Http::AsyncClient::Request* request_{};

void reset() {
request_ = nullptr;
receiver_ = nullptr;
if (complete_) {
request_ = nullptr;
receiver_ = nullptr;
}
}
};
} // namespace
Expand Down
6 changes: 6 additions & 0 deletions source/extensions/filters/http/jwt_authn/authenticator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ void AuthenticatorImpl::startVerify() {
if (jwks_data_->getJwtProvider().has_remote_jwks()) {
if (!fetcher_) {
fetcher_ = create_jwks_fetcher_cb_(cm_, jwks_data_->getJwtProvider().remote_jwks());
} else {
// Cancel the previous fetch to reset if it is pending or not completed.
// At most one outstanding request may be in-flight, and it is possible that
// a new call is from the callback itself, which in-turn will reset the
// fetcher afterwards.
fetcher_->cancel();
}
fetcher_->fetch(*parent_span_, *this);
return;
Expand Down
37 changes: 37 additions & 0 deletions test/common/tcp_proxy/tcp_proxy_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,43 @@ TEST_P(TcpProxyTest, ReceiveBeforeConnectEarlyDataWithEndStream) {
upstream_callbacks_->onUpstreamData(response, false);
}

// Test that when downstream closes without sending any data before upstream connection is
// established, the end_stream signal is properly propagated to upstream.
// This prevents upstream connection leaks.
TEST_P(TcpProxyTest, ReceiveBeforeConnectDownstreamClosesWithoutData) {
setup(/*connections=*/1, /*set_redirect_records=*/false, /*receive_before_connect=*/true);

// Downstream closes without sending any data.
Buffer::OwnedImpl empty_buffer;
EXPECT_CALL(*upstream_connections_.at(0), write(_, _)).Times(0);
filter_->onData(empty_buffer, /*end_stream=*/true);

// When upstream connection is established, the end_stream signal should be sent even though
// the buffer is empty. This ensures the upstream connection is properly closed.
EXPECT_CALL(*upstream_connections_.at(0), write(BufferStringEqual(""), /*end_stream*/ true));
raiseEventUpstreamConnected(/*conn_index=*/0);
}

// Test that when downstream sends empty buffer with end_stream before upstream is connected,
// the end_stream is properly handled.
TEST_P(TcpProxyTest, ReceiveBeforeConnectEmptyBufferWithEndStream) {
setup(/*connections=*/1, /*set_redirect_records=*/false, /*receive_before_connect=*/true);

// Downstream sends empty data with end_stream set.
Buffer::OwnedImpl empty_buffer;
EXPECT_CALL(*upstream_connections_.at(0), write(_, _)).Times(0);
filter_->onData(empty_buffer, /*end_stream=*/true);

// When upstream connection is established, end_stream should be propagated.
EXPECT_CALL(*upstream_connections_.at(0), write(BufferStringEqual(""), /*end_stream*/ true));
raiseEventUpstreamConnected(/*conn_index=*/0);

// Upstream can still send data back.
Buffer::OwnedImpl response("response data");
EXPECT_CALL(filter_callbacks_.connection_, write(BufferEqual(&response), _));
upstream_callbacks_->onUpstreamData(response, false);
}

TEST_P(TcpProxyTest, ReceiveBeforeConnectNoEarlyData) {
setup(1, /*set_redirect_records=*/false, /*receive_before_connect=*/true);
raiseEventUpstreamConnected(/*conn_index=*/0, /*expect_read_enable=*/false);
Expand Down
46 changes: 46 additions & 0 deletions test/common/tls/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,52 @@ TEST(UtilityTest, TestDnsNameMatching) {
EXPECT_FALSE(Utility::dnsNameMatch("lyft.com", ""));
}

TEST(UtilityTest, TestOtherNameUniversalWithEmbeddedNull) {
// Universal strings are utf-32.
uint32_t utf32_data[] = {
htonl('t'), htonl('e'), htonl('s'), htonl('t'), 0 /* embedded null */, htonl('s'), htonl('t'),
htonl('r'), htonl('i'), htonl('n'), htonl('g'),
};
ASN1_STRING* asn1_str = ASN1_UNIVERSALSTRING_new();
ASN1_STRING_set(asn1_str, utf32_data, sizeof(utf32_data));
GENERAL_NAME* name = GENERAL_NAME_new();
ASN1_OBJECT* oid = OBJ_txt2obj("1.2.3.4.5", 1);
ASN1_TYPE* type = ASN1_TYPE_new();
ASN1_TYPE_set(type, V_ASN1_UNIVERSALSTRING, asn1_str);
GENERAL_NAME_set0_othername(name, oid, type);

std::string expected = "test";
expected += '\0';
expected += "string";

EXPECT_EQ(Utility::generalNameAsString(name), expected);

GENERAL_NAME_free(name);
}

TEST(UtilityTest, TestOtherNameBmpWithEmbeddedNull) {
// `BMP` strings are utf-16.
uint16_t utf16_data[] = {
htons('t'), htons('e'), htons('s'), htons('t'), 0 /* embedded null */, htons('s'), htons('t'),
htons('r'), htons('i'), htons('n'), htons('g'),
};
ASN1_STRING* asn1_str = ASN1_BMPSTRING_new();
ASN1_STRING_set(asn1_str, utf16_data, sizeof(utf16_data));
GENERAL_NAME* name = GENERAL_NAME_new();
ASN1_OBJECT* oid = OBJ_txt2obj("1.2.3.4.5", 1);
ASN1_TYPE* type = ASN1_TYPE_new();
ASN1_TYPE_set(type, V_ASN1_BMPSTRING, asn1_str);
GENERAL_NAME_set0_othername(name, oid, type);

std::string expected = "test";
expected += '\0';
expected += "string";

EXPECT_EQ(Utility::generalNameAsString(name), expected);

GENERAL_NAME_free(name);
}

TEST(UtilityTest, TestGetSubjectAlternateNamesWithDNS) {
bssl::UniquePtr<X509> cert = readCertFromFile(
TestEnvironment::substitute("{{ test_rundir }}/test/common/tls/test_data/san_dns_cert.pem"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ TEST_F(AuthenticatorTest, TestAllowFailedMultipleIssuers) {
header->set_value_prefix("Bearer ");

createAuthenticator(nullptr, absl::nullopt, /*allow_failed=*/true);
EXPECT_CALL(*raw_fetcher_, cancel());
EXPECT_CALL(*raw_fetcher_, fetch(_, _))
.Times(2)
.WillRepeatedly(Invoke([](Tracing::Span&, JwksFetcher::JwksReceiver& receiver) {
Expand Down
Loading