Summary
There is a use-after-free in HttpConnectionManager
(HCM) with EnvoyQuicServerStream
that can crash Envoy.
Details
EnvoyQuicServerStream
doesn't run reset callbacks upon incoming RESET_STREAM
if no STOP_SENDING
is received before or afterwards.
If a client sends a request without FIN
and then a RESET_STREAM
frame, OnStreamReset()
will not call runResetCallbacks()
because the write side is not closed yet and Envoy is supposed to be able to send any response. Such a stream will not receive any more request payload and thus will hit stream idle timeout in HCM and send a local reply.
Upon FIN
being written to the codec, ConnectionManagerImpl::doEndStream()
will call resetStream()
on the EnvoyQuicServerStream
because HCM hasn't received FIN
yet, nor does it know about the RESET_STREAM
received on QUIC stream.
The resetStream()
implementation will call ResetWithError()
to send both STOP_SENDING
and RESET_STREAM
because reading has been stopped. ResetWithError()
also won't call runResetCallbacks()
because local_end_stream_
is already true
.
After this point, the QUIC stream is both read and write closed and thus legitimate to be destroyed from QUICHE's perspective, but ActiveStream
in HCM is still referencing the QUIC stream as the response encoder.
Accessing the response encoder after this can lead to a use-after-free, triggering the following crash stack:
Envoy::Http::ConnectionManagerImpl::resetAllStreams
Envoy::Http::ConnectionManagerImpl::doConnectionClose
Envoy::Http::ConnectionManagerImpl::onEvent
Envoy::Network::ConnectionImplBase::raiseConnectionEvent
Envoy::Quic::QuicFilterManagerConnectionImpl::onConnectionCloseEvent
Envoy::Quic::EnvoyQuicServerSession::OnConnectionClosed
external_quiche_quic::QuicConnection::TearDownLocalConnectionState
external_quiche_quic::QuicConnection::OnIdleNetworkDetected
external_quiche_quic::QuicAlarm::Fire
event_process_active_single_queue
event_process_active
event_base_loop
Envoy::Server::WorkerImpl::threadRoutine
PoC
Use a QUIC client to send a request without FIN
and then a RESET_STREAM
frame. After receiving the response, close the connection.
Impact
All HTTP/3 downstream users
Summary
There is a use-after-free in
HttpConnectionManager
(HCM) withEnvoyQuicServerStream
that can crash Envoy.Details
EnvoyQuicServerStream
doesn't run reset callbacks upon incomingRESET_STREAM
if noSTOP_SENDING
is received before or afterwards.If a client sends a request without
FIN
and then aRESET_STREAM
frame,OnStreamReset()
will not callrunResetCallbacks()
because the write side is not closed yet and Envoy is supposed to be able to send any response. Such a stream will not receive any more request payload and thus will hit stream idle timeout in HCM and send a local reply.Upon
FIN
being written to the codec,ConnectionManagerImpl::doEndStream()
will callresetStream()
on theEnvoyQuicServerStream
because HCM hasn't receivedFIN
yet, nor does it know about theRESET_STREAM
received on QUIC stream.The
resetStream()
implementation will callResetWithError()
to send bothSTOP_SENDING
andRESET_STREAM
because reading has been stopped.ResetWithError()
also won't callrunResetCallbacks()
becauselocal_end_stream_
is alreadytrue
.After this point, the QUIC stream is both read and write closed and thus legitimate to be destroyed from QUICHE's perspective, but
ActiveStream
in HCM is still referencing the QUIC stream as the response encoder.Accessing the response encoder after this can lead to a use-after-free, triggering the following crash stack:
PoC
Use a QUIC client to send a request without
FIN
and then aRESET_STREAM
frame. After receiving the response, close the connection.Impact
All HTTP/3 downstream users