Skip to content

Commit ffc021d

Browse files
danzh2010danzh1989
andauthored
quic: fix socket UAF during port migration (envoyproxy#31702)
Commit Message: the probing socket is released when port migration fails. If this happens in response to an incoming packet during an I/O event, the follow socket read could cause use-after-free. [2024-01-08 16:30:53.386][12][critical][backtrace] [./source/server/backtrace.h:104] Caught Segmentation fault, suspect faulting address 0x0 [2024-01-08 16:30:53.387][12][critical][backtrace] [./source/server/backtrace.h:91] Backtrace (use tools/stack_decode.py to get line numbers): [2024-01-08 16:30:53.387][12][critical][backtrace] [./source/server/backtrace.h:92] Envoy version: 0/1.29.0-dev/test/DEBUG/BoringSSL [2024-01-08 16:30:53.413][12][critical][backtrace] [./source/server/backtrace.h:96] #0: Envoy::SignalAction::sigHandler() [0x55bb876d499e] [2024-01-08 16:30:53.413][12][critical][backtrace] [./source/server/backtrace.h:98] #1: [0x7f55fbf92510] [2024-01-08 16:30:53.440][12][critical][backtrace] [./source/server/backtrace.h:96] #2: Envoy::Network::Utility::readPacketsFromSocket() [0x55bb875de0ef] [2024-01-08 16:30:53.466][12][critical][backtrace] [./source/server/backtrace.h:96] #3: Envoy::Quic::EnvoyQuicClientConnection::onFileEvent() [0x55bb8663e1eb] [2024-01-08 16:30:53.492][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#4: Envoy::Quic::EnvoyQuicClientConnection::setUpConnectionSocket()::$_0::operator()() [0x55bb8663f192] [2024-01-08 16:30:53.518][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#5: std::__invoke_impl<>() [0x55bb8663f151] [2024-01-08 16:30:53.544][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#6: std::__invoke_r<>() [0x55bb8663f0e2] [2024-01-08 16:30:53.569][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#7: std::_Function_handler<>::_M_invoke() [0x55bb8663efc2] [2024-01-08 16:30:53.595][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#8: std::function<>::operator()() [0x55bb85cb8f44] [2024-01-08 16:30:53.621][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#9: Envoy::Event::DispatcherImpl::createFileEvent()::$_5::operator()() [0x55bb8722560f] [2024-01-08 16:30:53.648][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#10: std::__invoke_impl<>() [0x55bb872255c1] [2024-01-08 16:30:53.674][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#11: std::__invoke_r<>() [0x55bb87225562] [2024-01-08 16:30:53.700][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#12: std::_Function_handler<>::_M_invoke() [0x55bb872253e2] [2024-01-08 16:30:53.700][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#13: std::function<>::operator()() [0x55bb85cb8f44] [2024-01-08 16:30:53.726][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#14: Envoy::Event::FileEventImpl::mergeInjectedEventsAndRunCb() [0x55bb872358ec] [2024-01-08 16:30:53.752][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#15: Envoy::Event::FileEventImpl::assignEvents()::$_1::operator()() [0x55bb87235ed1] [2024-01-08 16:30:53.778][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#16: Envoy::Event::FileEventImpl::assignEvents()::$_1::__invoke() [0x55bb87235949] [2024-01-08 16:30:53.804][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#17: event_persist_closure [0x55bb87fab72b] [2024-01-08 16:30:53.830][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#18: event_process_active_single_queue [0x55bb87faada2] [2024-01-08 16:30:53.856][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#19: event_process_active [0x55bb87fa56c8] [2024-01-08 16:30:53.882][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#20: event_base_loop [0x55bb87fa45cc] [2024-01-08 16:30:53.908][12][critical][backtrace] [./source/server/backtrace.h:96] envoyproxy#21: Envoy::Event::LibeventScheduler::run() [0x55bb8760a59f] Risk Level: low Testing: new unit test Docs Changes: N/A Release Notes: Yes Platform Specific Features: N/A Signed-off-by: Dan Zhang <danzh@google.com> Co-authored-by: Dan Zhang <danzh@google.com>
1 parent 24ffda3 commit ffc021d

File tree

5 files changed

+110
-32
lines changed

5 files changed

+110
-32
lines changed

changelogs/current.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ bug_fixes:
113113
change: |
114114
Fixed a bug in QUIC and HCM interaction which could cause ``use-after-free`` during asynchronous certificates retrieval.
115115
The fix is guarded by runtime ``envoy.reloadable_features.quic_fix_filter_manager_uaf``.
116+
- area: quic
117+
change: |
118+
Fixed a bug in QUIC upstream port migration which could cause use-after-free upon STATELESS_RESET packets.
116119
- area: redis
117120
change: |
118121
Fixed a bug causing crash if incoming redis key does not match against a ``prefix_route`` and ``catch_all_route`` is not defined.

source/common/quic/envoy_quic_client_connection.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111
namespace Envoy {
1212
namespace Quic {
1313

14+
// Used to defer deleting connection socket to avoid deleting IoHandle in a read loop.
15+
class DeferredDeletableSocket : public Event::DeferredDeletable {
16+
public:
17+
explicit DeferredDeletableSocket(std::unique_ptr<Network::ConnectionSocket> socket)
18+
: socket_(std::move(socket)) {}
19+
20+
void deleteIsPending() override { socket_->close(); }
21+
22+
private:
23+
std::unique_ptr<Network::ConnectionSocket> socket_;
24+
};
25+
1426
EnvoyQuicClientConnection::EnvoyQuicClientConnection(
1527
const quic::QuicConnectionId& server_connection_id,
1628
Network::Address::InstanceConstSharedPtr& initial_peer_address,
@@ -188,6 +200,10 @@ void EnvoyQuicClientConnection::onPathValidationFailure(
188200
// Note that the probing socket and probing writer will be deleted once context goes out of
189201
// scope.
190202
OnPathValidationFailureAtClient(/*is_multi_port=*/false, *context);
203+
std::unique_ptr<Network::ConnectionSocket> probing_socket =
204+
static_cast<EnvoyQuicPathValidationContext*>(context.get())->releaseSocket();
205+
// Extend the socket life time till the end of the current event loop.
206+
dispatcher_.deferredDelete(std::make_unique<DeferredDeletableSocket>(std::move(probing_socket)));
191207
}
192208

193209
void EnvoyQuicClientConnection::onFileEvent(uint32_t events,

source/common/quic/envoy_quic_client_connection.h

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ class EnvoyQuicClientConnection : public quic::QuicConnection,
2727
public QuicNetworkConnection,
2828
public Network::UdpPacketProcessor {
2929
public:
30+
// Holds all components needed for a QUIC connection probing/migration.
31+
class EnvoyQuicPathValidationContext : public quic::QuicPathValidationContext {
32+
public:
33+
EnvoyQuicPathValidationContext(const quic::QuicSocketAddress& self_address,
34+
const quic::QuicSocketAddress& peer_address,
35+
std::unique_ptr<EnvoyQuicPacketWriter> writer,
36+
std::unique_ptr<Network::ConnectionSocket> probing_socket);
37+
38+
~EnvoyQuicPathValidationContext() override;
39+
40+
quic::QuicPacketWriter* WriterToUse() override;
41+
42+
EnvoyQuicPacketWriter* releaseWriter();
43+
44+
Network::ConnectionSocket& probingSocket();
45+
46+
std::unique_ptr<Network::ConnectionSocket> releaseSocket();
47+
48+
private:
49+
std::unique_ptr<EnvoyQuicPacketWriter> writer_;
50+
Network::ConnectionSocketPtr socket_;
51+
};
52+
3053
// A connection socket will be created with given |local_addr|. If binding
3154
// port not provided in |local_addr|, pick up a random port.
3255
EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id,
@@ -91,29 +114,6 @@ class EnvoyQuicClientConnection : public quic::QuicConnection,
91114
probeAndMigrateToServerPreferredAddress(const quic::QuicSocketAddress& server_preferred_address);
92115

93116
private:
94-
// Holds all components needed for a QUIC connection probing/migration.
95-
class EnvoyQuicPathValidationContext : public quic::QuicPathValidationContext {
96-
public:
97-
EnvoyQuicPathValidationContext(const quic::QuicSocketAddress& self_address,
98-
const quic::QuicSocketAddress& peer_address,
99-
std::unique_ptr<EnvoyQuicPacketWriter> writer,
100-
std::unique_ptr<Network::ConnectionSocket> probing_socket);
101-
102-
~EnvoyQuicPathValidationContext() override;
103-
104-
quic::QuicPacketWriter* WriterToUse() override;
105-
106-
EnvoyQuicPacketWriter* releaseWriter();
107-
108-
Network::ConnectionSocket& probingSocket();
109-
110-
std::unique_ptr<Network::ConnectionSocket> releaseSocket();
111-
112-
private:
113-
std::unique_ptr<EnvoyQuicPacketWriter> writer_;
114-
Network::ConnectionSocketPtr socket_;
115-
};
116-
117117
// Receives notifications from the Quiche layer on path validation results.
118118
class EnvoyPathValidationResultDelegate : public quic::QuicPathValidator::ResultDelegate {
119119
public:

test/common/quic/envoy_quic_client_session_test.cc

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection {
4747
generator) {
4848
SetEncrypter(quic::ENCRYPTION_FORWARD_SECURE,
4949
std::make_unique<quic::test::TaggingEncrypter>(quic::ENCRYPTION_FORWARD_SECURE));
50+
InstallDecrypter(quic::ENCRYPTION_FORWARD_SECURE,
51+
std::make_unique<quic::test::TaggingDecrypter>());
5052
SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
5153
}
5254

@@ -64,10 +66,11 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
6466
: api_(Api::createApiForTest(time_system_)),
6567
dispatcher_(api_->allocateDispatcher("test_thread")), connection_helper_(*dispatcher_),
6668
alarm_factory_(*dispatcher_, *connection_helper_.GetClock()), quic_version_({GetParam()}),
67-
peer_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(),
68-
12345)),
69+
peer_addr_(
70+
Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(), 0)),
6971
self_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(),
7072
54321)),
73+
peer_socket_(createConnectionSocket(self_addr_, peer_addr_, nullptr)),
7174
quic_connection_(new TestEnvoyQuicClientConnection(
7275
quic::test::TestConnectionId(), connection_helper_, alarm_factory_, writer_,
7376
quic_version_, *dispatcher_, createConnectionSocket(peer_addr_, self_addr_, nullptr),
@@ -83,12 +86,15 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
8386
/*send_buffer_limit*/ 1024 * 1024, crypto_stream_factory_, quic_stat_names_, {},
8487
*store_.rootScope(), transport_socket_options_, {}),
8588
stats_({ALL_HTTP3_CODEC_STATS(POOL_COUNTER_PREFIX(store_, "http3."),
86-
POOL_GAUGE_PREFIX(store_, "http3."))}),
87-
http_connection_(envoy_quic_session_, http_connection_callbacks_, stats_, http3_options_,
88-
64 * 1024, 100) {
89+
POOL_GAUGE_PREFIX(store_, "http3."))}) {
90+
http3_options_.mutable_quic_protocol_options()
91+
->mutable_num_timeouts_to_trigger_port_migration()
92+
->set_value(1);
93+
http_connection_ = std::make_unique<QuicHttpClientConnectionImpl>(
94+
envoy_quic_session_, http_connection_callbacks_, stats_, http3_options_, 64 * 1024, 100);
8995
EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime());
9096
EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol());
91-
EXPECT_EQ(Http::Protocol::Http3, http_connection_.protocol());
97+
EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol());
9298

