Skip to content

Commit 8380f30

Browse files
[AUTO-CHERRYPICK] Fix python-twisted CVEs CVE-2024-41671 and CVE-2024-41810 in 2.0 - branch main (#10122)
Co-authored-by: sindhu-karri <33163197+sindhu-karri@users.noreply.github.com>
1 parent c595d61 commit 8380f30

3 files changed

Lines changed: 611 additions & 1 deletion

File tree

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
From a31f547fe5bb0a9cba97249f3180195c2208a286 Mon Sep 17 00:00:00 2001
2+
From: Sindhu Karri <lakarri@microsoft.com>
3+
Date: Thu, 1 Aug 2024 09:39:06 +0000
4+
Subject: [PATCH 1/3] 4a930de1 patch apply pass 1 without rejs
5+
6+
---
7+
src/twisted/web/http.py | 2 +-
8+
src/twisted/web/newsfragments/12248.bugfix | 1 +
9+
src/twisted/web/test/test_http.py | 120 ++++++++++++++++++---
10+
3 files changed, 109 insertions(+), 14 deletions(-)
11+
create mode 100644 src/twisted/web/newsfragments/12248.bugfix
12+
13+
diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
14+
index b80a55a..2c3ba55 100644
15+
--- a/src/twisted/web/http.py
16+
+++ b/src/twisted/web/http.py
17+
@@ -2331,8 +2333,8 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
18+
self.__header = line
19+
20+
def _finishRequestBody(self, data):
21+
- self.allContentReceived()
22+
self._dataBuffer.append(data)
23+
+ self.allContentReceived()
24+
25+
def _maybeChooseTransferDecoder(self, header, data):
26+
"""
27+
diff --git a/src/twisted/web/newsfragments/12248.bugfix b/src/twisted/web/newsfragments/12248.bugfix
28+
new file mode 100644
29+
index 0000000..2fb6067
30+
--- /dev/null
31+
+++ b/src/twisted/web/newsfragments/12248.bugfix
32+
@@ -0,0 +1 @@
33+
+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)
34+
diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
35+
index f8027f1..e07cf98 100644
36+
--- a/src/twisted/web/test/test_http.py
37+
+++ b/src/twisted/web/test/test_http.py
38+
@@ -135,7 +135,7 @@ class DummyHTTPHandler(http.Request):
39+
data = self.content.read()
40+
length = self.getHeader(b"content-length")
41+
if length is None:
42+
- length = networkString(str(length))
43+
+ length = str(length).encode()
44+
request = b"'''\n" + length + b"\n" + data + b"'''\n"
45+
self.setResponseCode(200)
46+
self.setHeader(b"Request", self.uri)
47+
@@ -566,17 +566,23 @@ class HTTP0_9Tests(HTTP1_0Tests):
48+
49+
class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
50+
"""
51+
- Tests that multiple pipelined requests with bodies are correctly buffered.
52+
+ Pipelined requests get buffered and executed in the order received,
53+
+ not processed in parallel.
54+
"""
55+
56+
requests = (
57+
b"POST / HTTP/1.1\r\n"
58+
b"Content-Length: 10\r\n"
59+
b"\r\n"
60+
- b"0123456789POST / HTTP/1.1\r\n"
61+
- b"Content-Length: 10\r\n"
62+
- b"\r\n"
63+
b"0123456789"
64+
+ # Chunk encoded request.
65+
+ b"POST / HTTP/1.1\r\n"
66+
+ b"Transfer-Encoding: chunked\r\n"
67+
+ b"\r\n"
68+
+ b"a\r\n"
69+
+ b"0123456789\r\n"
70+
+ b"0\r\n"
71+
+ b"\r\n"
72+
)
73+
74+
expectedResponses = [
75+
@@ -593,14 +599,16 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
76+
b"Request: /",
77+
b"Command: POST",
78+
b"Version: HTTP/1.1",
79+
- b"Content-Length: 21",
80+
- b"'''\n10\n0123456789'''\n",
81+
+ b"Content-Length: 23",
82+
+ b"'''\nNone\n0123456789'''\n",
83+
),
84+
]
85+
86+
- def test_noPipelining(self):
87+
+ def test_stepwiseTinyTube(self):
88+
"""
89+
- Test that pipelined requests get buffered, not processed in parallel.
90+
+ Imitate a slow connection that delivers one byte at a time.
91+
+ The request handler (L{DelayedHTTPHandler}) is puppeted to
92+
+ step through the handling of each request.
93+
"""
94+
b = StringTransport()
95+
a = http.HTTPChannel()
96+
@@ -609,10 +617,9 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
97+
# one byte at a time, to stress it.
98+
for byte in iterbytes(self.requests):
99+
a.dataReceived(byte)
100+
- value = b.value()
101+
102+
# So far only one request should have been dispatched.
103+
- self.assertEqual(value, b"")
104+
+ self.assertEqual(b.value(), b"")
105+
self.assertEqual(1, len(a.requests))
106+
107+
# Now, process each request one at a time.
108+
@@ -621,8 +628,95 @@ class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
109+
request = a.requests[0].original
110+
request.delayedProcess()
111+
112+
- value = b.value()
113+
- self.assertResponseEquals(value, self.expectedResponses)
114+
+ self.assertResponseEquals(b.value(), self.expectedResponses)
115+
+
116+
+ def test_stepwiseDumpTruck(self):
117+
+ """
118+
+ Imitate a fast connection where several pipelined
119+
+ requests arrive in a single read. The request handler
120+
+ (L{DelayedHTTPHandler}) is puppeted to step through the
121+
+ handling of each request.
122+
+ """
123+
+ b = StringTransport()
124+
+ a = http.HTTPChannel()
125+
+ a.requestFactory = DelayedHTTPHandlerProxy
126+
+ a.makeConnection(b)
127+
+
128+
+ a.dataReceived(self.requests)
129+
+
130+
+ # So far only one request should have been dispatched.
131+
+ self.assertEqual(b.value(), b"")
132+
+ self.assertEqual(1, len(a.requests))
133+
+
134+
+ # Now, process each request one at a time.
135+
+ while a.requests:
136+
+ self.assertEqual(1, len(a.requests))
137+
+ request = a.requests[0].original
138+
+ request.delayedProcess()
139+
+
140+
+ self.assertResponseEquals(b.value(), self.expectedResponses)
141+
+
142+
+ def test_immediateTinyTube(self):
143+
+ """
144+
+ Imitate a slow connection that delivers one byte at a time.
145+
+
146+
+ (L{DummyHTTPHandler}) immediately responds, but no more
147+
+ than one
148+
+ """
149+
+ b = StringTransport()
150+
+ a = http.HTTPChannel()
151+
+ a.requestFactory = DummyHTTPHandlerProxy # "sync"
152+
+ a.makeConnection(b)
153+
+
154+
+ # one byte at a time, to stress it.
155+
+ for byte in iterbytes(self.requests):
156+
+ a.dataReceived(byte)
157+
+ # There is never more than one request dispatched at a time:
158+
+ self.assertLessEqual(len(a.requests), 1)
159+
+
160+
+ self.assertResponseEquals(b.value(), self.expectedResponses)
161+
+
162+
+ def test_immediateDumpTruck(self):
163+
+ """
164+
+ Imitate a fast connection where several pipelined
165+
+ requests arrive in a single read. The request handler
166+
+ (L{DummyHTTPHandler}) immediately responds.
167+
+
168+
+ This doesn't check the at-most-one pending request
169+
+ invariant but exercises otherwise uncovered code paths.
170+
+ See GHSA-c8m8-j448-xjx7.
171+
+ """
172+
+ b = StringTransport()
173+
+ a = http.HTTPChannel()
174+
+ a.requestFactory = DummyHTTPHandlerProxy
175+
+ a.makeConnection(b)
176+
+
177+
+ # All bytes at once to ensure there's stuff to buffer.
178+
+ a.dataReceived(self.requests)
179+
+
180+
+ self.assertResponseEquals(b.value(), self.expectedResponses)
181+
+
182+
+ def test_immediateABiggerTruck(self):
183+
+ """
184+
+ Imitate a fast connection where a so many pipelined
185+
+ requests arrive in a single read that backpressure is indicated.
186+
+ The request handler (L{DummyHTTPHandler}) immediately responds.
187+
+
188+
+ This doesn't check the at-most-one pending request
189+
+ invariant but exercises otherwise uncovered code paths.
190+
+ See GHSA-c8m8-j448-xjx7.
191+
+
192+
+ @see: L{http.HTTPChannel._optimisticEagerReadSize}
193+
+ """
194+
+ b = StringTransport()
195+
+ a = http.HTTPChannel()
196+
+ a.requestFactory = DummyHTTPHandlerProxy
197+
+ a.makeConnection(b)
198+
+
199+
+ overLimitCount = a._optimisticEagerReadSize // len(self.requests) * 10
200+
+ a.dataReceived(self.requests * overLimitCount)
201+
+
202+
+ self.assertResponseEquals(b.value(), self.expectedResponses * overLimitCount)
203+
204+
def test_pipeliningReadLimit(self):
205+
"""
206+
--
207+
2.33.8
208+

0 commit comments

Comments
 (0)