File CVE-2024-41671.patch of Package python-Twisted.34938
Index: Twisted-15.2.1/twisted/web/http.py
===================================================================
--- Twisted-15.2.1.orig/twisted/web/http.py
+++ Twisted-15.2.1/twisted/web/http.py
@@ -1584,6 +1584,9 @@ class _ChunkedTransferDecoder(object):
self.finishCallback = finishCallback
self._buffer = bytearray()
self._start = 0
+ self._trailerHeaders = []
+ self._maxTrailerHeadersSize = 2**16
+ self._receivedTrailerHeadersSize = 0
def _dataReceived_CHUNK_LENGTH(self):
@@ -1656,6 +1659,42 @@ class _ChunkedTransferDecoder(object):
self.finishCallback(data)
return False
+ eolIndex = self._buffer.find(b"\r\n", self._start)
+
+ if eolIndex == -1:
+ # Still no end of network line marker found.
+ #
+ # Check if we've run up against the trailer size limit: if the next
+ # read contains the terminating CRLF then we'll have this many bytes
+ # of trailers (including the CRLFs).
+ minTrailerSize = (
+ self._receivedTrailerHeadersSize
+ + len(self._buffer)
+ + (1 if self._buffer.endswith(b"\r") else 2)
+ )
+ if minTrailerSize > self._maxTrailerHeadersSize:
+ raise _MalformedChunkedDataError("Trailer headers data is too long.")
+ # Continue processing more data.
+ return False
+
+ if eolIndex > 0:
+ # A trailer header was detected.
+ self._trailerHeaders.append(self._buffer[0:eolIndex])
+ del self._buffer[0 : eolIndex + 2]
+ self._start = 0
+ self._receivedTrailerHeadersSize += eolIndex + 2
+ if self._receivedTrailerHeadersSize > self._maxTrailerHeadersSize:
+ raise _MalformedChunkedDataError("Trailer headers data is too long.")
+ return True
+
+ # eolIndex in this part of code is equal to 0
+
+ data = memoryview(self._buffer)[2:].tobytes()
+
+ del self._buffer[:]
+ self.state = "FINISHED"
+ self.finishCallback(data)
+ return False
def _dataReceived_BODY(self):
if len(self._buffer) >= self.length:
@@ -1809,8 +1848,8 @@ class HTTPChannel(basic.LineReceiver, po
def _finishRequestBody(self, data):
- self.allContentReceived()
self.setLineMode(data)
+ self.allContentReceived()
def _maybeChooseTransferDecoder(self, header, data):
"""
@@ -1826,6 +1865,8 @@ class HTTPChannel(basic.LineReceiver, po
# Can this header determine the length?
if header == b'content-length':
+ if not data.isdigit():
+ return fail()
try:
length = int(data)
except ValueError:
@@ -1874,7 +1915,7 @@ class HTTPChannel(basic.LineReceiver, po
header, data = line.split(b':', 1)
header = header.lower()
- data = data.strip()
+ data = data.strip(b" \t")
if not self._maybeChooseTransferDecoder(header,data):
return False
Index: Twisted-15.2.1/twisted/web/test/test_http.py
===================================================================
--- Twisted-15.2.1.orig/twisted/web/test/test_http.py
+++ Twisted-15.2.1/twisted/web/test/test_http.py
@@ -2606,3 +2606,83 @@ class HexHelperTests(unittest.Synchronou
for s in self.badStrings:
self.assertRaises(ValueError, http._hexint, s)
+
+class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
+ """
+ Pipelined requests get buffered and executed in the order received,
+ not processed in parallel.
+ """
+
+ requests = (
+ b"POST / HTTP/1.1\r\n"
+ b"Content-Length: 10\r\n"
+ b"\r\n"
+ b"0123456789"
+ # Chunk encoded request.
+ b"POST / HTTP/1.1\r\n"
+ b"Transfer-Encoding: chunked\r\n"
+ b"\r\n"
+ b"a\r\n"
+ b"0123456789\r\n"
+ b"0\r\n"
+ b"\r\n"
+ )
+
+ expectedResponses = [
+ (
+ b"HTTP/1.1 200 OK",
+ b"Request: /",
+ b"Command: POST",
+ b"Version: HTTP/1.1",
+ b"Content-Length: 21",
+ b"'''\n10\n0123456789'''\n",
+ ),
+ (
+ b"HTTP/1.1 200 OK",
+ b"Request: /",
+ b"Command: POST",
+ b"Version: HTTP/1.1",
+ b"Content-Length: 23",
+ b"'''\nNone\n0123456789'''\n",
+ ),
+ ]
+
+ def test_immediateTinyTube(self):
+ """
+ Imitate a slow connection that delivers one byte at a time.
+
+ (L{DummyHTTPHandler}) immediately responds, but no more
+ than one
+ """
+ b = StringTransport()
+ a = http.HTTPChannel()
+ a.requestFactory = DummyHTTPHandler
+ a.makeConnection(b)
+
+ # one byte at a time, to stress it.
+ for byte in iterbytes(self.requests):
+ a.dataReceived(byte)
+ # There is never more than one request dispatched at a time:
+ self.assertLessEqual(len(a.requests), 1)
+
+ self.assertResponseEquals(b.value(), self.expectedResponses)
+
+ def test_immediateDumpTruck(self):
+ """
+ Imitate a fast connection where several pipelined
+ requests arrive in a single read. The request handler
+ (L{DummyHTTPHandler}) immediately responds.
+
+ This doesn't check the at-most-one pending request
+ invariant but exercises otherwise uncovered code paths.
+ See GHSA-c8m8-j448-xjx7.
+ """
+ b = StringTransport()
+ a = http.HTTPChannel()
+ a.requestFactory = DummyHTTPHandler
+ a.makeConnection(b)
+
+ # All bytes at once to ensure there's stuff to buffer.
+ a.dataReceived(self.requests)
+
+ self.assertResponseEquals(b.value(), self.expectedResponses)