#0 0x00007f9f9fbc9f7c in pthread_kill@@GLIBC_2.34 () from /usr/drte/v5/lib64/libc.so.6
No symbol table info available.
#1 0x00007f9f9fb78f22 in raise () from /usr/drte/v5/lib64/libc.so.6
No symbol table info available.
#2 0x000056431657e33f in Envoy::SignalAction::sigHandler (sig=11, info=<optimized out>, context=0x7f9f7227dd00) at external/envoy/source/common/signal/signal_action.cc:53
tracer = {<Envoy::Logger::Loggable<(Envoy::Logger::Id)4>> = {<No data fields>}, static log_to_stderr_ = false, static MaxStackDepth = 64, stack_trace_ = {0x564316317acf <quic::QuicSpdyStream::OnHeadersDecoded(quic::QuicHeaderList, bool)+271>, 0x56431631dafb <quic::QpackDecodedHeadersAccumulator::OnDecodingCompleted()+107>, 0x5643164b4c4a <quic::QpackDecoderHeaderTable::InsertEntry(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >)+90>, 0x56431631fb9d <quic::QpackEncoderStreamReceiver::OnInstructionDecoded(quic::QpackInstruction const*)+157>, 0x5643163255f2 <quic::QpackInstructionDecoder::DoStartField()+626>, 0x564316325750 <quic::QpackInstructionDecoder::Decode(std::basic_string_view<char, std::char_traits<char> >)+288>, 0x564316328035 <quic::QpackReceiveStream::OnDataAvailable()+117>, 0x5643163644eb <quic::QuicStreamSequencer::OnFrameData(unsigned long, unsigned long, char const*)+843>, 0x5643163818a2 <quic::QuicConnection::OnStreamFrame(quic::QuicStreamFrame const&)+626>, 0x5643163d3c6c <quic::QuicFramer::ProcessIetfFrameData(quic::QuicDataReader*, quic::QuicPacketHeader const&, quic::EncryptionLevel)+460>, 0x5643163d518b <quic::QuicFramer::ProcessIetfDataPacket(quic::QuicDataReader*, quic::QuicPacketHeader*, quic::QuicEncryptedPacket const&, char*, unsigned long)+2491>, 0x5643163d549a <quic::QuicFramer::ProcessPacketInternal(quic::QuicEncryptedPacket const&)+458>, 0x5643163d55d8 <quic::QuicFramer::ProcessPacket(quic::QuicEncryptedPacket const&)+24>, 0x56431638c1d2 <quic::QuicConnection::ProcessUdpPacket(quic::QuicSocketAddress const&, quic::QuicSocketAddress const&, quic::QuicReceivedPacket const&)+1010>, 0x56431634f224 <quic::QuicSession::ProcessUdpPacket(quic::QuicSocketAddress const&, quic::QuicSocketAddress const&, quic::QuicReceivedPacket const&)+84>, 0x5643162e9d2d <Envoy::Quic::EnvoyQuicServerSession::ProcessUdpPacket(quic::QuicSocketAddress const&, quic::QuicSocketAddress const&, quic::QuicReceivedPacket const&)+61>, 0x5643162ab5fd <quic::QuicDispatcher::MaybeDispatchPacket(quic::ReceivedPacketInfo const&)+429>, 0x5643162ae094 <quic::QuicDispatcher::ProcessPacket(quic::QuicSocketAddress const&, quic::QuicSocketAddress const&, quic::QuicReceivedPacket const&)+308>, 0x56431629bdf9 <Envoy::Quic::EnvoyQuicDispatcher::processPacket(quic::QuicSocketAddress const&, quic::QuicSocketAddress const&, quic::QuicReceivedPacket const&)+25>, 0x564316297beb <Envoy::Quic::ActiveQuicListener::onDataWorker(Envoy::Network::UdpRecvData&&)+235>, 0x5643162be179 <Envoy::Network::UdpListenerImpl::processPacket(std::shared_ptr<Envoy::Network::Address::Instance const>, std::shared_ptr<Envoy::Network::Address::Instance const>, std::unique_ptr<Envoy::Buffer::Instance, std::default_delete<Envoy::Buffer::Instance> >, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >)+121>, 0x564316571661 <Envoy::Network::passPayloadToProcessor(unsigned long, std::unique_ptr<Envoy::Buffer::Instance, std::default_delete<Envoy::Buffer::Instance> >, std::shared_ptr<Envoy::Network::Address::Instance const>, std::shared_ptr<Envoy::Network::Address::Instance const>, Envoy::Network::UdpPacketProcessor&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >)+689>, 0x564316572d25 <Envoy::Network::Utility::readFromSocket(Envoy::Network::IoHandle&, Envoy::Network::Address::Instance const&, Envoy::Network::UdpPacketProcessor&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >, bool, unsigned int*)+4821>, 0x5643165731b4 <Envoy::Network::Utility::readPacketsFromSocket(Envoy::Network::IoHandle&, Envoy::Network::Address::Instance const&, Envoy::Network::UdpPacketProcessor&, Envoy::TimeSource&, bool, unsigned int&)+276>, 0x5643162bf625 <Envoy::Network::UdpListenerImpl::handleReadCallback()+293>, 0x5643162bfb2d <Envoy::Network::UdpListenerImpl::onSocketEvent(short)+381>, 0x564316412b5e <std::_Function_handler<void(unsigned int), Envoy::Event::DispatcherImpl::createFileEvent(os_fd_t, Envoy::Event::FileReadyCb, Envoy::Event::FileTriggerType, uint32_t)::<lambda(uint32_t)> >::_M_invoke(const std::_Any_data &, unsigned int &&)+62>, 0x564316416921 <Envoy::Event::FileEventImpl::mergeInjectedEventsAndRunCb(unsigned int)+97>, 0x5643165a2922 <event_process_active_single_queue+1346>, 0x5643165a2e47 <event_base_loop+1079>, 0x564315a2ee72 <Envoy::Server::WorkerImpl::threadRoutine(Envoy::OptRef<Envoy::Server::GuardDog>, std::function<void ()> const&)+418>, 0x5643167391a5 <Envoy::Thread::ThreadImplPosix::ThreadImplPosix(std::function<void ()>, std::optional<Envoy::Thread::Options> const&)::{lambda(void*)#1}::_FUN(void*)+21>, 0x7f9f9fbc8173 <start_thread+723>, 0x5643163b9b0b <quic::QuicPacketCreator::ExpansionOnNewFrameWithLastFrame(quic::QuicFrame const&, quic::QuicTransportVersion)+43>, 0x7f9f7227dbf0, 0x5643163ba704 <quic::QuicPacketCreator::BytesFree() const+36>, 0x7f9f7227de30, 0x5643163bb726 <quic::QuicPacketCreator::CreateStreamFrame(unsigned int, unsigned long, unsigned long, bool, quic::QuicFrame*)+246>, 0x70001000a, 0x0, 0x18c09a, 0x6b2f, 0xcd37, 0x83ac0, 0x10a9, 0x0, 0x631, 0x31f, 0x0, 0x0, 0x70001000a, 0x5fd046e0b387700, 0x1, 0x45584f077340, 0x564317efe6d8 <tcmalloc::tcmalloc_internal::Static::page_allocator_+5304>, 0x1, 0xdc, 0x564317efd220 <tcmalloc::tcmalloc_internal::Static::page_allocator_>, 0x7f9f7227dcc0, 0x564316c06625 <tcmalloc::tcmalloc_internal::HugePageFiller<tcmalloc::tcmalloc_internal::PageTracker<&tcmalloc::tcmalloc_internal::SystemRelease> >::Put(tcmalloc::tcmalloc_internal::PageTracker<&tcmalloc::tcmalloc_internal::SystemRelease>*, tcmalloc::tcmalloc_internal::PageId, tcmalloc::tcmalloc_internal::Length)+949>, 0x7f9f7227dce0, 0x1, 0x45584f077340, 0x564317efd220 <tcmalloc::tcmalloc_internal::Static::page_allocator_>}, stack_depth_ = 33}
status = <optimized out>
__func__ = "sigHandler"
#3 <signal handler called>
No symbol table info available.
#4 0x00005643162f13d5 in Envoy::Quic::EnvoyQuicServerStream::OnInitialHeadersComplete (this=0x4557eee87500, fin=<optimized out>, frame_len=<optimized out>, header_list=...) at external/envoy/source/common/quic/envoy_quic_server_stream.cc:304
__func__ = "OnInitialHeadersComplete"
rst = quic::QUIC_STREAM_NO_ERROR
server_session = <optimized out>
headers = {_M_t = {<std::__uniq_ptr_impl<Envoy::Http::RequestHeaderMapImpl, std::default_delete<Envoy::Http::RequestHeaderMapImpl> >> = {_M_t = {<std::_Tuple_impl<0, Envoy::Http::RequestHeaderMapImpl*, std::default_delete<Envoy::Http::RequestHeaderMapImpl> >> = {<std::_Tuple_impl<1, std::default_delete<Envoy::Http::RequestHeaderMapImpl> >> = {<std::_Head_base<1, std::default_delete<Envoy::Http::RequestHeaderMapImpl>, true>> = {_M_head_impl = {<No data fields>}}, <No data fields>}, <std::_Head_base<0, Envoy::Http::RequestHeaderMapImpl*, false>> = {_M_head_impl = 0x455724410c80}, <No data fields>}, <No data fields>}}, <No data fields>}}
#5 0x0000564316317acf in quic::QuicSpdyStream::OnHeadersDecoded (this=0x4557eee87500, headers=..., header_list_size_limit_exceeded=<optimized out>) at external/com_github_google_quiche/quiche/quic/core/http/quic_spdy_stream.cc:592
debug_visitor = 0x0
#6 0x000056431631dafb in quic::QpackDecodedHeadersAccumulator::OnDecodingCompleted (this=0x4557407d0d80) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc:63
No locals.
#7 0x00005643164b4c4a in quic::QpackDecoderHeaderTable::InsertEntry (this=0x45593a6b7340, name=..., value=...) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_header_table.cc:189
it = <optimized out>
observer = 0x45591781ab48
index = 29
#8 0x000056431631fb9d in quic::QpackEncoderStreamReceiver::OnInstructionDecoded (this=0x45593a6b7220, instruction=0x4557499d5a80) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_instruction_decoder.h:74
No locals.
#9 0x00005643163255f2 in quic::QpackInstructionDecoder::DoStartField (this=0x45593a6b7230) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_instruction_decoder.cc:109
No locals.
#10 0x0000564316325750 in quic::QpackInstructionDecoder::Decode (this=0x45593a6b7230, data=...) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_instruction_decoder.cc:49
success = true
bytes_consumed = 0
#11 0x0000564316328035 in quic::QpackReceiveStream::OnDataAvailable (this=0x45591f446480) at external/com_github_google_quiche/quiche/quic/core/qpack/qpack_receive_stream.cc:27
iov = {iov_base = 0x4558cfa84005, iov_len = 1859}
#12 0x00005643163644eb in quic::QuicStreamSequencer::OnFrameData (this=0x45591f446488, byte_offset=<optimized out>, data_len=1194, data_buffer=0x7f9f7227fe85 "[redacted]") at external/com_github_google_quiche/quiche/quic/core/quic_stream_sequencer.cc:124
previous_readable_bytes = 0
bytes_written = 1194
error_details = {static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7f9f7227ef90 ""}, _M_string_length = 0, {_M_local_buf = "[redacted]", _M_allocated_capacity = 9845652197662656768}}
result = quic::QUIC_NO_ERROR
__func__ = "OnFrameData"
stream_unblocked = true
#13 0x00005643163818a2 in quic::QuicConnection::OnStreamFrame (this=0x455821998000, frame=...) at external/com_github_google_quiche/quiche/quic/core/quic_connection.cc:1379
__func__ = "OnStreamFrame"
#14 0x00005643163d3c6c in quic::QuicFramer::ProcessIetfFrameData (this=0x455821998070, reader=0x7f9f7227fa50, header=..., decrypted_level=quic::ENCRYPTION_FORWARD_SECURE) at external/com_github_google_quiche/quiche/quic/core/quic_framer.cc:2842
frame = {<quic::QuicInlinedFrame<quic::QuicStreamFrame>> = {<No data fields>}, type = quic::STREAM_FRAME, fin = false, data_length = 1194, stream_id = 6, data_buffer = 0x7f9f7227fe85 "[redacted]", offset = 5}
frame_type = 14
encoded_bytes = <optimized out>
connection_context = 0x455821998048
#15 0x00005643163d518b in quic::QuicFramer::ProcessIetfDataPacket (this=0x455821998070, encrypted_reader=<optimized out>, header=0x7f9f7227fc40, packet=..., decrypted_buffer=0x7f9f7227fe80 "[redacted]", buffer_length=1472) at external/com_github_google_quiche/quiche/quic/core/quic_framer.cc:1915
Hack a QUIC client implementation to send a request with headers referencing Qpack dynamic table and STOP_SENDING
frame. Meanwhile stop the Qpack encoder stream from writing any payload. Wait for all ACK
s to arrive, and then unblock Qpack encoder stream from writing.
Envoy users who have configured HTTP/3 downstream.
Summary
A crash was observed in
EnvoyQuicServerStream::OnInitialHeadersComplete()
with following call stack. It is a use-after-free caused by QUICHE continuing push request headers afterStopReading()
being called on the stream. As afterStopReading()
, the HCM'sActiveStream
might have already be destroyed and any up calls from QUICHE could potentially cause use after free.Details
The
EnvoyQuicServerStream
received a request header referencing a new Qpack dynamic table entry which hasn't arrived yet. And then it received aSTOP_SENDING
frame and thus calledStopReading()
to drop incoming request and called HCM reset callback. And the arrival of the Qpack dynamic table entry (QpackReceiveStream::OnDataAvailable()
in the crash stack) causes the request header to be decoded and callsEnvoyQuicServerStream::OnInitialHeadersComplete()
which accessing the destroyed HCMActiveStream
object.PoC
Hack a QUIC client implementation to send a request with headers referencing Qpack dynamic table and
STOP_SENDING
frame. Meanwhile stop the Qpack encoder stream from writing any payload. Wait for allACK
s to arrive, and then unblock Qpack encoder stream from writing.Impact
Envoy users who have configured HTTP/3 downstream.