diff --git a/CVE-2023-46137.patch b/CVE-2023-46137.patch deleted file mode 100644 index 8c37b9b4a867f02b49621bd8b5aea754aa696d25..0000000000000000000000000000000000000000 --- a/CVE-2023-46137.patch +++ /dev/null @@ -1,192 +0,0 @@ -From 8d500550fdee4c55e3158f8d8c293c2dc1587869 Mon Sep 17 00:00:00 2001 -From: starlet-dx <15929766099@163.com> -Date: Fri, 29 Dec 2023 15:36:52 +0800 -Subject: [PATCH 1/1] 11976 stop processing pipelined HTTP/1.1 requests that are received together #11979 - -Origin: -https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c ---- - src/twisted/web/http.py | 32 +++++++-- - src/twisted/web/newsfragments/11976.bugfix | 7 ++ - src/twisted/web/test/test_web.py | 81 +++++++++++++++++++++- - 3 files changed, 114 insertions(+), 6 deletions(-) - create mode 100644 src/twisted/web/newsfragments/11976.bugfix - -diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py -index b80a55a..23f8817 100644 ---- a/src/twisted/web/http.py -+++ b/src/twisted/web/http.py -@@ -2443,14 +2443,38 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): - - self._handlingRequest = True - -+ # We go into raw mode here even though we will be receiving lines next -+ # in the protocol; however, this data will be buffered and then passed -+ # back to line mode in the setLineMode call in requestDone. -+ self.setRawMode() -+ - req = self.requests[-1] - req.requestReceived(command, path, version) - -- def dataReceived(self, data): -+ def rawDataReceived(self, data: bytes) -> None: - """ -- Data was received from the network. Process it. -+ This is called when this HTTP/1.1 parser is in raw mode rather than -+ line mode. -+ -+ It may be in raw mode for one of two reasons: -+ -+ 1. All the headers of a request have been received and this -+ L{HTTPChannel} is currently receiving its body. -+ -+ 2. The full content of a request has been received and is currently -+ being processed asynchronously, and this L{HTTPChannel} is -+ buffering the data of all subsequent requests to be parsed -+ later. -+ -+ In the second state, the data will be played back later. -+ -+ @note: This isn't really a public API, and should be invoked only by -+ L{LineReceiver}'s line parsing logic. If you wish to drive an -+ L{HTTPChannel} from a custom data source, call C{dataReceived} on -+ it directly. -+ -+ @see: L{LineReceive.rawDataReceived} - """ -- # If we're currently handling a request, buffer this data. - if self._handlingRequest: - self._dataBuffer.append(data) - if ( -@@ -2462,9 +2486,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): - # ready. See docstring for _optimisticEagerReadSize above. - self._networkProducer.pauseProducing() - return -- return basic.LineReceiver.dataReceived(self, data) - -- def rawDataReceived(self, data): - self.resetTimeout() - - try: -diff --git a/src/twisted/web/newsfragments/11976.bugfix b/src/twisted/web/newsfragments/11976.bugfix -new file mode 100644 -index 0000000..8ac292b ---- /dev/null -+++ b/src/twisted/web/newsfragments/11976.bugfix -@@ -0,0 +1,7 @@ -+In Twisted 16.3.0, we changed twisted.web to stop dispatching HTTP/1.1 -+pipelined requests to application code. There was a bug in this change which -+still allowed clients which could send multiple full HTTP requests in a single -+TCP segment to trigger asynchronous processing of later requests, which could -+lead to out-of-order responses. This has now been corrected and twisted.web -+should never process a pipelined request over HTTP/1.1 until the previous -+request has fully completed. -diff --git a/src/twisted/web/test/test_web.py b/src/twisted/web/test/test_web.py -index 3eb35a9..b2b2ad7 100644 ---- a/src/twisted/web/test/test_web.py -+++ b/src/twisted/web/test/test_web.py -@@ -8,6 +8,7 @@ Tests for various parts of L{twisted.web}. - import os - import zlib - from io import BytesIO -+from typing import List - - from zope.interface import implementer - from zope.interface.verify import verifyObject -@@ -17,10 +18,13 @@ from twisted.internet.address import IPv4Address, IPv6Address - from twisted.internet.task import Clock - from twisted.logger import LogLevel, globalLogPublisher - from twisted.python import failure, reflect -+from twisted.python.compat import iterbytes - from twisted.python.filepath import FilePath --from twisted.test.proto_helpers import EventLoggingObserver -+from twisted.test.proto_helpers import EventLoggingObserver, StringTransport - from twisted.trial import unittest - from twisted.web import error, http, iweb, resource, server -+from twisted.web.resource import Resource -+from twisted.web.server import NOT_DONE_YET, Request, Site - from twisted.web.static import Data - from twisted.web.test.requesthelper import DummyChannel, DummyRequest - from ._util import assertIsFilesystemTemporary -@@ -1849,3 +1853,78 @@ class ExplicitHTTPFactoryReactor(unittest.TestCase): - - factory = http.HTTPFactory() - self.assertIs(factory.reactor, reactor) -+ -+ -+class QueueResource(Resource): -+ """ -+ Add all requests to an internal queue, -+ without responding to the requests. -+ You can access the requests from the queue and handle their response. -+ """ -+ -+ isLeaf = True -+ -+ def __init__(self) -> None: -+ super().__init__() -+ self.dispatchedRequests: List[Request] = [] -+ -+ def render_GET(self, request: Request) -> int: -+ self.dispatchedRequests.append(request) -+ return NOT_DONE_YET -+ -+ -+class TestRFC9112Section932(unittest.TestCase): -+ """ -+ Verify that HTTP/1.1 request ordering is preserved. -+ """ -+ -+ def test_multipleRequestsInOneSegment(self) -> None: -+ """ -+ Twisted MUST NOT respond to a second HTTP/1.1 request while the first -+ is still pending. -+ """ -+ qr = QueueResource() -+ site = Site(qr) -+ proto = site.buildProtocol(None) -+ serverTransport = StringTransport() -+ proto.makeConnection(serverTransport) -+ proto.dataReceived( -+ b"GET /first HTTP/1.1\r\nHost: a\r\n\r\n" -+ b"GET /second HTTP/1.1\r\nHost: a\r\n\r\n" -+ ) -+ # The TCP data contains 2 requests, -+ # but only 1 request was dispatched, -+ # as the first request was not yet finalized. -+ self.assertEqual(len(qr.dispatchedRequests), 1) -+ # The first request is finalized and the -+ # second request is dispatched right away. -+ qr.dispatchedRequests[0].finish() -+ self.assertEqual(len(qr.dispatchedRequests), 2) -+ -+ def test_multipleRequestsInDifferentSegments(self) -> None: -+ """ -+ Twisted MUST NOT respond to a second HTTP/1.1 request while the first -+ is still pending, even if the second request is received in a separate -+ TCP package. -+ """ -+ qr = QueueResource() -+ site = Site(qr) -+ proto = site.buildProtocol(None) -+ serverTransport = StringTransport() -+ proto.makeConnection(serverTransport) -+ raw_data = ( -+ b"GET /first HTTP/1.1\r\nHost: a\r\n\r\n" -+ b"GET /second HTTP/1.1\r\nHost: a\r\n\r\n" -+ ) -+ # Just go byte by byte for the extreme case in which each byte is -+ # received in a separate TCP package. -+ for chunk in iterbytes(raw_data): -+ proto.dataReceived(chunk) -+ # The TCP data contains 2 requests, -+ # but only 1 request was dispatched, -+ # as the first request was not yet finalized. -+ self.assertEqual(len(qr.dispatchedRequests), 1) -+ # The first request is finalized and the -+ # second request is dispatched right away. -+ qr.dispatchedRequests[0].finish() -+ self.assertEqual(len(qr.dispatchedRequests), 2) --- -2.30.0 - diff --git a/CVE-2024-41671.patch b/CVE-2024-41671.patch deleted file mode 100644 index a1e87b0f549f7e9eefdbc328a9c0d5342d957073..0000000000000000000000000000000000000000 --- a/CVE-2024-41671.patch +++ /dev/null @@ -1,322 +0,0 @@ -From ef2c755e9e9d57d58132af790bd2fd2b957b3fb1 Mon Sep 17 00:00:00 2001 -From: Tom Most -Date: Mon, 22 Jul 2024 23:21:49 -0700 -Subject: [PATCH] Tests and partial fix - ---- - src/twisted/web/http.py | 35 +++- - src/twisted/web/newsfragments/12248.bugfix | 1 + - src/twisted/web/test/test_http.py | 191 +++++++++++++++++++-- - 3 files changed, 212 insertions(+), 15 deletions(-) - create mode 100644 src/twisted/web/newsfragments/12248.bugfix - -diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py -index 23f8817..c7216d0 100644 ---- a/src/twisted/web/http.py -+++ b/src/twisted/web/http.py -@@ -1921,6 +1921,9 @@ class _ChunkedTransferDecoder: - self.finishCallback = finishCallback - self._buffer = bytearray() - self._start = 0 -+ self._trailerHeaders = [] -+ self._maxTrailerHeadersSize = 2**16 -+ self._receivedTrailerHeadersSize = 0 - - def _dataReceived_CHUNK_LENGTH(self) -> bool: - """ -@@ -2007,11 +2010,35 @@ class _ChunkedTransferDecoder: - @raises _MalformedChunkedDataError: when anything other than CRLF is - received. - """ -- if len(self._buffer) < 2: -+ 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 not self._buffer.startswith(b"\r\n"): -- raise _MalformedChunkedDataError("Chunk did not end with CRLF") -+ 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[:] -@@ -2331,8 +2358,8 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): - self.__header = line - - def _finishRequestBody(self, data): -- self.allContentReceived() - self._dataBuffer.append(data) -+ self.allContentReceived() - - def _maybeChooseTransferDecoder(self, header, data): - """ -diff --git a/src/twisted/web/newsfragments/12248.bugfix b/src/twisted/web/newsfragments/12248.bugfix -new file mode 100644 -index 0000000..2fb6067 ---- /dev/null -+++ b/src/twisted/web/newsfragments/12248.bugfix -@@ -0,0 +1 @@ -+The HTTP 1.0 and 1.1 server provided by twisted.web could process pipelined HTTP requests out-of-order, possibly resulting in information disclosure (CVE-2024-41671/GHSA-c8m8-j448-xjx7) -diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py -index f304991..ae061cd 100644 ---- a/src/twisted/web/test/test_http.py -+++ b/src/twisted/web/test/test_http.py -@@ -460,10 +460,9 @@ class HTTP1_0Tests(unittest.TestCase, ResponseTestMixin): - # one byte at a time, to stress it. - for byte in iterbytes(self.requests): - a.dataReceived(byte) -- value = b.value() - - # So far only one request should have been dispatched. -- self.assertEqual(value, b"") -+ self.assertEqual(b.value(), b"") - self.assertEqual(1, len(a.requests)) - - # Now, process each request one at a time. -@@ -472,8 +471,95 @@ class HTTP1_0Tests(unittest.TestCase, ResponseTestMixin): - request = a.requests[0].original - request.delayedProcess() - -- value = b.value() -- self.assertResponseEquals(value, self.expected_response) -+ self.assertResponseEquals(b.value(), self.expectedResponses) -+ -+ def test_stepwiseDumpTruck(self): -+ """ -+ Imitate a fast connection where several pipelined -+ requests arrive in a single read. The request handler -+ (L{DelayedHTTPHandler}) is puppeted to step through the -+ handling of each request. -+ """ -+ b = StringTransport() -+ a = http.HTTPChannel() -+ a.requestFactory = DelayedHTTPHandlerProxy -+ a.makeConnection(b) -+ -+ a.dataReceived(self.requests) -+ -+ # So far only one request should have been dispatched. -+ self.assertEqual(b.value(), b"") -+ self.assertEqual(1, len(a.requests)) -+ -+ # Now, process each request one at a time. -+ while a.requests: -+ self.assertEqual(1, len(a.requests)) -+ request = a.requests[0].original -+ request.delayedProcess() -+ -+ self.assertResponseEquals(b.value(), self.expectedResponses) -+ -+ 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 = DummyHTTPHandlerProxy # "sync" -+ 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 = DummyHTTPHandlerProxy -+ a.makeConnection(b) -+ -+ # All bytes at once to ensure there's stuff to buffer. -+ a.dataReceived(self.requests) -+ -+ self.assertResponseEquals(b.value(), self.expectedResponses) -+ -+ def test_immediateABiggerTruck(self): -+ """ -+ Imitate a fast connection where a so many pipelined -+ requests arrive in a single read that backpressure is indicated. -+ 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. -+ -+ @see: L{http.HTTPChannel._optimisticEagerReadSize} -+ """ -+ b = StringTransport() -+ a = http.HTTPChannel() -+ a.requestFactory = DummyHTTPHandlerProxy -+ a.makeConnection(b) -+ -+ overLimitCount = a._optimisticEagerReadSize // len(self.requests) * 10 -+ a.dataReceived(self.requests * overLimitCount) -+ -+ self.assertResponseEquals(b.value(), self.expectedResponses * overLimitCount) - - - class HTTP1_1Tests(HTTP1_0Tests): -@@ -574,10 +660,15 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin): - b"POST / HTTP/1.1\r\n" - b"Content-Length: 10\r\n" - b"\r\n" -- b"0123456789POST / 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 = [ -@@ -594,14 +685,16 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin): - b"Request: /", - b"Command: POST", - b"Version: HTTP/1.1", -- b"Content-Length: 21", -- b"'''\n10\n0123456789'''\n", -+ b"Content-Length: 23", -+ b"'''\nNone\n0123456789'''\n", - ), - ] - -- def test_noPipelining(self): -+ def test_stepwiseTinyTube(self): - """ -- Test that pipelined requests get buffered, not processed in parallel. -+ Imitate a slow connection that delivers one byte at a time. -+ The request handler (L{DelayedHTTPHandler}) is puppeted to -+ step through the handling of each request. - """ - b = StringTransport() - a = http.HTTPChannel() -@@ -1474,6 +1567,82 @@ class ChunkedTransferEncodingTests(unittest.TestCase): - self.assertEqual(errors, []) - self.assertEqual(successes, [True]) - -+ def test_trailerHeaders(self): -+ """ -+ L{_ChunkedTransferDecoder.dataReceived} decodes chunked-encoded data -+ and ignores trailer headers which come after the terminating zero-length -+ chunk. -+ """ -+ L = [] -+ finished = [] -+ p = http._ChunkedTransferDecoder(L.append, finished.append) -+ p.dataReceived(b"3\r\nabc\r\n5\r\n12345\r\n") -+ p.dataReceived( -+ b"a\r\n0123456789\r\n0\r\nServer-Timing: total;dur=123.4\r\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\r\n" -+ ) -+ self.assertEqual(L, [b"abc", b"12345", b"0123456789"]) -+ self.assertEqual(finished, [b""]) -+ self.assertEqual( -+ p._trailerHeaders, -+ [ -+ b"Server-Timing: total;dur=123.4", -+ b"Expires: Wed, 21 Oct 2015 07:28:00 GMT", -+ ], -+ ) -+ -+ def test_shortTrailerHeader(self): -+ """ -+ L{_ChunkedTransferDecoder.dataReceived} decodes chunks of input with -+ tailer header broken up and delivered in multiple calls. -+ """ -+ L = [] -+ finished = [] -+ p = http._ChunkedTransferDecoder(L.append, finished.append) -+ for s in iterbytes( -+ b"3\r\nabc\r\n5\r\n12345\r\n0\r\nServer-Timing: total;dur=123.4\r\n\r\n" -+ ): -+ p.dataReceived(s) -+ self.assertEqual(L, [b"a", b"b", b"c", b"1", b"2", b"3", b"4", b"5"]) -+ self.assertEqual(finished, [b""]) -+ self.assertEqual(p._trailerHeaders, [b"Server-Timing: total;dur=123.4"]) -+ -+ def test_tooLongTrailerHeader(self): -+ r""" -+ L{_ChunkedTransferDecoder.dataReceived} raises -+ L{_MalformedChunkedDataError} when the trailing headers data is too long. -+ """ -+ p = http._ChunkedTransferDecoder( -+ lambda b: None, -+ lambda b: None, # pragma: nocov -+ ) -+ p._maxTrailerHeadersSize = 10 -+ self.assertRaises( -+ http._MalformedChunkedDataError, -+ p.dataReceived, -+ b"3\r\nabc\r\n0\r\nTotal-Trailer: header;greater-then=10\r\n\r\n", -+ ) -+ -+ def test_unfinishedTrailerHeader(self): -+ r""" -+ L{_ChunkedTransferDecoder.dataReceived} raises -+ L{_MalformedChunkedDataError} when the trailing headers data is too long -+ and doesn't have final CRLF characters. -+ """ -+ p = http._ChunkedTransferDecoder( -+ lambda b: None, -+ lambda b: None, # pragma: nocov -+ ) -+ p._maxTrailerHeadersSize = 10 -+ # 9 bytes are received so far, in 2 packets. -+ # For now, all is ok. -+ p.dataReceived(b"3\r\nabc\r\n0\r\n01234567") -+ p.dataReceived(b"\r") -+ # Once the 10th byte is received, the processing fails. -+ self.assertRaises( -+ http._MalformedChunkedDataError, -+ p.dataReceived, -+ b"A", -+ ) - - class ChunkingTests(unittest.TestCase, ResponseTestMixin): - --- -2.41.0 - diff --git a/python-twisted.spec b/python-twisted.spec index 7dc70da03b138d3db85f48655061bea1300daf25..6e44225c64e72a7dac9d6bcbf6421e957bbc4555 100644 --- a/python-twisted.spec +++ b/python-twisted.spec @@ -1,16 +1,16 @@ %define debug_package %{nil} Name: python-twisted -Version: 22.10.0 -Release: 4 +Version: 24.3.0 +Release: 1 Summary: An event-driven networking engine written in Python License: MIT URL: http://twistedmatrix.com/ Source0: https://github.com/twisted/twisted/archive/twisted-%{version}/twisted-%{version}.tar.gz -# https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c -Patch0: CVE-2023-46137.patch -Patch1: CVE-2024-41810.patch -Patch2: CVE-2024-41671.patch +Patch0: CVE-2024-41810.patch + +BuildRequires: python3-pip python3-hatchling python3-hatch-vcs python3-wheel +BuildRequires: python3-hatch-fancy-pypi-readme python3-incremental %description Twisted is an event-based framework for internet applications, @@ -79,10 +79,12 @@ The python-twisted-help package contains related documents. %autosetup -n twisted-twisted-%{version} -p1 %build -%py3_build +%pyproject_build %install -%py3_install +%pyproject_install +sed -i 's/zope-interface/zope.interface/g' %{buildroot}%{python3_sitelib}/twisted-%{version}.dist-info/METADATA + mv %{buildroot}%{_bindir}/trial %{buildroot}%{_bindir}/trial-%{python3_version} mv %{buildroot}%{_bindir}/twistd %{buildroot}%{_bindir}/twistd-%{python3_version} ln -s ./trial-%{python3_version} %{buildroot}%{_bindir}/trial-3 @@ -104,14 +106,16 @@ PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python3_sitelib} %{bu %files -n python3-twisted %doc NEWS.rst README.rst LICENSE %{_bindir}/{trial-3*,twistd-3*} -%{python3_sitelib}/twisted -%{python3_sitelib}/Twisted-%{version}-py%{python3_version}.egg-info +%{python3_sitelib}/twisted* %{_bindir}/{cftp,ckeygen,conch,mailmail,pyhtmlizer,tkconch,trial,twist,twistd} %files help %{_mandir}/man1/{cftp.1*,ckeygen.1*,conch.1*,mailmail.1*,pyhtmlizer.1*,tkconch.1*,trial.1*,twistd.1*} %changelog +* Thu Sep 19 2024 xu_ping <707078654@qq.com> - 24.3.0-1 +- Upgrade version to 24.7.0 + * Tue Jul 30 2024 yinyongkang - 22.10.0-4 - Fix CVE-2024-41810 and CVE-2024-41671 diff --git a/twisted-22.10.0.tar.gz b/twisted-24.3.0.tar.gz similarity index 33% rename from twisted-22.10.0.tar.gz rename to twisted-24.3.0.tar.gz index 0ec68b61897721138a3fdf1c2614a951d9045b98..55078ee6afa4f9c3abf7f38553bccafb1b58e7bb 100644 Binary files a/twisted-22.10.0.tar.gz and b/twisted-24.3.0.tar.gz differ