summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python
diff options
context:
space:
mode:
authorNarpat Mali <narpat.mali@windriver.com>2023-05-04 11:47:20 +0000
committerArmin Kuster <akuster808@gmail.com>2023-05-06 07:41:57 -0400
commit996dddd658cffe51ea7bab872591f367e29f74bd (patch)
treebb0bb962d4eda426f65ba272a143d06b1a2a17c5 /meta-python/recipes-devtools/python
parent3deca451692e99d269812959e6b5e0b519a82f30 (diff)
downloadmeta-openembedded-996dddd658cffe51ea7bab872591f367e29f74bd.tar.gz
python3-werkzeug: fix for CVE-2023-25577
Werkzeug is a comprehensive WSGI web application library. Prior to version 2.2.3, Werkzeug's multipart form data parser will parse an unlimited number of parts, including file parts. Parts can be a small amount of bytes, but each requires CPU time to parse and may use more memory as Python data. If a request can be made to an endpoint that accesses `request.data`, `request.form`, `request.files`, or `request.get_data(parse_form_data=False)`, it can cause unexpectedly high resource usage. This allows an attacker to cause a denial of service by sending crafted multipart data to an endpoint that will parse it. The amount of CPU time required can block worker processes from handling legitimate requests. The amount of RAM required can trigger an out of memory kill of the process. Unlimited file parts can use up memory and file handles. If many concurrent requests are sent continuously, this can exhaust or kill all available workers. Version 2.2.3 contains a patch for this issue. Signed-off-by: Narpat Mali <narpat.mali@windriver.com> Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python')
-rw-r--r--meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch231
-rw-r--r--meta-python/recipes-devtools/python/python3-werkzeug_2.1.1.bb2
2 files changed, 233 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch b/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch
new file mode 100644
index 0000000000..61551d8fca
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch
@@ -0,0 +1,231 @@
1From 5a56cdcbaec2153cd67596c6c2c8056e1ea5ed56 Mon Sep 17 00:00:00 2001
2From: David Lord <davidism@gmail.com>
3Date: Tue, 2 May 2023 11:31:10 +0000
4Subject: [PATCH] Merge pull request from GHSA-xg9f-g7g7-2323
5
6limit the maximum number of multipart form parts
7
8CVE: CVE-2023-25577
9
10Upstream-Status: Backport [https://github.com/pallets/werkzeug/commit/517cac5a804e8c4dc4ed038bb20dacd038e7a9f1]
11
12Signed-off-by: Narpat Mali <narpat.mali@windriver.com>
13---
14 CHANGES.rst | 5 +++++
15 docs/request_data.rst | 37 +++++++++++++++++---------------
16 src/werkzeug/formparser.py | 12 ++++++++++-
17 src/werkzeug/sansio/multipart.py | 8 +++++++
18 src/werkzeug/wrappers/request.py | 8 +++++++
19 tests/test_formparser.py | 9 ++++++++
20 6 files changed, 61 insertions(+), 18 deletions(-)
21
22diff --git a/CHANGES.rst b/CHANGES.rst
23index a351d7c..6e809ba 100644
24--- a/CHANGES.rst
25+++ b/CHANGES.rst
26@@ -1,5 +1,10 @@
27 .. currentmodule:: werkzeug
28
29+- Specify a maximum number of multipart parts, default 1000, after which a
30+ ``RequestEntityTooLarge`` exception is raised on parsing. This mitigates a DoS
31+ attack where a larger number of form/file parts would result in disproportionate
32+ resource use.
33+
34 Version 2.1.1
35 -------------
36
37diff --git a/docs/request_data.rst b/docs/request_data.rst
38index 83c6278..e55841e 100644
39--- a/docs/request_data.rst
40+++ b/docs/request_data.rst
41@@ -73,23 +73,26 @@ read the stream *or* call :meth:`~Request.get_data`.
42 Limiting Request Data
43 ---------------------
44
45-To avoid being the victim of a DDOS attack you can set the maximum
46-accepted content length and request field sizes. The :class:`Request`
47-class has two attributes for that: :attr:`~Request.max_content_length`
48-and :attr:`~Request.max_form_memory_size`.
49-
50-The first one can be used to limit the total content length. For example
51-by setting it to ``1024 * 1024 * 16`` the request won't accept more than
52-16MB of transmitted data.
53-
54-Because certain data can't be moved to the hard disk (regular post data)
55-whereas temporary files can, there is a second limit you can set. The
56-:attr:`~Request.max_form_memory_size` limits the size of `POST`
57-transmitted form data. By setting it to ``1024 * 1024 * 2`` you can make
58-sure that all in memory-stored fields are not more than 2MB in size.
59-
60-This however does *not* affect in-memory stored files if the
61-`stream_factory` used returns a in-memory file.
62+The :class:`Request` class provides a few attributes to control how much data is
63+processed from the request body. This can help mitigate DoS attacks that craft the
64+request in such a way that the server uses too many resources to handle it. Each of
65+these limits will raise a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` if they are
66+exceeded.
67+
68+- :attr:`~Request.max_content_length` Stop reading request data after this number
69+ of bytes. It's better to configure this in the WSGI server or HTTP server, rather
70+ than the WSGI application.
71+- :attr:`~Request.max_form_memory_size` Stop reading request data if any form part is
72+ larger than this number of bytes. While file parts can be moved to disk, regular
73+ form field data is stored in memory only.
74+- :attr:`~Request.max_form_parts` Stop reading request data if more than this number
75+ of parts are sent in multipart form data. This is useful to stop a very large number
76+ of very small parts, especially file parts. The default is 1000.
77+
78+Using Werkzeug to set these limits is only one layer of protection. WSGI servers
79+and HTTPS servers should set their own limits on size and timeouts. The operating system
80+or container manager should set limits on memory and processing time for server
81+processes.
82
83
84 How to extend Parsing?
85diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py
86index 10d58ca..bebb2fc 100644
87--- a/src/werkzeug/formparser.py
88+++ b/src/werkzeug/formparser.py
89@@ -179,6 +179,8 @@ class FormDataParser:
90 :param cls: an optional dict class to use. If this is not specified
91 or `None` the default :class:`MultiDict` is used.
92 :param silent: If set to False parsing errors will not be caught.
93+ :param max_form_parts: The maximum number of parts to be parsed. If this is
94+ exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
95 """
96
97 def __init__(
98@@ -190,6 +192,8 @@ class FormDataParser:
99 max_content_length: t.Optional[int] = None,
100 cls: t.Optional[t.Type[MultiDict]] = None,
101 silent: bool = True,
102+ *,
103+ max_form_parts: t.Optional[int] = None,
104 ) -> None:
105 if stream_factory is None:
106 stream_factory = default_stream_factory
107@@ -199,6 +203,7 @@ class FormDataParser:
108 self.errors = errors
109 self.max_form_memory_size = max_form_memory_size
110 self.max_content_length = max_content_length
111+ self.max_form_parts = max_form_parts
112
113 if cls is None:
114 cls = MultiDict
115@@ -281,6 +286,7 @@ class FormDataParser:
116 self.errors,
117 max_form_memory_size=self.max_form_memory_size,
118 cls=self.cls,
119+ max_form_parts=self.max_form_parts,
120 )
121 boundary = options.get("boundary", "").encode("ascii")
122
123@@ -346,10 +352,12 @@ class MultiPartParser:
124 max_form_memory_size: t.Optional[int] = None,
125 cls: t.Optional[t.Type[MultiDict]] = None,
126 buffer_size: int = 64 * 1024,
127+ max_form_parts: t.Optional[int] = None,
128 ) -> None:
129 self.charset = charset
130 self.errors = errors
131 self.max_form_memory_size = max_form_memory_size
132+ self.max_form_parts = max_form_parts
133
134 if stream_factory is None:
135 stream_factory = default_stream_factory
136@@ -409,7 +417,9 @@ class MultiPartParser:
137 [None],
138 )
139
140- parser = MultipartDecoder(boundary, self.max_form_memory_size)
141+ parser = MultipartDecoder(
142+ boundary, self.max_form_memory_size, max_parts=self.max_form_parts
143+ )
144
145 fields = []
146 files = []
147diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
148index 2d54422..e7d742b 100644
149--- a/src/werkzeug/sansio/multipart.py
150+++ b/src/werkzeug/sansio/multipart.py
151@@ -83,10 +83,13 @@ class MultipartDecoder:
152 self,
153 boundary: bytes,
154 max_form_memory_size: Optional[int] = None,
155+ *,
156+ max_parts: Optional[int] = None,
157 ) -> None:
158 self.buffer = bytearray()
159 self.complete = False
160 self.max_form_memory_size = max_form_memory_size
161+ self.max_parts = max_parts
162 self.state = State.PREAMBLE
163 self.boundary = boundary
164
165@@ -113,6 +116,7 @@ class MultipartDecoder:
166 % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
167 re.MULTILINE,
168 )
169+ self._parts_decoded = 0
170
171 def last_newline(self) -> int:
172 try:
173@@ -177,6 +181,10 @@ class MultipartDecoder:
174 name=name,
175 )
176 self.state = State.DATA
177+ self._parts_decoded += 1
178+
179+ if self.max_parts is not None and self._parts_decoded > self.max_parts:
180+ raise RequestEntityTooLarge()
181
182 elif self.state == State.DATA:
183 if self.buffer.find(b"--" + self.boundary) == -1:
184diff --git a/src/werkzeug/wrappers/request.py b/src/werkzeug/wrappers/request.py
185index 57b739c..a6d5429 100644
186--- a/src/werkzeug/wrappers/request.py
187+++ b/src/werkzeug/wrappers/request.py
188@@ -83,6 +83,13 @@ class Request(_SansIORequest):
189 #: .. versionadded:: 0.5
190 max_form_memory_size: t.Optional[int] = None
191
192+ #: The maximum number of multipart parts to parse, passed to
193+ #: :attr:`form_data_parser_class`. Parsing form data with more than this
194+ #: many parts will raise :exc:`~.RequestEntityTooLarge`.
195+ #:
196+ #: .. versionadded:: 2.2.3
197+ max_form_parts = 1000
198+
199 #: The form data parser that should be used. Can be replaced to customize
200 #: the form date parsing.
201 form_data_parser_class: t.Type[FormDataParser] = FormDataParser
202@@ -246,6 +253,7 @@ class Request(_SansIORequest):
203 self.max_form_memory_size,
204 self.max_content_length,
205 self.parameter_storage_class,
206+ max_form_parts=self.max_form_parts,
207 )
208
209 def _load_form_data(self) -> None:
210diff --git a/tests/test_formparser.py b/tests/test_formparser.py
211index 5fc803e..834324f 100644
212--- a/tests/test_formparser.py
213+++ b/tests/test_formparser.py
214@@ -127,6 +127,15 @@ class TestFormParser:
215 req.max_form_memory_size = 400
216 assert req.form["foo"] == "Hello World"
217
218+ req = Request.from_values(
219+ input_stream=io.BytesIO(data),
220+ content_length=len(data),
221+ content_type="multipart/form-data; boundary=foo",
222+ method="POST",
223+ )
224+ req.max_form_parts = 1
225+ pytest.raises(RequestEntityTooLarge, lambda: req.form["foo"])
226+
227 def test_missing_multipart_boundary(self):
228 data = (
229 b"--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n"
230--
2312.40.0
diff --git a/meta-python/recipes-devtools/python/python3-werkzeug_2.1.1.bb b/meta-python/recipes-devtools/python/python3-werkzeug_2.1.1.bb
index 476a3a5964..324a4b7996 100644
--- a/meta-python/recipes-devtools/python/python3-werkzeug_2.1.1.bb
+++ b/meta-python/recipes-devtools/python/python3-werkzeug_2.1.1.bb
@@ -12,6 +12,8 @@ LIC_FILES_CHKSUM = "file://LICENSE.rst;md5=5dc88300786f1c214c1e9827a5229462"
12 12
13PYPI_PACKAGE = "Werkzeug" 13PYPI_PACKAGE = "Werkzeug"
14 14
15SRC_URI += "file://CVE-2023-25577.patch"
16
15SRC_URI[sha256sum] = "f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74" 17SRC_URI[sha256sum] = "f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"
16 18
17inherit pypi setuptools3 19inherit pypi setuptools3