Skip to content

Crash in EnvoyQuicServerStream::OnInitialHeadersComplete()

Moderate
phlax published GHSA-mgxp-7hhp-8299 Jun 4, 2024

Package

QUIC (Envoyproxy)

Affected versions

< 1.30.2

Patched versions

1.30.2, 1.29.5, 1.28.4, 1.27.6

Description

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 after StopReading() being called on the stream. As after StopReading(), the HCM's ActiveStream might have already be destroyed and any up calls from QUICHE could potentially cause use after free.

Details

#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

The EnvoyQuicServerStream received a request header referencing a new Qpack dynamic table entry which hasn't arrived yet. And then it received a STOP_SENDING frame and thus called StopReading() 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 calls EnvoyQuicServerStream::OnInitialHeadersComplete() which accessing the destroyed HCM ActiveStream 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 all ACKs to arrive, and then unblock Qpack encoder stream from writing.

Impact

Envoy users who have configured HTTP/3 downstream.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2024-32974

Weaknesses

Credits