diff -Nru python-eventlet-0.25.1/debian/changelog python-eventlet-0.25.1/debian/changelog --- python-eventlet-0.25.1/debian/changelog 2020-07-02 07:30:30.000000000 +0000 +++ python-eventlet-0.25.1/debian/changelog 2021-05-11 14:36:49.000000000 +0000 @@ -1,3 +1,13 @@ +python-eventlet (0.25.1-2ubuntu1.1) focal-security; urgency=medium + + * SECURITY UPDATE: Denial of service + - debian/patches/CVE-2021-21419.patch: limit maximum uncompressed + frame length to 8MiB in eventlet/websocket.py, + tests/websocket_new_test.py. + - CVE-2021-21419 + + -- Leonidas Da Silva Barbosa Tue, 11 May 2021 11:36:49 -0300 + python-eventlet (0.25.1-2ubuntu1) focal; urgency=medium * d/p/ssl-compat.patch: Fix compatibility with SSLContext base diff -Nru python-eventlet-0.25.1/debian/patches/CVE-2021-21419.patch python-eventlet-0.25.1/debian/patches/CVE-2021-21419.patch --- python-eventlet-0.25.1/debian/patches/CVE-2021-21419.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-eventlet-0.25.1/debian/patches/CVE-2021-21419.patch 2021-05-11 14:36:43.000000000 +0000 @@ -0,0 +1,207 @@ +From 1412f5e4125b4313f815778a1acb4d3336efcd07 Mon Sep 17 00:00:00 2001 +From: Onno Kortmann +Date: Thu, 1 Apr 2021 16:15:47 +0200 +Subject: [PATCH] websocket: Limit maximum uncompressed frame length to 8MiB + +This fixes a memory exhaustion DOS attack vector. + +References: GHSA-9p9m-jm8w-94p2 +https://github.com/eventlet/eventlet/security/advisories/GHSA-9p9m-jm8w-94p2 +--- + eventlet/websocket.py | 34 +++++++++++++++++---- + tests/websocket_new_test.py | 59 ++++++++++++++++++++++++++++++++++++- + 2 files changed, 86 insertions(+), 7 deletions(-) + +Index: python-eventlet-0.30.0/eventlet/websocket.py +=================================================================== +--- python-eventlet-0.30.0.orig/eventlet/websocket.py ++++ python-eventlet-0.30.0/eventlet/websocket.py +@@ -38,6 +38,7 @@ for _mod in ('wsaccel.utf8validator', 'a + break + + ACCEPTABLE_CLIENT_ERRORS = set((errno.ECONNRESET, errno.EPIPE)) ++DEFAULT_MAX_FRAME_LENGTH = 8 << 20 + + __all__ = ["WebSocketWSGI", "WebSocket"] + PROTOCOL_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' +@@ -75,14 +76,20 @@ class WebSocketWSGI(object): + :class:`WebSocket`. To close the socket, simply return from the + function. Note that the server will log the websocket request at + the time of closure. ++ ++ An optional argument max_frame_length can be given, which will set the ++ maximum incoming *uncompressed* payload length of a frame. By default, this ++ is set to 8MiB. Note that excessive values here might create a DOS attack ++ vector. + """ + +- def __init__(self, handler): ++ def __init__(self, handler, max_frame_length=DEFAULT_MAX_FRAME_LENGTH): + self.handler = handler + self.protocol_version = None + self.support_legacy_versions = True + self.supported_protocols = [] + self.origin_checker = None ++ self.max_frame_length = max_frame_length + + @classmethod + def configured(cls, +@@ -323,7 +330,8 @@ class WebSocketWSGI(object): + sock.sendall(b'\r\n'.join(handshake_reply) + b'\r\n\r\n') + return RFC6455WebSocket(sock, environ, self.protocol_version, + protocol=negotiated_protocol, +- extensions=parsed_extensions) ++ extensions=parsed_extensions, ++ max_frame_length=self.max_frame_length) + + def _extract_number(self, value): + """ +@@ -502,7 +510,8 @@ class ProtocolError(ValueError): + + + class RFC6455WebSocket(WebSocket): +- def __init__(self, sock, environ, version=13, protocol=None, client=False, extensions=None): ++ def __init__(self, sock, environ, version=13, protocol=None, client=False, extensions=None, ++ max_frame_length=DEFAULT_MAX_FRAME_LENGTH): + super(RFC6455WebSocket, self).__init__(sock, environ, version) + self.iterator = self._iter_frames() + self.client = client +@@ -511,6 +520,8 @@ class RFC6455WebSocket(WebSocket): + + self._deflate_enc = None + self._deflate_dec = None ++ self.max_frame_length = max_frame_length ++ self._remote_close_data = None + + class UTF8Decoder(object): + def __init__(self): +@@ -582,12 +593,13 @@ class RFC6455WebSocket(WebSocket): + return data + + class Message(object): +- def __init__(self, opcode, decoder=None, decompressor=None): ++ def __init__(self, opcode, max_frame_length, decoder=None, decompressor=None): + self.decoder = decoder + self.data = [] + self.finished = False + self.opcode = opcode + self.decompressor = decompressor ++ self.max_frame_length = max_frame_length + + def push(self, data, final=False): + self.finished = final +@@ -596,7 +608,12 @@ class RFC6455WebSocket(WebSocket): + def getvalue(self): + data = b"".join(self.data) + if not self.opcode & 8 and self.decompressor: +- data = self.decompressor.decompress(data + b'\x00\x00\xff\xff') ++ data = self.decompressor.decompress(data + b"\x00\x00\xff\xff", self.max_frame_length) ++ if self.decompressor.unconsumed_tail: ++ raise FailedConnectionError( ++ 1009, ++ "Incoming compressed frame exceeds length limit of {} bytes.".format(self.max_frame_length)) ++ + if self.decoder: + data = self.decoder.decode(data, self.finished) + return data +@@ -610,6 +627,7 @@ class RFC6455WebSocket(WebSocket): + + def _handle_control_frame(self, opcode, data): + if opcode == 8: # connection close ++ self._remote_close_data = data + if not data: + status = 1000 + elif len(data) > 1: +@@ -709,13 +727,17 @@ class RFC6455WebSocket(WebSocket): + length = struct.unpack('!H', recv(2))[0] + elif length == 127: + length = struct.unpack('!Q', recv(8))[0] ++ ++ if length > self.max_frame_length: ++ raise FailedConnectionError(1009, "Incoming frame of {} bytes is above length limit of {} bytes.".format( ++ length, self.max_frame_length)) + if masked: + mask = struct.unpack('!BBBB', recv(4)) + received = 0 + if not message or opcode & 8: + decoder = self.UTF8Decoder() if opcode == 1 else None + decompressor = self._get_permessage_deflate_dec(rsv1) +- message = self.Message(opcode, decoder=decoder, decompressor=decompressor) ++ message = self.Message(opcode, self.max_frame_length, decoder=decoder, decompressor=decompressor) + if not length: + message.push(b'', final=finished) + else: +Index: python-eventlet-0.30.0/tests/websocket_new_test.py +=================================================================== +--- python-eventlet-0.30.0.orig/tests/websocket_new_test.py ++++ python-eventlet-0.30.0/tests/websocket_new_test.py +@@ -30,7 +30,12 @@ def handle(ws): + else: + ws.close() + +-wsapp = websocket.WebSocketWSGI(handle) ++ ++# Set a lower limit of DEFAULT_MAX_FRAME_LENGTH for testing, as ++# sending an 8MiB frame over the loopback interface can trigger a ++# timeout. ++TEST_MAX_FRAME_LENGTH = 50000 ++wsapp = websocket.WebSocketWSGI(handle, max_frame_length=TEST_MAX_FRAME_LENGTH) + + + class TestWebSocket(tests.wsgi_test._TestBase): +@@ -534,3 +539,55 @@ class TestWebSocketWithCompression(tests + + ws.close() + eventlet.sleep(0.01) ++ ++ def test_large_frame_size_compressed_13(self): ++ # Test fix for GHSA-9p9m-jm8w-94p2 ++ extensions_string = 'permessage-deflate' ++ extensions = {'permessage-deflate': { ++ 'client_no_context_takeover': False, ++ 'server_no_context_takeover': False}} ++ ++ sock = eventlet.connect(self.server_addr) ++ sock.sendall(six.b(self.connect % extensions_string)) ++ sock.recv(1024) ++ ws = websocket.RFC6455WebSocket(sock, {}, client=True, extensions=extensions) ++ ++ should_still_fit = b"x" * TEST_MAX_FRAME_LENGTH ++ one_too_much = should_still_fit + b"x" ++ ++ # send just fitting frame twice to make sure they are fine independently ++ ws.send(should_still_fit) ++ assert ws.wait() == should_still_fit ++ ws.send(should_still_fit) ++ assert ws.wait() == should_still_fit ++ ws.send(one_too_much) ++ ++ res = ws.wait() ++ assert res is None # socket closed ++ # TODO: The websocket currently sents compressed control frames, which contradicts RFC7692. ++ # Renable the following assert after that has been fixed. ++ # assert ws._remote_close_data == b"\x03\xf1Incoming compressed frame is above length limit." ++ eventlet.sleep(0.01) ++ ++ def test_large_frame_size_uncompressed_13(self): ++ # Test fix for GHSA-9p9m-jm8w-94p2 ++ sock = eventlet.connect(self.server_addr) ++ sock.sendall(six.b(self.connect)) ++ sock.recv(1024) ++ ws = websocket.RFC6455WebSocket(sock, {}, client=True) ++ ++ should_still_fit = b"x" * TEST_MAX_FRAME_LENGTH ++ one_too_much = should_still_fit + b"x" ++ ++ # send just fitting frame twice to make sure they are fine independently ++ ws.send(should_still_fit) ++ assert ws.wait() == should_still_fit ++ ws.send(should_still_fit) ++ assert ws.wait() == should_still_fit ++ ws.send(one_too_much) ++ ++ res = ws.wait() ++ assert res is None # socket closed ++ # close code should be available now ++ assert ws._remote_close_data == b"\x03\xf1Incoming frame of 50001 bytes is above length limit of 50000 bytes." ++ eventlet.sleep(0.01) diff -Nru python-eventlet-0.25.1/debian/patches/series python-eventlet-0.25.1/debian/patches/series --- python-eventlet-0.25.1/debian/patches/series 2020-07-02 07:21:57.000000000 +0000 +++ python-eventlet-0.25.1/debian/patches/series 2021-05-11 14:36:43.000000000 +0000 @@ -12,3 +12,4 @@ #0015-more-python3.7-fixes.patch 0016-imp-rename.patch ssl-compat.patch +CVE-2021-21419.patch