python3-twisted: Fix CVE-2024-41671

Twisted is an event-based framework for internet applications, supporting Python 3.6+.
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. This vulnerability
is fixed in 24.7.0rc1.

References:
https://nvd.nist.gov/vuln/detail/CVE-2024-41671
https://ubuntu.com/security/CVE-2024-41671

Upstream patches:
f1cb4e616e
ef2c755e9e

Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
This commit is contained in:
Soumya Sambu
2025-04-23 04:59:35 +00:00
committed by Armin Kuster
parent 85275437cd
commit 5c4b61d38a
3 changed files with 232 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
From f1cb4e616e9f23b4dd044a6db44365060950c64f Mon Sep 17 00:00:00 2001
From: Tom Most <twm@freecog.net>
Date: Mon, 22 Jul 2024 22:21:10 -0700
Subject: [PATCH] Use chunking in the pipelining tests
CVE: CVE-2024-41671
Upstream-Status: Backport [https://github.com/twisted/twisted/commit/f1cb4e616e9f23b4dd044a6db44365060950c64f]
Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
src/twisted/web/test/test_http.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
index 7ffea4e..5d88ff1 100644
--- a/src/twisted/web/test/test_http.py
+++ b/src/twisted/web/test/test_http.py
@@ -575,9 +575,11 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
b"Content-Length: 10\r\n"
b"\r\n"
b"0123456789POST / HTTP/1.1\r\n"
- b"Content-Length: 10\r\n"
+ b"Transfer-Encoding: chunked\r\n"
b"\r\n"
+ b"a\r\n"
b"0123456789"
+ b"0\r\n"
)
expectedResponses = [
--
2.40.0

View File

@@ -0,0 +1,196 @@
From ef2c755e9e9d57d58132af790bd2fd2b957b3fb1 Mon Sep 17 00:00:00 2001
From: Tom Most <twm@freecog.net>
Date: Mon, 22 Jul 2024 23:21:49 -0700
Subject: [PATCH] Tests and partial fix
CVE: CVE-2024-41671
Upstream-Status: Backport [https://github.com/twisted/twisted/commit/ef2c755e9e9d57d58132af790bd2fd2b957b3fb1]
Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
src/twisted/web/http.py | 2 +-
src/twisted/web/test/test_http.py | 112 +++++++++++++++++++++++++++---
2 files changed, 102 insertions(+), 12 deletions(-)
diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
index a53ebc2..96a1335 100644
--- a/src/twisted/web/http.py
+++ b/src/twisted/web/http.py
@@ -2256,8 +2256,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/test/test_http.py b/src/twisted/web/test/test_http.py
index 5d88ff1..86c85d2 100644
--- a/src/twisted/web/test/test_http.py
+++ b/src/twisted/web/test/test_http.py
@@ -136,7 +136,7 @@ class DummyHTTPHandler(http.Request):
data = self.content.read()
length = self.getHeader(b"content-length")
if length is None:
- length = networkString(str(length))
+ length = str(length).encode()
request = b"'''\n" + length + b"\n" + data + b"'''\n"
self.setResponseCode(200)
self.setHeader(b"Request", self.uri)
@@ -567,7 +567,8 @@ class HTTP0_9Tests(HTTP1_0Tests):
class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
"""
- Tests that multiple pipelined requests with bodies are correctly buffered.
+ Pipelined requests get buffered and executed in the order received,
+ not processed in parallel.
"""
requests = (
@@ -578,8 +579,9 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b"a\r\n"
- b"0123456789"
+ b"0123456789\r\n"
b"0\r\n"
+ b"\r\n"
)
expectedResponses = [
@@ -596,14 +598,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()
@@ -612,10 +616,9 @@ class PipeliningBodyTests(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.
@@ -624,8 +627,95 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
request = a.requests[0].original
request.delayedProcess()
- value = b.value()
- self.assertResponseEquals(value, self.expectedResponses)
+ 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)
def test_pipeliningReadLimit(self):
"""
--
2.40.0

View File

@@ -11,6 +11,9 @@ SRC_URI[sha256sum] = "57f32b1f6838facb8c004c89467840367ad38e9e535f8252091345dba5
PYPI_PACKAGE = "Twisted"
SRC_URI += "file://CVE-2024-41671-0001.patch \
file://CVE-2024-41671-0002.patch"
inherit pypi python_setuptools_build_meta
do_install:append() {