9399
time_system_.advanceTimeWait(std::chrono::milliseconds(1));
94100
ON_CALL(writer_, WritePacket(_, _, _, _, _, _))
@@ -98,6 +104,9 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
98104
void SetUp() override {
99105
envoy_quic_session_.Initialize();
100106
setQuicConfigWithDefaultValues(envoy_quic_session_.config());
107+
quic::test::QuicConfigPeer::SetReceivedStatelessResetToken(
108+
envoy_quic_session_.config(),
109+
quic::QuicUtils::GenerateStatelessResetToken(quic::test::TestConnectionId()));
101110
envoy_quic_session_.OnConfigNegotiated();
102111
envoy_quic_session_.addConnectionCallbacks(network_connection_callbacks_);
103112
envoy_quic_session_.setConnectionStats(
@@ -112,12 +121,13 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
112121
EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose));
113122
envoy_quic_session_.close(Network::ConnectionCloseType::NoFlush);
114123
}
124+
peer_socket_->close();
115125
}
116126

117127
EnvoyQuicClientStream& sendGetRequest(Http::ResponseDecoder& response_decoder,
118128
Http::StreamCallbacks& stream_callbacks) {
119129
auto& stream =
120-
dynamic_cast<EnvoyQuicClientStream&>(http_connection_.newStream(response_decoder));
130+
dynamic_cast<EnvoyQuicClientStream&>(http_connection_->newStream(response_decoder));
121131
stream.getStream().addCallbacks(stream_callbacks);
122132

123133
std::string host("www.abc.com");
@@ -136,8 +146,12 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
136146
EnvoyQuicAlarmFactory alarm_factory_;
137147
quic::ParsedQuicVersionVector quic_version_;
138148
testing::NiceMock<quic::test::MockPacketWriter> writer_;
149+
// Initialized with port 0 and modified during peer_socket_ creation.
139150
Network::Address::InstanceConstSharedPtr peer_addr_;
140151
Network::Address::InstanceConstSharedPtr self_addr_;
152+
// Used in some tests to trigger a read event on the connection to test its full interaction with
153+
// socket read utility functions.
154+
Network::ConnectionSocketPtr peer_socket_;
141155
quic::DeterministicConnectionIdGenerator connection_id_generator_{
142156
quic::kQuicDefaultConnectionIdLength};
143157
TestEnvoyQuicClientConnection* quic_connection_;
@@ -156,13 +170,13 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam<quic::ParsedQui
156170
testing::StrictMock<Stats::MockGauge> write_current_;
157171
Http::Http3::CodecStats stats_;
158172
envoy::config::core::v3::Http3ProtocolOptions http3_options_;
159-
QuicHttpClientConnectionImpl http_connection_;
173+
std::unique_ptr<QuicHttpClientConnectionImpl> http_connection_;
160174
};
161175

162176
INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientSessionTests, EnvoyQuicClientSessionTest,
163177
testing::ValuesIn(quic::CurrentSupportedHttp3Versions()));
164178

165-
TEST_P(EnvoyQuicClientSessionTest, ShutdownNoOp) { http_connection_.shutdownNotice(); }
179+
TEST_P(EnvoyQuicClientSessionTest, ShutdownNoOp) { http_connection_->shutdownNotice(); }
166180

167181
TEST_P(EnvoyQuicClientSessionTest, NewStream) {
168182
Http::MockResponseDecoder response_decoder;
@@ -412,5 +426,49 @@ TEST_P(EnvoyQuicClientSessionTest, VerifyContextAbortOnFlushWriteBuffer) {
412426
"unexpectedly reached");
413427
}
414428

429+
// Tests that receiving a STATELESS_RESET packet on the probing socket doesn't cause crash.
430+
TEST_P(EnvoyQuicClientSessionTest, StatelessResetOnProbingSocket) {
431+
quic::QuicNewConnectionIdFrame frame;
432+
frame.connection_id = quic::test::TestConnectionId(1234);
433+
ASSERT_NE(frame.connection_id, quic_connection_->connection_id());
434+
frame.stateless_reset_token = quic::QuicUtils::GenerateStatelessResetToken(frame.connection_id);
435+
frame.retire_prior_to = 0u;
436+
frame.sequence_number = 1u;
437+
quic_connection_->OnNewConnectionIdFrame(frame);
438+
quic_connection_->SetSelfAddress(envoyIpAddressToQuicSocketAddress(self_addr_->ip()));
439+
440+
// Trigger port migration.
441+
quic_connection_->OnPathDegradingDetected();
442+
EXPECT_TRUE(envoy_quic_session_.HasPendingPathValidation());
443+
auto* path_validation_context =
444+
dynamic_cast<EnvoyQuicClientConnection::EnvoyQuicPathValidationContext*>(
445+
quic_connection_->GetPathValidationContext());
446+
Network::ConnectionSocket& probing_socket = path_validation_context->probingSocket();
447+
const Network::Address::InstanceConstSharedPtr& new_self_address =
448+
probing_socket.connectionInfoProvider().localAddress();
449+
EXPECT_NE(new_self_address->asString(), self_addr_->asString());
450+
451+
// Send a STATELESS_RESET packet to the probing socket.
452+
std::unique_ptr<quic::QuicEncryptedPacket> stateless_reset_packet =
453+
quic::QuicFramer::BuildIetfStatelessResetPacket(
454+
frame.connection_id, /*received_packet_length*/ 1200,
455+
quic::QuicUtils::GenerateStatelessResetToken(quic::test::TestConnectionId()));
456+
Buffer::RawSlice slice;
457+
slice.mem_ = const_cast<char*>(stateless_reset_packet->data());
458+
slice.len_ = stateless_reset_packet->length();
459+
peer_socket_->ioHandle().sendmsg(&slice, 1, 0, peer_addr_->ip(), *new_self_address);
460+
461+
// Receiving the STATELESS_RESET on the probing socket shouldn't close the connection but should
462+
// fail the probing.
463+
EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose))
464+
.Times(0);
465+
while (envoy_quic_session_.HasPendingPathValidation()) {
466+
// Running event loop to receive the STATELESS_RESET and following socket reads shouldn't cause
467+
// crash.
468+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
469+
}
470+
EXPECT_EQ(self_addr_->asString(), quic_connection_->self_address().ToString());
471+
}
472+
415473
} // namespace Quic
416474
} // namespace Envoy

test/common/quic/test_utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class TestQuicCryptoClientStream : public quic::QuicCryptoClientStream {
162162
proof_handler, has_application_state) {}
163163

164164
bool encryption_established() const override { return true; }
165+
quic::HandshakeState GetHandshakeState() const override { return quic::HANDSHAKE_CONFIRMED; }
165166
};
166167

167168
class TestQuicCryptoClientStreamFactory : public EnvoyQuicCryptoClientStreamFactoryInterface {

0 commit comments

Comments
 (0)