Do not accept fin that would decrease the stream's final offset.
gfe-relnote: protected by gfe2_reloadable_flag_quic_no_decrease_in_final_offset
PiperOrigin-RevId: 276115511
Change-Id: I6c6fa7e6f8a6374aceb7ef0c19756ea3928479c9
diff --git a/quic/core/quic_stream_sequencer.cc b/quic/core/quic_stream_sequencer.cc
index 8f84fb9..ad07830 100644
--- a/quic/core/quic_stream_sequencer.cc
+++ b/quic/core/quic_stream_sequencer.cc
@@ -9,6 +9,7 @@
#include <string>
#include <utility>
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_stream.h"
#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
@@ -26,6 +27,7 @@
QuicStreamSequencer::QuicStreamSequencer(StreamInterface* quic_stream)
: stream_(quic_stream),
buffered_frames_(kStreamReceiveWindowLimit),
+ highest_offset_(0),
close_offset_(std::numeric_limits<QuicStreamOffset>::max()),
blocked_(false),
num_frames_received_(0),
@@ -66,6 +68,7 @@
void QuicStreamSequencer::OnFrameData(QuicStreamOffset byte_offset,
size_t data_len,
const char* data_buffer) {
+ highest_offset_ = std::max(highest_offset_, byte_offset + data_len);
const size_t previous_readable_bytes = buffered_frames_.ReadableBytes();
size_t bytes_written;
std::string error_details;
@@ -123,10 +126,33 @@
// If there is a scheduled close, the new offset should match it.
if (close_offset_ != kMaxOffset && offset != close_offset_) {
- stream_->Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+ if (!GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+ stream_->Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+ return false;
+ }
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_no_decrease_in_final_offset, 1, 2);
+ stream_->CloseConnectionWithDetails(
+ QUIC_STREAM_SEQUENCER_INVALID_STATE,
+ QuicStrCat("Stream ", stream_->id(),
+ " received new final offset: ", offset,
+ ", which is different from close offset: ", close_offset_));
return false;
}
+ // The final offset should be no less than the highest offset that is
+ // received.
+ if (GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_no_decrease_in_final_offset, 2, 2);
+ if (offset < highest_offset_) {
+ stream_->CloseConnectionWithDetails(
+ QUIC_STREAM_SEQUENCER_INVALID_STATE,
+ QuicStrCat(
+ "Stream ", stream_->id(), " received fin with offset: ", offset,
+ ", which reduces current highest offset: ", highest_offset_));
+ return false;
+ }
+ }
+
close_offset_ = offset;
MaybeCloseStream();
diff --git a/quic/core/quic_stream_sequencer.h b/quic/core/quic_stream_sequencer.h
index a735a1e..8f8a6a0 100644
--- a/quic/core/quic_stream_sequencer.h
+++ b/quic/core/quic_stream_sequencer.h
@@ -11,6 +11,7 @@
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
namespace quic {
@@ -182,6 +183,9 @@
// Stores received data in offset order.
QuicStreamSequencerBuffer buffered_frames_;
+ // The highest offset that is received so far.
+ QuicStreamOffset highest_offset_;
+
// The offset, if any, we got a stream termination for. When this many bytes
// have been processed, the sequencer will be closed.
QuicStreamOffset close_offset_;
diff --git a/quic/core/quic_stream_sequencer_test.cc b/quic/core/quic_stream_sequencer_test.cc
index 8ad845b..15484d8 100644
--- a/quic/core/quic_stream_sequencer_test.cc
+++ b/quic/core/quic_stream_sequencer_test.cc
@@ -15,6 +15,7 @@
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
@@ -374,12 +375,15 @@
OnFinFrame(3, "");
EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
- EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ if (!GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+ EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ } else {
+ EXPECT_CALL(stream_, CloseConnectionWithDetails(
+ QUIC_STREAM_SEQUENCER_INVALID_STATE,
+ "Stream 1 received new final offset: 1, which is "
+ "different from close offset: 3"));
+ }
OnFinFrame(1, "");
- EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
-
- OnFinFrame(3, "");
- EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
}
class QuicSequencerRandomTest : public QuicStreamSequencerTest {
@@ -757,13 +761,32 @@
// Regression test for https://crbug.com/992486.
TEST_F(QuicStreamSequencerTest, CorruptFinFrames) {
SetQuicReloadableFlag(quic_no_stream_data_after_reset, true);
- EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ if (!GetQuicReloadableFlag(quic_no_decrease_in_final_offset)) {
+ EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ } else {
+ EXPECT_CALL(stream_, CloseConnectionWithDetails(
+ QUIC_STREAM_SEQUENCER_INVALID_STATE,
+ "Stream 1 received new final offset: 1, which is "
+ "different from close offset: 2"));
+ }
OnFinFrame(2u, "");
OnFinFrame(0u, "a");
EXPECT_FALSE(sequencer_->HasBytesToRead());
}
+// Regression test for crbug.com/1015693
+TEST_F(QuicStreamSequencerTest, ReceiveFinLessThanHighestOffset) {
+ SetQuicReloadableFlag(quic_no_decrease_in_final_offset, true);
+ EXPECT_CALL(stream_, OnDataAvailable()).Times(1);
+ EXPECT_CALL(stream_, CloseConnectionWithDetails(
+ QUIC_STREAM_SEQUENCER_INVALID_STATE,
+ "Stream 1 received fin with offset: 0, which "
+ "reduces current highest offset: 3"));
+ OnFrame(0u, "abc");
+ OnFinFrame(0u, "");
+}
+
} // namespace
} // namespace test
} // namespace quic