Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:chajain
python-eventlet
0002-websocket-Limit-maximum-uncompressed-frame...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0002-websocket-Limit-maximum-uncompressed-frame-length-to.patch of Package python-eventlet
From ac5fa097bbe782652e512cf359116dde1b50a08a Mon Sep 17 00:00:00 2001 From: Onno Kortmann <onno@gmx.net> Date: Thu, 1 Apr 2021 16:15:47 +0200 Subject: [PATCH 2/2] 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 (cherry picked from commit 1412f5e4125b4313f815778a1acb4d3336efcd07) Conflicts: eventlet/websocket.py tests/websocket_new_test.py --- eventlet/websocket.py | 27 ++++++++++++++++++++----- tests/websocket_new_test.py | 39 ++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/eventlet/websocket.py b/eventlet/websocket.py index 5c9eec7..3997a37 100644 --- a/eventlet/websocket.py +++ b/eventlet/websocket.py @@ -35,6 +35,7 @@ for _mod in ('wsaccel.utf8validator', 'autobahn.utf8validator'): 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' @@ -72,14 +73,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, @@ -240,7 +247,8 @@ class WebSocketWSGI(object): handshake_reply.append(b"Sec-WebSocket-Protocol: " + six.b(negotiated_protocol)) sock.sendall(b'\r\n'.join(handshake_reply) + b'\r\n\r\n') return RFC6455WebSocket(sock, environ, self.protocol_version, - protocol=negotiated_protocol) + protocol=negotiated_protocol, + max_frame_length=self.max_frame_length) def _extract_number(self, value): """ @@ -420,11 +428,14 @@ class ProtocolError(ValueError): class RFC6455WebSocket(WebSocket): - def __init__(self, sock, environ, version=13, protocol=None, client=False): + def __init__(self, sock, environ, version=13, protocol=None, client=False, + max_frame_length=DEFAULT_MAX_FRAME_LENGTH): super(RFC6455WebSocket, self).__init__(sock, environ, version) self.iterator = self._iter_frames() self.client = client self.protocol = protocol + self.max_frame_length = max_frame_length + self._remote_close_data = None class UTF8Decoder(object): def __init__(self): @@ -457,11 +468,12 @@ class RFC6455WebSocket(WebSocket): return data class Message(object): - def __init__(self, opcode, decoder=None): + def __init__(self, opcode, max_frame_length, decoder=None): self.decoder = decoder self.data = [] self.finished = False self.opcode = opcode + self.max_frame_length = max_frame_length def push(self, data, final=False): if self.decoder: @@ -481,6 +493,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: @@ -575,12 +588,16 @@ 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 - message = self.Message(opcode, decoder=decoder) + message = self.Message(opcode, self.max_frame_length, decoder=decoder) if not length: message.push(b'', final=finished) else: diff --git a/tests/websocket_new_test.py b/tests/websocket_new_test.py index 712bccd..b6feb45 100644 --- a/tests/websocket_new_test.py +++ b/tests/websocket_new_test.py @@ -29,7 +29,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): @@ -228,3 +233,35 @@ class TestWebSocket(tests.wsgi_test._TestBase): sock.sendall(b'\x07\xff') # Weird packet. done_with_request.wait() assert not error_detected[0] + + def test_large_frame_size_uncompressed_13(self): + # Test fix for GHSA-9p9m-jm8w-94p2 + connect = [ + "GET /echo HTTP/1.1", + "Upgrade: websocket", + "Connection: Upgrade", + "Host: %s:%s" % self.server_addr, + "Origin: http://%s:%s" % self.server_addr, + "Sec-WebSocket-Version: 13", + "Sec-WebSocket-Key: d9MXuOzlVQ0h+qRllvSCIg==", + ] + sock = eventlet.connect(self.server_addr) + sock.sendall(six.b('\r\n'.join(connect) + '\r\n\r\n')) + 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) -- 2.25.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor