Skip to content

Commit b701b03

Browse files
authored
hcm: Ensure operations are not called on deleted stream decoders (envoyproxy#39346)
Previously, it was possible for the `ActiveStream` in the `HttpConnectionManager` to get deleted while still trying to process packets from the codec. This change uses the `ActiveStreamHandle` with weak pointer semantics to ensure that even with incoming data packets, methods are not called on a deleted `ActiveStream`, which represents the HCM's `RequestDecoder`. --------- Signed-off-by: Ali Beyad <[email protected]>
1 parent 297581b commit b701b03

17 files changed

+223
-37
lines changed

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ bug_fixes:
4949
draining. This can result in not enough connections being established for current pending requests. This is most problematic for
5050
long-lived requests (such as streaming gRPC requests or long-poll requests) because a connection could be in the draining state
5151
for a long time.
52+
- area: hcm
53+
change: |
54+
Fixes a bug where the lifetime of the HttpConnectionManager's ActiveStream can be out of sync
55+
with the lifetime of the codec stream.
5256
5357
removed_config_or_runtime:
5458
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`

envoy/http/api_listener.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,6 @@
55
namespace Envoy {
66
namespace Http {
77

8-
class RequestDecoderHandle {
9-
public:
10-
virtual ~RequestDecoderHandle() = default;
11-
12-
/**
13-
* @return a reference to the underlying decoder if it is still valid.
14-
*/
15-
virtual OptRef<RequestDecoder> get() PURE;
16-
};
17-
using RequestDecoderHandlePtr = std::unique_ptr<RequestDecoderHandle>;
18-
198
/**
209
* ApiListener that allows consumers to interact with HTTP streams via API calls.
2110
*/

envoy/http/codec.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "envoy/access_log/access_log.h"
88
#include "envoy/buffer/buffer.h"
99
#include "envoy/common/matchers.h"
10+
#include "envoy/common/optref.h"
1011
#include "envoy/common/pure.h"
1112
#include "envoy/grpc/status.h"
1213
#include "envoy/http/header_formatter.h"
@@ -53,6 +54,17 @@ const char MaxResponseHeadersSizeOverrideKey[] =
5354
class Stream;
5455
class RequestDecoder;
5556

57+
class RequestDecoderHandle {
58+
public:
59+
virtual ~RequestDecoderHandle() = default;
60+
61+
/**
62+
* @return a reference to the underlying decoder if it is still valid.
63+
*/
64+
virtual OptRef<RequestDecoder> get() PURE;
65+
};
66+
using RequestDecoderHandlePtr = std::unique_ptr<RequestDecoderHandle>;
67+
5668
/**
5769
* Error codes used to convey the reason for a GOAWAY.
5870
*/
@@ -264,6 +276,12 @@ class RequestDecoder : public virtual StreamDecoder {
264276
* @return List of shared pointers to access loggers for this stream.
265277
*/
266278
virtual AccessLog::InstanceSharedPtrVector accessLogHandlers() PURE;
279+
280+
/**
281+
* @return A handle to the request decoder. Caller can check the request decoder's liveness via
282+
* the handle.
283+
*/
284+
virtual RequestDecoderHandlePtr getRequestDecoderHandle() PURE;
267285
};
268286

269287
/**

source/common/http/conn_manager_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
207207
filter_log_handlers.end());
208208
return combined_log_handlers;
209209
}
210+
211+
RequestDecoderHandlePtr getRequestDecoderHandle() override {
212+
return std::make_unique<ActiveStreamHandle>(*this);
213+
}
214+
210215
// Hand off headers/trailers and stream info to the codec's response encoder, for logging later
211216
// (i.e. possibly after this stream has been destroyed).
212217
//

source/common/http/http2/codec_impl.cc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ void ConnectionImpl::StreamImpl::decodeData() {
498498
stream_manager_.decodeAsChunks() &&
499499
pending_recv_data_->length() > stream_manager_.defer_processing_segment_size_;
500500

501+
StreamDecoder* stream_decoder = decoder();
501502
if (decode_data_in_chunk) {
502503
Buffer::OwnedImpl chunk_buffer;
503504
// TODO(kbaichoo): Consider implementing an approximate move for chunking.
@@ -508,15 +509,19 @@ void ConnectionImpl::StreamImpl::decodeData() {
508509
stream_manager_.body_buffered_ = true;
509510
ASSERT(pending_recv_data_->length() > 0);
510511

511-
decoder().decodeData(chunk_buffer, sendEndStream());
512+
if (stream_decoder) {
513+
stream_decoder->decodeData(chunk_buffer, sendEndStream());
514+
}
512515
already_drained_data = true;
513516

514517
if (!buffersOverrun()) {
515518
scheduleProcessingOfBufferedData(true);
516519
}
517520
} else {
518521
// Send the entire buffer through.
519-
decoder().decodeData(*pending_recv_data_, sendEndStream());
522+
if (stream_decoder) {
523+
stream_decoder->decodeData(*pending_recv_data_, sendEndStream());
524+
}
520525
}
521526
}
522527

@@ -597,7 +602,11 @@ void ConnectionImpl::ServerStreamImpl::decodeHeaders() {
597602
Http::Utility::transformUpgradeRequestFromH2toH1(*headers);
598603
}
599604
#endif
600-
request_decoder_->decodeHeaders(std::move(headers), sendEndStream());
605+
RequestDecoder* request_decoder = request_decoder_handle_->get().ptr();
606+
ENVOY_BUG(request_decoder != nullptr, "Missing request_decoder_");
607+
if (request_decoder) {
608+
request_decoder->decodeHeaders(std::move(headers), sendEndStream());
609+
}
601610
}
602611

603612
void ConnectionImpl::ServerStreamImpl::decodeTrailers() {
@@ -608,8 +617,12 @@ void ConnectionImpl::ServerStreamImpl::decodeTrailers() {
608617
// Consume any buffered trailers.
609618
stream_manager_.trailers_buffered_ = false;
610619

611-
request_decoder_->decodeTrailers(
612-
std::move(absl::get<RequestTrailerMapPtr>(headers_or_trailers_)));
620+
RequestDecoder* request_decoder = request_decoder_handle_->get().ptr();
621+
ENVOY_BUG(request_decoder != nullptr, "Missing request_decoder_");
622+
if (request_decoder) {
623+
request_decoder->decodeTrailers(
624+
std::move(absl::get<RequestTrailerMapPtr>(headers_or_trailers_)));
625+
}
613626
}
614627

615628
void ConnectionImpl::StreamImpl::pendingSendBufferHighWatermark() {
@@ -827,7 +840,10 @@ void ConnectionImpl::StreamImpl::onMetadataDecoded(MetadataMapPtr&& metadata_map
827840
ENVOY_CONN_LOG(debug, "decode metadata called with empty map, skipping", parent_.connection_);
828841
parent_.stats_.metadata_empty_frames_.inc();
829842
} else {
830-
decoder().decodeMetadata(std::move(metadata_map_ptr));
843+
StreamDecoder* stream_decoder = decoder();
844+
if (stream_decoder) {
845+
stream_decoder->decodeMetadata(std::move(metadata_map_ptr));
846+
}
831847
}
832848
}
833849

source/common/http/http2/codec_impl.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ class ConnectionImpl : public virtual Connection,
324324
// sendPendingFrames so pending outbound frames have one final chance to be flushed. If we
325325
// submit a reset, nghttp2 will cancel outbound frames that have not yet been sent.
326326
virtual bool useDeferredReset() const PURE;
327-
virtual StreamDecoder& decoder() PURE;
327+
virtual StreamDecoder* decoder() PURE;
328328
virtual HeaderMap& headers() PURE;
329329
virtual void allocTrailers() PURE;
330330
virtual HeaderMapPtr cloneTrailers(const HeaderMap& trailers) PURE;
@@ -523,7 +523,7 @@ class ConnectionImpl : public virtual Connection,
523523
HeadersState headersState() const override { return headers_state_; }
524524
// Do not use deferred reset on upstream connections.
525525
bool useDeferredReset() const override { return false; }
526-
StreamDecoder& decoder() override { return response_decoder_; }
526+
StreamDecoder* decoder() override { return &response_decoder_; }
527527
void decodeHeaders() override;
528528
void decodeTrailers() override;
529529
HeaderMap& headers() override {
@@ -585,7 +585,7 @@ class ConnectionImpl : public virtual Connection,
585585
// written out before force resetting the stream, assuming there is enough H2 connection flow
586586
// control window is available.
587587
bool useDeferredReset() const override { return true; }
588-
StreamDecoder& decoder() override { return *request_decoder_; }
588+
StreamDecoder* decoder() override { return request_decoder_handle_->get().ptr(); }
589589
void decodeHeaders() override;
590590
void decodeTrailers() override;
591591
HeaderMap& headers() override {
@@ -610,7 +610,9 @@ class ConnectionImpl : public virtual Connection,
610610
void encodeTrailers(const ResponseTrailerMap& trailers) override {
611611
encodeTrailersBase(trailers);
612612
}
613-
void setRequestDecoder(Http::RequestDecoder& decoder) override { request_decoder_ = &decoder; }
613+
void setRequestDecoder(Http::RequestDecoder& decoder) override {
614+
request_decoder_handle_ = decoder.getRequestDecoderHandle();
615+
}
614616
void setDeferredLoggingHeadersAndTrailers(Http::RequestHeaderMapConstSharedPtr,
615617
Http::ResponseHeaderMapConstSharedPtr,
616618
Http::ResponseTrailerMapConstSharedPtr,
@@ -626,7 +628,7 @@ class ConnectionImpl : public virtual Connection,
626628
}
627629

628630
private:
629-
RequestDecoder* request_decoder_{};
631+
RequestDecoderHandlePtr request_decoder_handle_;
630632
HeadersState headers_state_ = HeadersState::Request;
631633
};
632634

source/common/quic/envoy_quic_server_stream.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <openssl/evp.h>
55

66
#include <memory>
7+
#include <utility>
78

89
#include "source/common/buffer/buffer_impl.h"
910
#include "source/common/common/assert.h"
@@ -214,7 +215,10 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len,
214215
}
215216
#endif
216217

217-
request_decoder_->decodeHeaders(std::move(headers), /*end_stream=*/fin);
218+
Http::RequestDecoder* decoder = request_decoder_->get().ptr();
219+
if (decoder != nullptr) {
220+
decoder->decodeHeaders(std::move(headers), /*end_stream=*/fin);
221+
}
218222
ConsumeHeaderList();
219223
}
220224

@@ -262,7 +266,10 @@ void EnvoyQuicServerStream::OnBodyAvailable() {
262266
// A stream error has occurred, stop processing.
263267
return;
264268
}
265-
request_decoder_->decodeData(*buffer, fin_read_and_no_trailers);
269+
Http::RequestDecoder* decoder = request_decoder_->get().ptr();
270+
if (decoder != nullptr) {
271+
decoder->decodeData(*buffer, fin_read_and_no_trailers);
272+
}
266273
}
267274

268275
if (!sequencer()->IsClosed() || read_side_closed()) {
@@ -314,7 +321,10 @@ void EnvoyQuicServerStream::maybeDecodeTrailers() {
314321
onStreamError(close_connection_upon_invalid_header_, rst);
315322
return;
316323
}
317-
request_decoder_->decodeTrailers(std::move(trailers));
324+
Http::RequestDecoder* decoder = request_decoder_->get().ptr();
325+
if (decoder != nullptr) {
326+
decoder->decodeTrailers(std::move(trailers));
327+
}
318328
MarkTrailersConsumed();
319329
}
320330
}
@@ -499,7 +509,10 @@ void EnvoyQuicServerStream::OnMetadataComplete(size_t /*frame_len*/,
499509
return;
500510
}
501511
if (!header_list.empty()) {
502-
request_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
512+
Http::RequestDecoder* decoder = request_decoder_->get().ptr();
513+
if (decoder != nullptr) {
514+
decoder->decodeMetadata(metadataMapFromHeaderList(header_list));
515+
}
503516
}
504517
}
505518

@@ -542,8 +555,8 @@ bool EnvoyQuicServerStream::hasPendingData() {
542555
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
543556
void EnvoyQuicServerStream::useCapsuleProtocol() {
544557
http_datagram_handler_ = std::make_unique<HttpDatagramHandler>(*this);
545-
ASSERT(request_decoder_ != nullptr);
546-
http_datagram_handler_->setStreamDecoder(request_decoder_);
558+
ASSERT(request_decoder_->get().has_value());
559+
http_datagram_handler_->setStreamDecoder(request_decoder_->get().ptr());
547560
RegisterHttp3DatagramVisitor(http_datagram_handler_.get());
548561
}
549562
#endif

source/common/quic/envoy_quic_server_stream.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase,
2727
headers_with_underscores_action);
2828

2929
void setRequestDecoder(Http::RequestDecoder& decoder) override {
30-
request_decoder_ = &decoder;
31-
stats_gatherer_->setAccessLogHandlers(request_decoder_->accessLogHandlers());
30+
request_decoder_ = decoder.getRequestDecoderHandle();
31+
stats_gatherer_->setAccessLogHandlers(request_decoder_->get()->accessLogHandlers());
3232
}
3333

3434
// Http::StreamEncoder
@@ -122,7 +122,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase,
122122
void useCapsuleProtocol();
123123
#endif
124124

125-
Http::RequestDecoder* request_decoder_{nullptr};
125+
Http::RequestDecoderHandlePtr request_decoder_;
126126
envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
127127
headers_with_underscores_action_;
128128

source/common/quic/http_datagram_handler.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ void HttpDatagramHandler::decodeCapsule(const quiche::Capsule& capsule) {
1818
quiche::QuicheBuffer serialized_capsule = SerializeCapsule(capsule, &capsule_buffer_allocator_);
1919
Buffer::InstancePtr buffer = std::make_unique<Buffer::OwnedImpl>();
2020
buffer->add(serialized_capsule.AsStringView());
21-
if (!stream_decoder_) {
21+
if (stream_decoder_) {
22+
stream_decoder_->decodeData(*buffer, stream_.IsDoneReading());
23+
} else {
2224
IS_ENVOY_BUG("HTTP/3 Datagram received before a stream decoder is set.");
2325
}
24-
stream_decoder_->decodeData(*buffer, stream_.IsDoneReading());
2526
}
2627

2728
void HttpDatagramHandler::OnHttp3Datagram(quic::QuicStreamId stream_id, absl::string_view payload) {

test/common/http/http2/codec_impl_test.cc

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class Http2CodecImplTestFixture {
248248
server_wrapper_ = std::make_unique<ConnectionWrapper>(server_.get());
249249
createHeaderValidator();
250250
request_encoder_ = &client_->newStream(response_decoder_);
251-
setupDefaultConnectionMocks();
251+
setupDefaultMocks();
252252
driveToCompletion();
253253

254254
EXPECT_CALL(server_callbacks_, newStream(_, _))
@@ -267,7 +267,16 @@ class Http2CodecImplTestFixture {
267267
.WillByDefault(Return(true));
268268
}
269269

270-
void setupDefaultConnectionMocks() {
270+
void setupRequestDecoderMock(MockRequestDecoder& request_decoder) {
271+
EXPECT_CALL(request_decoder, getRequestDecoderHandle())
272+
.WillRepeatedly(Invoke([&request_decoder]() {
273+
auto handle = std::make_unique<NiceMock<MockRequestDecoderHandle>>();
274+
ON_CALL(*handle, get()).WillByDefault(Return(OptRef<RequestDecoder>(request_decoder)));
275+
return handle;
276+
}));
277+
}
278+
279+
void setupDefaultMocks() {
271280
ON_CALL(client_connection_, write(_, _))
272281
.WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void {
273282
if (corrupt_metadata_frame_) {
@@ -280,6 +289,7 @@ class Http2CodecImplTestFixture {
280289
[&](Buffer::Instance& data, bool) -> void { client_wrapper_->buffer_.add(data); }));
281290
// Set to the small read size (reads are suggested to be 16k aligned).
282291
ON_CALL(server_connection_, bufferLimit()).WillByDefault(Return(16 * 1024));
292+
setupRequestDecoderMock(request_decoder_);
283293
}
284294

285295
void http2OptionsFromTuple(envoy::config::core::v3::Http2ProtocolOptions& options,
@@ -1895,6 +1905,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) {
18951905
StreamEncoder* response_encoder2;
18961906
MockStreamCallbacks server_stream_callbacks2;
18971907
MockRequestDecoder request_decoder2;
1908+
setupRequestDecoderMock(request_decoder2);
18981909
// When the server stream is created it should check the status of the
18991910
// underlying connection. Pretend it is overrun.
19001911
EXPECT_CALL(server_connection_, aboveHighWatermark()).WillOnce(Return(true));
@@ -2505,7 +2516,7 @@ TEST_P(Http2CodecImplStreamLimitTest, MaxClientStreams) {
25052516
server_http2_options_, random_, max_request_headers_kb_, max_request_headers_count_,
25062517
headers_with_underscores_action_);
25072518
server_wrapper_ = std::make_unique<ConnectionWrapper>(server_.get());
2508-
setupDefaultConnectionMocks();
2519+
setupDefaultMocks();
25092520
driveToCompletion();
25102521
for (int i = 0; i < 101; ++i) {
25112522
request_encoder_ = &client_->newStream(response_decoder_);
@@ -4351,6 +4362,7 @@ TEST_P(Http2CodecImplTest, CheckHeaderPaddedWhitespaceValidation) {
43514362
MockStreamCallbacks server_stream_callbacks;
43524363
MockRequestDecoder request_decoder;
43534364

4365+
setupRequestDecoderMock(request_decoder);
43544366
EXPECT_CALL(server_callbacks_, newStream(_, _))
43554367
.WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& {
43564368
response_encoder = &encoder;
@@ -4437,6 +4449,8 @@ TEST_P(Http2CodecImplTest, CheckHeaderValueValidation) {
44374449

44384450
scoped_runtime_.mergeValues({{"envoy.reloadable_features.validate_upstream_headers", "false"}});
44394451
stream_error_on_invalid_http_messaging_ = true;
4452+
4453+
setupRequestDecoderMock(request_decoder_);
44404454
initialize();
44414455

44424456
#ifdef ENVOY_ENABLE_UHV
@@ -4471,6 +4485,7 @@ TEST_P(Http2CodecImplTest, CheckHeaderValueValidation) {
44714485
MockStreamCallbacks server_stream_callbacks;
44724486
MockRequestDecoder request_decoder;
44734487

4488+
setupRequestDecoderMock(request_decoder);
44744489
EXPECT_CALL(server_callbacks_, newStream(_, _))
44754490
.WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& {
44764491
response_encoder = &encoder;
@@ -4591,7 +4606,7 @@ class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testin
45914606
server_http2_options_, random_, max_request_headers_kb_, max_request_headers_count_,
45924607
headers_with_underscores_action_);
45934608
server_wrapper_ = std::make_unique<ConnectionWrapper>(server_.get());
4594-
setupDefaultConnectionMocks();
4609+
setupDefaultMocks();
45954610
driveToCompletion();
45964611
}
45974612

0 commit comments

Comments
 (0)