diff --git a/src/aioquic/quic/connection.py b/src/aioquic/quic/connection.py index 796feec5c..1d72c1ede 100644 --- a/src/aioquic/quic/connection.py +++ b/src/aioquic/quic/connection.py @@ -93,6 +93,7 @@ STREAM_COUNT_MAX = 0x1000000000000000 UDP_HEADER_SIZE = 8 MAX_PENDING_RETIRES = 100 +MAX_PENDING_CRYPTO = 524288 NetworkAddress = Any @@ -1625,6 +1626,13 @@ def _handle_crypto_frame( ) stream = self._crypto_streams[context.epoch] + pending = offset + length - stream.receiver.starting_offset() + if pending > MAX_PENDING_CRYPTO: + raise QuicConnectionError( + error_code=QuicErrorCode.CRYPTO_BUFFER_EXCEEDED, + frame_type=frame_type, + reason_phrase="too much crypto buffering", + ) event = stream.receiver.handle_frame(frame) if event is not None: # pass data to TLS layer diff --git a/src/aioquic/quic/stream.py b/src/aioquic/quic/stream.py index 95f578b58..5b94446f4 100644 --- a/src/aioquic/quic/stream.py +++ b/src/aioquic/quic/stream.py @@ -48,6 +48,9 @@ def get_stop_frame(self) -> QuicStopSendingFrame: stream_id=self._stream_id, ) + def starting_offset(self) -> int: + return self._buffer_start + def handle_frame( self, frame: QuicStreamFrame ) -> Optional[events.StreamDataReceived]: diff --git a/tests/test_connection.py b/tests/test_connection.py index 47fb2db45..599d24635 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -10,6 +10,7 @@ from aioquic.quic import events from aioquic.quic.configuration import SMALLEST_MAX_DATAGRAM_SIZE, QuicConfiguration from aioquic.quic.connection import ( + MAX_PENDING_CRYPTO, STREAM_COUNT_MAX, NetworkAddress, QuicConnection, @@ -1364,6 +1365,34 @@ def test_handle_crypto_frame_over_largest_offset(self): cm.exception.reason_phrase, "offset + length cannot exceed 2^62 - 1" ) + def test_excessive_crypto_buffering(self): + with client_and_server() as (client, server): + # Client receives data that causes more than 512K of buffering; note that + # because the stream buffer is a single buffer and not a set of fragments, + # the total buffering size depends not on how much data is received, but + # how much buffering is needed. We send fragments of only 100 bytes + # at offsets 10000, 20000, 30000 etc. + highest_good_offset = 0 + with self.assertRaises(QuicConnectionError) as cm: + # We don't start at zero as we want to force buffering, not cause + # a TLS error. + for offset in range(10000, 1000000, 10000): + client._handle_crypto_frame( + client_receive_context(client), + QuicFrameType.CRYPTO, + Buffer( + data=encode_uint_var(offset) + + encode_uint_var(100) + + b"\x00" * 100 + ), + ) + highest_good_offset = offset + self.assertEqual( + cm.exception.error_code, QuicErrorCode.CRYPTO_BUFFER_EXCEEDED + ) + self.assertEqual(cm.exception.frame_type, QuicFrameType.CRYPTO) + self.assertEqual(highest_good_offset, (MAX_PENDING_CRYPTO // 10000) * 10000) + def test_handle_data_blocked_frame(self): with client_and_server() as (client, server): # client receives DATA_BLOCKED: 12345