[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#988357: marked as done (unblock: python-eventlet/0.26.1-7 CVE-2021-21419)



Your message dated Tue, 11 May 2021 19:04:08 +0000
with message-id <E1lgXfs-0000BT-Me@respighi.debian.org>
and subject line unblock python-eventlet
has caused the Debian Bug report #988357,
regarding unblock: python-eventlet/0.26.1-7 CVE-2021-21419
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
988357: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=988357
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

Please unblock package python-eventlet

[ Reason ]
CVE-2021-21419

[ Impact ]
Malicious peer may exhaust memory on Eventlet side by sending
highly compressed data frame.

[ Tests ]
The Eventlet package contains its own test suite.

[ Risks ]
Regression? Hopefully not. The affected code is only in the
websocket.py file.

[ Checklist ]
  [x] all changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in testing

Please unblock python-eventlet/0.26.1-7

Cheers,

Thomas Goirand (zigo)
diff -Nru python-eventlet-0.26.1/debian/changelog python-eventlet-0.26.1/debian/changelog
--- python-eventlet-0.26.1/debian/changelog	2021-02-18 17:07:30.000000000 +0100
+++ python-eventlet-0.26.1/debian/changelog	2021-05-11 08:03:43.000000000 +0200
@@ -1,3 +1,11 @@
+python-eventlet (0.26.1-7) unstable; urgency=medium
+
+  * CVE-2021-21419: Malicious peer may exhaust memory on Eventlet side
+    by sending highly compressed data frame. Appled upstream patch: websocket:
+    Limit maximum uncompressed frame length to 8MiB (Closes: #988342).
+
+ -- Thomas Goirand <zigo@debian.org>  Tue, 11 May 2021 08:03:43 +0200
+
 python-eventlet (0.26.1-6) unstable; urgency=medium
 
   * Hack a modified debian/greendns.py with filename=None instead of
diff -Nru python-eventlet-0.26.1/debian/patches/CVE-2021-21419_websocket-Limit-maximum-uncompressed-frame-length-to-8MiB.patch python-eventlet-0.26.1/debian/patches/CVE-2021-21419_websocket-Limit-maximum-uncompressed-frame-length-to-8MiB.patch
--- python-eventlet-0.26.1/debian/patches/CVE-2021-21419_websocket-Limit-maximum-uncompressed-frame-length-to-8MiB.patch	1970-01-01 01:00:00.000000000 +0100
+++ python-eventlet-0.26.1/debian/patches/CVE-2021-21419_websocket-Limit-maximum-uncompressed-frame-length-to-8MiB.patch	2021-05-11 08:03:43.000000000 +0200
@@ -0,0 +1,203 @@
+Description: CVE-2021-21419: 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
+Author: Onno Kortmann <onno@gmx.net>
+Date: Thu, 1 Apr 2021 16:15:47 +0200
+Origin: https://github.com/eventlet/eventlet/commit/1412f5e4125b4313f815778a1acb4d3336efcd07.patch
+Bug-Debian: https://bugs.debian.org/988342
+Last-Update: 2021-05-11
+
+Index: python-eventlet/eventlet/websocket.py
+===================================================================
+--- python-eventlet.orig/eventlet/websocket.py
++++ python-eventlet/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/tests/websocket_new_test.py
+===================================================================
+--- python-eventlet.orig/tests/websocket_new_test.py
++++ python-eventlet/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.26.1/debian/patches/series python-eventlet-0.26.1/debian/patches/series
--- python-eventlet-0.26.1/debian/patches/series	2021-02-18 17:07:30.000000000 +0100
+++ python-eventlet-0.26.1/debian/patches/series	2021-05-11 08:03:43.000000000 +0200
@@ -13,3 +13,4 @@
 0017-py39-Add-_at_fork_reinit-method-to-Semaphores.patch
 0018-pyopenssl-tsafe-module-was-deprecated-and-removed-in.patch
 fix-infinte-recursion-in-ssl.patch
+CVE-2021-21419_websocket-Limit-maximum-uncompressed-frame-length-to-8MiB.patch

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply to: