Fix CVE-2023-5625 (Patch for round CVE-2021-21419)
(cherry picked from commit 96c83705384a72ddbccba69904d59a0a5b5351f1)
This commit is contained in:
parent
97bf537c1b
commit
117339f1e9
207
CVE-2021-21419.patch
Normal file
207
CVE-2021-21419.patch
Normal file
@ -0,0 +1,207 @@
|
||||
From 1412f5e4125b4313f815778a1acb4d3336efcd07 Mon Sep 17 00:00:00 2001
|
||||
From: Onno Kortmann <onno@gmx.net>
|
||||
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(-)
|
||||
|
||||
diff --git a/eventlet/websocket.py b/eventlet/websocket.py
|
||||
index 2222b4ba65..245993d55b 100644
|
||||
--- a/eventlet/websocket.py
|
||||
+++ b/eventlet/websocket.py
|
||||
@@ -38,6 +38,7 @@
|
||||
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 @@ def my_handler(ws):
|
||||
: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,
|
||||
@@ -324,7 +331,8 @@ def _handle_hybi_request(self, environ):
|
||||
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):
|
||||
"""
|
||||
@@ -503,7 +511,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
|
||||
@@ -512,6 +521,8 @@ def __init__(self, sock, environ, version=13, protocol=None, client=False, exten
|
||||
|
||||
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):
|
||||
@@ -583,12 +594,13 @@ def _get_bytes(self, numbytes):
|
||||
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
|
||||
@@ -597,7 +609,12 @@ def push(self, data, final=False):
|
||||
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
|
||||
@@ -611,6 +628,7 @@ def _apply_mask(data, mask, length=None, offset=0):
|
||||
|
||||
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:
|
||||
@@ -710,13 +728,17 @@ def _recv_frame(self, message=None):
|
||||
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:
|
||||
diff --git a/tests/websocket_new_test.py b/tests/websocket_new_test.py
|
||||
index 5f98025ec7..cc857924fe 100644
|
||||
--- a/tests/websocket_new_test.py
|
||||
+++ b/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 @@ def test_compressed_send_recv_both_no_context_13(self):
|
||||
|
||||
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)
|
||||
@ -1,11 +1,13 @@
|
||||
%global _empty_manifest_terminate_build 0
|
||||
Name: python-eventlet
|
||||
Version: 0.30.2
|
||||
Release: 1
|
||||
Release: 2
|
||||
Summary: Highly concurrent networking library
|
||||
License: MIT License
|
||||
URL: http://eventlet.net
|
||||
Source0: https://files.pythonhosted.org/packages/23/db/8ff5a9dec5ff016d5836254b676d507c2180d8838d7e545277d938896913/eventlet-0.30.2.tar.gz
|
||||
# https://github.com/eventlet/eventlet/commit/1412f5e4125b4313f815778a1acb4d3336efcd07
|
||||
Patch0: CVE-2021-21419.patch
|
||||
BuildArch: noarch
|
||||
|
||||
%description
|
||||
@ -40,7 +42,7 @@ Provides: python3-eventlet-doc
|
||||
Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it.
|
||||
|
||||
%prep
|
||||
%autosetup -n eventlet-0.30.2 -S git
|
||||
%autosetup -n eventlet-0.30.2 -p1
|
||||
|
||||
%build
|
||||
%py3_build
|
||||
@ -83,6 +85,9 @@ mv %{buildroot}/doclist.lst .
|
||||
%{_docdir}/*
|
||||
|
||||
%changelog
|
||||
* Mon Oct 23 2023 yaoxin <yao_xin001@hoperun.com> - 0.30.2-2
|
||||
- Fix CVE-2023-5625 (Patch for round CVE-2021-21419)
|
||||
|
||||
* Mon Jul 26 2021 OpenStack_SIG <openstack@openeuler.org> - 0.30.2-1
|
||||
- update to 0.30.2
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user