diff mbox series

[meta-python,kirkstone,1/1] python3-twisted: Fix CVE-2023-46137

Message ID 20250916125620.1306702-1-soumya.sambu@windriver.com
State New
Headers show
Series [meta-python,kirkstone,1/1] python3-twisted: Fix CVE-2023-46137 | expand

Commit Message

ssambu Sept. 16, 2025, 12:56 p.m. UTC
From: Soumya Sambu <soumya.sambu@windriver.com>

Twisted is an event-based framework for internet applications. Prior to version
23.10.0rc1, when sending multiple HTTP requests in one TCP packet, twisted.web
will process the requests asynchronously without guaranteeing the response order.
If one of the endpoints is controlled by an attacker, the attacker can delay the
response on purpose to manipulate the response of the second request when a
victim launched two requests using HTTP pipeline. Version 23.10.0rc1 contains a
patch for this issue.

References:
https://nvd.nist.gov/vuln/detail/CVE-2023-46137
https://security-tracker.debian.org/tracker/CVE-2023-46137

Upstream patch:
https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c

Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
 .../python3-twisted/CVE-2023-46137.patch      | 196 ++++++++++++++++++
 .../python/python3-twisted_22.2.0.bb          |   3 +-
 2 files changed, 198 insertions(+), 1 deletion(-)
 create mode 100644 meta-python/recipes-devtools/python/python3-twisted/CVE-2023-46137.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-twisted/CVE-2023-46137.patch b/meta-python/recipes-devtools/python/python3-twisted/CVE-2023-46137.patch
new file mode 100644
index 0000000000..d504448885
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-twisted/CVE-2023-46137.patch
@@ -0,0 +1,196 @@ 
+From 1e6e9d23cac59689760558dcb6634285e694b04c Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Tue Sep 12 11:32:55 2023 -0700
+Subject: [PATCH] 11976 stop processing pipelined HTTP/1.1 requests that are
+ received together (#11979)
+
+CVE: CVE-2023-46137
+
+Upstream-Status: Backport [https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c]
+
+Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
+---
+ 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 96a1335..b99480f 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2366,14 +2366,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 (
+@@ -2385,9 +2409,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.40.0
+
diff --git a/meta-python/recipes-devtools/python/python3-twisted_22.2.0.bb b/meta-python/recipes-devtools/python/python3-twisted_22.2.0.bb
index ef602890ea..5b23ceeb91 100644
--- a/meta-python/recipes-devtools/python/python3-twisted_22.2.0.bb
+++ b/meta-python/recipes-devtools/python/python3-twisted_22.2.0.bb
@@ -13,7 +13,8 @@  PYPI_PACKAGE = "Twisted"
 
 SRC_URI += "file://CVE-2024-41671-0001.patch \
             file://CVE-2024-41671-0002.patch \
-            file://CVE-2024-41810.patch"
+            file://CVE-2024-41810.patch \
+            file://CVE-2023-46137.patch"
 
 inherit pypi python_setuptools_build_meta