CVE-2026-10882
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fnet/spdy/spdy_session_unittest.cc |
modified | |
HeadersSentDelegatenet/spdy/spdy_session_unittest.cc |
modified | |
ifnet/spdy/spdy_session_unittest.cc |
modified | |
ifnet/spdy/spdy_stream.cc |
modified |
Files Changed
net/spdy/spdy_session_unittest.ccnet/spdy/spdy_stream.cc
Patch
From 6810eeb018a99d750b574fe44d44573133d677e3 Mon Sep 17 00:00:00 2001
From: Kenichi Ishibashi <bashi@chromium.org>
Date: Mon, 20 Apr 2026 16:30:29 -0700
Subject: [PATCH] Fix potential crash in SpdyStream::IncreaseRecvWindowSize
This CL fixes a potential crash in SpdyStream::IncreaseRecvWindowSize
caused by accessing a destroyed object.
SpdyStream::IncreaseRecvWindowSize calls
session_->SendStreamWindowUpdate, which can trigger session draining and
stream destruction if the number of queued capped frames exceeds the
limit. If the stream is destroyed during this call, accessing member
variables like unacked_recv_window_bytes_ after the call results in
accessing a invalid memory.
This CL adds a base::WeakPtr check after SendStreamWindowUpdate to
ensure the stream is still alive before proceeding.
Bug: 503420443
Test: SpdySessionTest.WindowUpdateExceedsCappedFramesLimit
Change-Id: Idb1f5252c9e05ef24701647ad2e70fd386547640
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7775828
Reviewed-by: Adam Rice <ricea@chromium.org>
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1617854}
---
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
index 86d5502d..91c548b1 100644
--- a/net/spdy/spdy_session_unittest.cc
+++ b/net/spdy/spdy_session_unittest.cc
@@ -2686,6 +2686,124 @@
EXPECT_TRUE(data.AllReadDataConsumed());
}
+// Tests that the session drains when the number of queued capped frames
+// exceeds the limit during a window update. This is a regression test for
+// crbug.com/503420443.
+TEST_F(SpdySessionTest, WindowUpdateExceedsCappedFramesLimit) {
+ // Set the capped frames limit to 1.
+ session_deps_.session_max_queued_capped_frames = 1;
+
+ spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 1, LOWEST));
+ spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 3, LOWEST));
+ spdy::SpdySerializedFrame req3(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 5, LOWEST));
+
+ spdy::SpdySerializedFrame rst1(
+ spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
+ spdy::SpdySerializedFrame rst2(
+ spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
+
+ spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(
+ 0, spdy::ERROR_CODE_PROTOCOL_ERROR, "Exceeded max queued capped frames"));
+
+ MockWrite writes[] = {
+ CreateMockWrite(req1, 0), CreateMockWrite(req2, 1),
+ CreateMockWrite(req3, 2), CreateMockWrite(rst1, 3),
+ CreateMockWrite(rst2, 4), CreateMockWrite(goaway, 5),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_IO_PENDING, 6), MockRead(ASYNC, 0, 7) // EOF
+ };
+
+ SequencedSocketData data(reads, writes);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ AddSSLSocketData();
+
+ CreateNetworkSession();
+ CreateSpdySession();
+
+ // Create three streams.
+ base::WeakPtr<SpdyStream> stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream1);
+
+ base::WeakPtr<SpdyStream> stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream2);
+
+ base::WeakPtr<SpdyStream> stream3 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream3);
+
+ test::StreamDelegateDoNothing delegate1(stream1);
+ stream1->SetDelegate(&delegate1);
+
+ test::StreamDelegateDoNothing delegate2(stream2);
+ stream2->SetDelegate(&delegate2);
+
+ // Custom delegate to wait for headers sent on stream3 (last one).
+ class HeadersSentDelegate : public test::StreamDelegateDoNothing {
+ public:
+ HeadersSentDelegate(const base::WeakPtr<SpdyStream>& stream,
+ base::OnceClosure quit_closure)
+ : StreamDelegateDoNothing(stream),
+ quit_closure_(std::move(quit_closure)) {}
+
+ void OnHeadersSent() override {
+ test::StreamDelegateDoNothing::OnHeadersSent();
+ if (quit_closure_) {
+ std::move(quit_closure_).Run();
+ }
+ }
+
+ private:
+ base::OnceClosure quit_closure_;
+ };
+
+ base::RunLoop run_loop;
+ HeadersSentDelegate delegate3(stream3, run_loop.QuitClosure());
+ stream3->SetDelegate(&delegate3);
+
+ quiche::HttpHeaderBlock headers1(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND);
+
+ quiche::HttpHeaderBlock headers2(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND);
+
+ quiche::HttpHeaderBlock headers3(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream3->SendRequestHeaders(std::move(headers3), NO_MORE_DATA_TO_SEND);
+
+ // Wait for headers to be sent for all streams.
+ run_loop.Run();
+
+ EXPECT_EQ(1u, stream1->stream_id());
+ EXPECT_EQ(3u, stream2->stream_id());
+ EXPECT_EQ(5u, stream3->stream_id());
+
+ // Cancel stream1 and stream2 to enqueue 2 capped frames (RST_STREAM).
+ // This fills the queue up to count 2 (limit is 1).
+ stream1->Cancel(ERR_ABORTED);
+ stream2->Cancel(ERR_ABORTED);
+
+ // Trigger a window update on stream3. This will try to enqueue a third
+ // capped frame (WINDOW_UPDATE). Since count (2) > limit (1), this should
+ // trigger session draining.
+ stream3->IncreaseRecvWindowSize(40000);
+
+ // Wait for stream3 to close due to session drain.
+ EXPECT_THAT(delegate3.WaitForClose(), IsError(ERR_CONNECTION_CLOSED));
+}
+
TEST_F(SpdySessionTest, VerifyDomainAuthentication) {
SequencedSocketData data;
session_deps_.socket_factory->AddSocketDataProvider(&data);
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 6610b2b..8ddbe24 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -276,8 +276,15 @@
if (unacked_recv_window_bytes_ > max_recv_window_size_ / 2 ||
elapsed >= session_->TimeToBufferSmallWindowUpdates()) {
last_recv_window_update_ = base::TimeTicks::Now();
+ // SendStreamWindowUpdate() can result in session draining and stream
+ // destruction if the number of queued capped frames exceeds the limit.
+ // Check weak_this after the call to detect this.
+ base::WeakPtr<SpdyStream> weak_this = weak_ptr_factory_.GetWeakPtr();
session_->SendStreamWindowUpdate(
stream_id_, static_cast<uint32_t>(unacked_recv_window_bytes_));
+ if (!weak_this) {
+ return;
+ }
unacked_recv_window_bytes_ = 0;
}
}
Regression Test / PoC
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
index 86d5502d..91c548b1 100644
--- a/net/spdy/spdy_session_unittest.cc
+++ b/net/spdy/spdy_session_unittest.cc
@@ -2686,6 +2686,124 @@
EXPECT_TRUE(data.AllReadDataConsumed());
}
+// Tests that the session drains when the number of queued capped frames
+// exceeds the limit during a window update. This is a regression test for
+// crbug.com/503420443.
+TEST_F(SpdySessionTest, WindowUpdateExceedsCappedFramesLimit) {
+ // Set the capped frames limit to 1.
+ session_deps_.session_max_queued_capped_frames = 1;
+
+ spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 1, LOWEST));
+ spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 3, LOWEST));
+ spdy::SpdySerializedFrame req3(spdy_util_.ConstructSpdyGet(
+ base::span<const std::string_view>(), 5, LOWEST));
+
+ spdy::SpdySerializedFrame rst1(
+ spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
+ spdy::SpdySerializedFrame rst2(
+ spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
+
+ spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(
+ 0, spdy::ERROR_CODE_PROTOCOL_ERROR, "Exceeded max queued capped frames"));
+
+ MockWrite writes[] = {
+ CreateMockWrite(req1, 0), CreateMockWrite(req2, 1),
+ CreateMockWrite(req3, 2), CreateMockWrite(rst1, 3),
+ CreateMockWrite(rst2, 4), CreateMockWrite(goaway, 5),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_IO_PENDING, 6), MockRead(ASYNC, 0, 7) // EOF
+ };
+
+ SequencedSocketData data(reads, writes);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ AddSSLSocketData();
+
+ CreateNetworkSession();
+ CreateSpdySession();
+
+ // Create three streams.
+ base::WeakPtr<SpdyStream> stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream1);
+
+ base::WeakPtr<SpdyStream> stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream2);
+
+ base::WeakPtr<SpdyStream> stream3 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+ test_url_, LOWEST, NetLogWithSource());
+ ASSERT_TRUE(stream3);
+
+ test::StreamDelegateDoNothing delegate1(stream1);
+ stream1->SetDelegate(&delegate1);
+
+ test::StreamDelegateDoNothing delegate2(stream2);
+ stream2->SetDelegate(&delegate2);
+
+ // Custom delegate to wait for headers sent on stream3 (last one).
+ class HeadersSentDelegate : public test::StreamDelegateDoNothing {
+ public:
+ HeadersSentDelegate(const base::WeakPtr<SpdyStream>& stream,
+ base::OnceClosure quit_closure)
+ : StreamDelegateDoNothing(stream),
+ quit_closure_(std::move(quit_closure)) {}
+
+ void OnHeadersSent() override {
+ test::StreamDelegateDoNothing::OnHeadersSent();
+ if (quit_closure_) {
+ std::move(quit_closure_).Run();
+ }
+ }
+
+ private:
+ base::OnceClosure quit_closure_;
+ };
+
+ base::RunLoop run_loop;
+ HeadersSentDelegate delegate3(stream3, run_loop.QuitClosure());
+ stream3->SetDelegate(&delegate3);
+
+ quiche::HttpHeaderBlock headers1(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND);
+
+ quiche::HttpHeaderBlock headers2(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND);
+
+ quiche::HttpHeaderBlock headers3(
+ spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+ stream3->SendRequestHeaders(std::move(headers3), NO_MORE_DATA_TO_SEND);
+
+ // Wait for headers to be sent for all streams.
+ run_loop.Run();
+
+ EXPECT_EQ(1u, stream1->stream_id());
+ EXPECT_EQ(3u, stream2->stream_id());
+ EXPECT_EQ(5u, stream3->stream_id());
+
+ // Cancel stream1 and stream2 to enqueue 2 capped frames (RST_STREAM).
+ // This fills the queue up to count 2 (limit is 1).
+ stream1->Cancel(ERR_ABORTED);
+ stream2->Cancel(ERR_ABORTED);
+
+ // Trigger a window update on stream3. This will try to enqueue a third
+ // capped frame (WINDOW_UPDATE). Since count (2) > limit (1), this should
+ // trigger session draining.
+ stream3->IncreaseRecvWindowSize(40000);
+
+ // Wait for stream3 to close due to session drain.
+ EXPECT_THAT(delegate3.WaitForClose(), IsError(ERR_CONNECTION_CLOSED));
+}
+
TEST_F(SpdySessionTest, VerifyDomainAuthentication) {
SequencedSocketData data;
session_deps_.socket_factory->AddSocketDataProvider(&data);
Original Bug Report
Missing lifetime check in SpdyStream::IncreaseRecvWindowSize leads to use-after-free in Network Service
Missing lifetime check in SpdyStream::IncreaseRecvWindowSize leads to use-after-free in Network Service
Summary
A use-after-free write exists in the HTTP/2 stack of Chromium’s Network Service. When a malicious HTTP/2 server sends a burst of control frames followed by a padded DATA frame, the resulting WINDOW_UPDATE enqueue can synchronously trigger session draining, which destroys the SpdyStream object while SpdyStream::IncreaseRecvWindowSize is still on the call stack. The function then writes to a member of the freed object. This affects all desktop and mobile platforms. No special GPU or hardware requirements apply.
Bisect
Introducing Commit: 410676ab9660a0271949e043ddb8678a6d18b097
- Date: 2019-08-13
- Author: David Schinazi
- Review: https://chromium-review.googlesource.com/c/chromium/src/+/1752387
This commit added the capped-frame overflow check in EnqueueSessionWrite as a mitigation for CVE-2019-9512, CVE-2019-9514, and CVE-2019-9515. Before this commit, EnqueueSessionWrite never synchronously destroyed streams, so the missing lifetime check in IncreaseRecvWindowSize was not reachable. The mitigation introduced a new code path where DoDrainSession fires synchronously from within EnqueueSessionWrite, creating the UAF.
Root Cause
SpdyStream::IncreaseRecvWindowSize calls session_->SendStreamWindowUpdate and then immediately writes to a member field without verifying that the stream is still alive:
// net/spdy/spdy_stream.cc
void SpdyStream::IncreaseRecvWindowSize(int32_t delta_window_size) {
// ...
if (unacked_recv_window_bytes_ > max_recv_window_size_ / 2 ||
elapsed >= session_->TimeToBufferSmallWindowUpdates()) {
last_recv_window_update_ = base::TimeTicks::Now();
session_->SendStreamWindowUpdate(
stream_id_, static_cast<uint32_t>(unacked_recv_window_bytes_));
unacked_recv_window_bytes_ = 0; // UAF write: stream may have been freed
}
}
The call to SendStreamWindowUpdate chains through SendWindowUpdateFrame into EnqueueSessionWrite. EnqueueSessionWrite contains a guard that synchronously drains the session when the capped-frame queue exceeds a configurable limit:
// net/spdy/spdy_session.cc
void SpdySession::EnqueueSessionWrite(...) {
// ...
if (write_queue_.num_queued_capped_frames() >
session_max_queued_capped_frames_) {
DoDrainSession(ERR_CONNECTION_CLOSED, "Exceeded max queued capped frames");
return;
}
// ...
}
DoDrainSession calls StartGoingAway, which iterates over all active streams and destroys them via CloseActiveStreamIterator and DeleteStream. The SpdyStream whose IncreaseRecvWindowSize is on the call stack is among those destroyed. When control returns to IncreaseRecvWindowSize, the assignment unacked_recv_window_bytes_ = 0 writes to freed heap memory.
The inconsistency is visible within the same file. SpdyStream::OnPaddingConsumed does capture a WeakPtr before calling DecreaseRecvWindowSize and checks it before proceeding, but this guard does not cover the IncreaseRecvWindowSize call that follows:
// net/spdy/spdy_stream.cc
void SpdyStream::OnPaddingConsumed(size_t len) {
base::WeakPtr<SpdyStream> weak_this = GetWeakPtr();
DecreaseRecvWindowSize(static_cast<int32_t>(len));
if (!weak_this)
return;
IncreaseRecvWindowSize(static_cast<int32_t>(len));
// No check after IncreaseRecvWindowSize; if it triggers DoDrainSession,
// `this` is already freed before we return.
}
Similarly, SpdyStream::OnDataReceived protects itself with a WeakPtr around DecreaseRecvWindowSize but the consume-callback path through OnReadBufferConsumed into IncreaseRecvWindowSize lacks the same protection.
The triggering sequence works as follows. SpdySession::OnStreamPadding, called during frame decoding inside DoReadComplete, first invokes the session-level IncreaseRecvWindowSize and then calls OnPaddingConsumed on the stream:
// net/spdy/spdy_session.cc
void SpdySession::OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) {
DecreaseRecvWindowSize(static_cast<int32_t>(len));
IncreaseRecvWindowSize(static_cast<int32_t>(len)); // session-level
auto it = active_streams_.find(stream_id);
if (it == active_streams_.end())
return;
it->second->OnPaddingConsumed(len); // stream-level
}
An attacker controlling an HTTP/2 server can exploit the differential thresholds between session-level and stream-level WINDOW_UPDATE to ensure the session-level call does not trigger draining while the stream-level call does. The session-level WINDOW_UPDATE threshold is half of 15 MB (7.5 MB), while the stream-level threshold is half of 6 MB (3 MB). Alternatively, both levels have a time-based fallback that fires after kDefaultTimeToBufferSmallWindowUpdates (5 seconds) of inactivity. By sending exactly N SETTINGS frames (where N equals the configured session_max_queued_capped_frames limit) followed by a single padded DATA frame after the 5-second window has elapsed, the attacker arranges for the session WINDOW_UPDATE to pass the overflow check (N > N is false), bringing the queue to N+1, and then the stream WINDOW_UPDATE to fail the check (N+1 > N is true), triggering DoDrainSession.
Because all frames arrive within a single DoReadComplete call (the entire burst is under kReadBufferSize of 8192 bytes), the write loop never has an opportunity to drain any of the queued SETTINGS ACK frames. The capped-frame count is guaranteed to be at N when the critical WINDOW_UPDATE is enqueued.
The session_max_queued_capped_frames parameter defaults to 10000 and is configurable through the HTTP2 field trial (“spdy_session_max_queued_capped_frames”). The vulnerability exists regardless of the configured value; only the number of SETTINGS frames the attacker must send changes. With the default value of 10000, the attacker sends 10001 SETTINGS frames (approximately 90 KB), which may span multiple DoReadComplete iterations due to the 8 KB read buffer, but the write loop is unable to keep pace with the read loop’s frame processing, allowing the queue to grow past the limit. At default settings the attacker can also apply TCP-level backpressure by not reading from its end of the connection, stalling the client’s outbound socket and preventing any queued ACKs from being flushed. For stable reproduction in the attached PoC, the limit is set to 100 via the field trial so that the entire attack burst (100 SETTINGS plus one padded DATA frame, 1166 bytes total) fits within a single 8 KB read and triggers deterministically in one DoReadComplete call.
Reproduce
Tested on commit effccce563102fd8315819caf7677f68394be7af (Linux x86_64). No source modifications required.
Start the malicious HTTP/2 server from the issue directory:
python3 server.py
Launch Chrome in a separate terminal. The field trial parameter lowers the capped-frame limit from the default 10000 to 100 for stable, deterministic reproduction; the underlying bug is identical at any limit value.
ASAN_OPTIONS=detect_odr_violation=0 ~/chromium/src/out/asan-release/chrome \
--no-sandbox --disable-gpu --ignore-certificate-errors \
--user-data-dir=/tmp/poc-$(date +%s) \
--headless=new \
--force-fieldtrials=HTTP2/Experiment/ \
--force-fieldtrial-params="HTTP2.Experiment:spdy_session_max_queued_capped_frames/100" \
https://localhost:8443/
The Network Service IO thread crashes with a heap-use-after-free within approximately 8 seconds.
==1795377==ERROR: AddressSanitizer: heap-use-after-free on address 0x7c827e119d14 at pc 0x7f12fcc0c1e9 bp 0x7b1271590750 sp 0x7b1271590748
WRITE of size 4 at 0x7c827e119d14 thread T7 (Chrome_ChildIOT)
#0 0x7f12fcc0c1e8 in net::SpdyStream::IncreaseRecvWindowSize(int) net/spdy/spdy_stream.cc:289:32
#1 0x7f12fcc10913 in net::SpdyStream::OnPaddingConsumed(unsigned long) net/spdy/spdy_stream.cc:497:3
#2 0x7f12fcbd6cd4 in net::SpdySession::OnStreamPadding(unsigned int, unsigned long) net/spdy/spdy_session.cc:3032:15
#3 0x7f12fb3c9537 in http2::FrameDecoderState::ReadPadLength(http2::DecodeBuffer*, bool) net/third_party/quiche/src/quiche/http2/decoder/frame_decoder_state.cc:29:21
#4 0x7f12fb3ce259 in http2::DataPayloadDecoder::ResumeDecodingPayload(http2::FrameDecoderState*, http2::DecodeBuffer*) net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/data_payload_decoder.cc:98:23
#5 0x7f12fb3ca47f in http2::Http2FrameDecoder::StartDecodingPayload(http2::DecodeBuffer*) net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder.cc:293:32
#6 0x7f12fb3c9e29 in http2::Http2FrameDecoder::DecodeFrame(http2::DecodeBuffer*) net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder.cc:57:16
#7 0x7f12fb3993d5 in http2::Http2DecoderAdapter::ProcessInputFrame(char const*, unsigned long) net/third_party/quiche/src/quiche/http2/core/http2_frame_decoder_adapter.cc:800:40
#8 0x7f12fb399206 in http2::Http2DecoderAdapter::ProcessInput(char const*, unsigned long) net/third_party/quiche/src/quiche/http2/core/http2_frame_decoder_adapter.cc:272:30
#9 0x7f12fcbcd528 in net::SpdySession::DoReadComplete(int) net/spdy/spdy_session.cc:2063:53
#10 0x7f12fcbcbed6 in net::SpdySession::DoReadLoop(net::SpdySession::ReadState, int) net/spdy/spdy_session.cc:1981:18
#11 0x7f12fcbc87b0 in net::SpdySession::PumpReadLoop(net::SpdySession::ReadState, int) net/spdy/spdy_session.cc:1957:17
0x7c827e119d14 is located 148 bytes inside of 648-byte region [0x7c827e119c80,0x7c827e119f08)
freed by thread T7 (Chrome_ChildIOT) here:
#0 0x560911f23ec2 in operator delete(void*, unsigned long)
#1 0x7f12fcbc19a5 in net::SpdySession::CloseActiveStreamIterator(...) net/spdy/spdy_session.cc:1855:3
#2 0x7f12fcbc3e7b in net::SpdySession::StartGoingAway(unsigned int, net::Error) net/spdy/spdy_session.cc:1396:5
#3 0x7f12fcbbd03f in net::SpdySession::DoDrainSession(...) net/spdy/spdy_session.cc:2744:5
#4 0x7f12fcbcafa6 in net::SpdySession::EnqueueSessionWrite(...) net/spdy/spdy_session.cc:2562:5
#5 0x7f12fcbc354a in net::SpdySession::SendWindowUpdateFrame(...) net/spdy/spdy_session.cc:2473:3
#6 0x7f12fcbc2f8c in net::SpdySession::SendStreamWindowUpdate(...) net/spdy/spdy_session.cc:1349:3
#7 0x7f12fcc0bf49 in net::SpdyStream::IncreaseRecvWindowSize(int) net/spdy/spdy_stream.cc:284:15
#8 0x7f12fcc10913 in net::SpdyStream::OnPaddingConsumed(unsigned long) net/spdy/spdy_stream.cc:497:3
#9 0x7f12fcbd6cd4 in net::SpdySession::OnStreamPadding(unsigned int, unsigned long) net/spdy/spdy_session.cc:3032:15
previously allocated by thread T7 (Chrome_ChildIOT) here:
#0 0x560911f232bd in operator new(unsigned long)
#1 0x7f12fcbc8c3e in net::SpdySession::CreateStream(...) gen/third_party/libc++/src/include/__memory/unique_ptr.h:756:26
References
- net/spdy/spdy_stream.cc: IncreaseRecvWindowSize
- net/spdy/spdy_stream.cc: OnPaddingConsumed
- net/spdy/spdy_session.cc: EnqueueSessionWrite
- net/spdy/spdy_session.cc: OnStreamPadding
- net/spdy/spdy_session.cc: DoDrainSession
- Introducing commit (HTTP2 DoS Mitigations)
Credit
Please use c6eed09fc8b174b0f3eebedcceb1e792 as the credit for this vulnerability. Thank you.
- https://chromium-review.googlesource.com/c/chromium/src/+/1752387
- https://localhost:8443/
- https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=2544
- https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=2686
- https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=3013
- https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;l=250
- https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;l=487