summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch')
-rw-r--r--meta-python/recipes-devtools/python/python3-werkzeug/CVE-2023-25577.patch231
1 files changed, 231 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