aboutsummaryrefslogtreecommitdiffstats
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 @@
+From 5a56cdcbaec2153cd67596c6c2c8056e1ea5ed56 Mon Sep 17 00:00:00 2001
+From: David Lord <davidism@gmail.com>
+Date: Tue, 2 May 2023 11:31:10 +0000
+Subject: [PATCH] Merge pull request from GHSA-xg9f-g7g7-2323
+
+limit the maximum number of multipart form parts
+
+CVE: CVE-2023-25577
+
+Upstream-Status: Backport [https://github.com/pallets/werkzeug/commit/517cac5a804e8c4dc4ed038bb20dacd038e7a9f1]
+
+Signed-off-by: Narpat Mali <narpat.mali@windriver.com>
+---
+ CHANGES.rst | 5 +++++
+ docs/request_data.rst | 37 +++++++++++++++++---------------
+ src/werkzeug/formparser.py | 12 ++++++++++-
+ src/werkzeug/sansio/multipart.py | 8 +++++++
+ src/werkzeug/wrappers/request.py | 8 +++++++
+ tests/test_formparser.py | 9 ++++++++
+ 6 files changed, 61 insertions(+), 18 deletions(-)
+
+diff --git a/CHANGES.rst b/CHANGES.rst
+index a351d7c..6e809ba 100644
+--- a/CHANGES.rst
++++ b/CHANGES.rst
+@@ -1,5 +1,10 @@
+ .. currentmodule:: werkzeug
+
++- Specify a maximum number of multipart parts, default 1000, after which a
++ ``RequestEntityTooLarge`` exception is raised on parsing. This mitigates a DoS
++ attack where a larger number of form/file parts would result in disproportionate
++ resource use.
++
+ Version 2.1.1
+ -------------
+
+diff --git a/docs/request_data.rst b/docs/request_data.rst
+index 83c6278..e55841e 100644
+--- a/docs/request_data.rst
++++ b/docs/request_data.rst
+@@ -73,23 +73,26 @@ read the stream *or* call :meth:`~Request.get_data`.
+ Limiting Request Data
+ ---------------------
+
+-To avoid being the victim of a DDOS attack you can set the maximum
+-accepted content length and request field sizes. The :class:`Request`
+-class has two attributes for that: :attr:`~Request.max_content_length`
+-and :attr:`~Request.max_form_memory_size`.
+-
+-The first one can be used to limit the total content length. For example
+-by setting it to ``1024 * 1024 * 16`` the request won't accept more than
+-16MB of transmitted data.
+-
+-Because certain data can't be moved to the hard disk (regular post data)
+-whereas temporary files can, there is a second limit you can set. The
+-:attr:`~Request.max_form_memory_size` limits the size of `POST`
+-transmitted form data. By setting it to ``1024 * 1024 * 2`` you can make
+-sure that all in memory-stored fields are not more than 2MB in size.
+-
+-This however does *not* affect in-memory stored files if the
+-`stream_factory` used returns a in-memory file.
++The :class:`Request` class provides a few attributes to control how much data is
++processed from the request body. This can help mitigate DoS attacks that craft the
++request in such a way that the server uses too many resources to handle it. Each of
++these limits will raise a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` if they are
++exceeded.
++
++- :attr:`~Request.max_content_length` Stop reading request data after this number
++ of bytes. It's better to configure this in the WSGI server or HTTP server, rather
++ than the WSGI application.
++- :attr:`~Request.max_form_memory_size` Stop reading request data if any form part is
++ larger than this number of bytes. While file parts can be moved to disk, regular
++ form field data is stored in memory only.
++- :attr:`~Request.max_form_parts` Stop reading request data if more than this number
++ of parts are sent in multipart form data. This is useful to stop a very large number
++ of very small parts, especially file parts. The default is 1000.
++
++Using Werkzeug to set these limits is only one layer of protection. WSGI servers
++and HTTPS servers should set their own limits on size and timeouts. The operating system
++or container manager should set limits on memory and processing time for server
++processes.
+
+
+ How to extend Parsing?
+diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py
+index 10d58ca..bebb2fc 100644
+--- a/src/werkzeug/formparser.py
++++ b/src/werkzeug/formparser.py
+@@ -179,6 +179,8 @@ class FormDataParser:
+ :param cls: an optional dict class to use. If this is not specified
+ or `None` the default :class:`MultiDict` is used.
+ :param silent: If set to False parsing errors will not be caught.
++ :param max_form_parts: The maximum number of parts to be parsed. If this is
++ exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
+ """
+
+ def __init__(
+@@ -190,6 +192,8 @@ class FormDataParser:
+ max_content_length: t.Optional[int] = None,
+ cls: t.Optional[t.Type[MultiDict]] = None,
+ silent: bool = True,
++ *,
++ max_form_parts: t.Optional[int] = None,
+ ) -> None:
+ if stream_factory is None:
+ stream_factory = default_stream_factory
+@@ -199,6 +203,7 @@ class FormDataParser:
+ self.errors = errors
+ self.max_form_memory_size = max_form_memory_size
+ self.max_content_length = max_content_length
++ self.max_form_parts = max_form_parts
+
+ if cls is None:
+ cls = MultiDict
+@@ -281,6 +286,7 @@ class FormDataParser:
+ self.errors,
+ max_form_memory_size=self.max_form_memory_size,
+ cls=self.cls,
++ max_form_parts=self.max_form_parts,
+ )
+ boundary = options.get("boundary", "").encode("ascii")
+
+@@ -346,10 +352,12 @@ class MultiPartParser:
+ max_form_memory_size: t.Optional[int] = None,
+ cls: t.Optional[t.Type[MultiDict]] = None,
+ buffer_size: int = 64 * 1024,
++ max_form_parts: t.Optional[int] = None,
+ ) -> None:
+ self.charset = charset
+ self.errors = errors
+ self.max_form_memory_size = max_form_memory_size
++ self.max_form_parts = max_form_parts
+
+ if stream_factory is None:
+ stream_factory = default_stream_factory
+@@ -409,7 +417,9 @@ class MultiPartParser:
+ [None],
+ )
+
+- parser = MultipartDecoder(boundary, self.max_form_memory_size)
++ parser = MultipartDecoder(
++ boundary, self.max_form_memory_size, max_parts=self.max_form_parts
++ )
+
+ fields = []
+ files = []
+diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
+index 2d54422..e7d742b 100644
+--- a/src/werkzeug/sansio/multipart.py
++++ b/src/werkzeug/sansio/multipart.py
+@@ -83,10 +83,13 @@ class MultipartDecoder:
+ self,
+ boundary: bytes,
+ max_form_memory_size: Optional[int] = None,
++ *,
++ max_parts: Optional[int] = None,
+ ) -> None:
+ self.buffer = bytearray()
+ self.complete = False
+ self.max_form_memory_size = max_form_memory_size
++ self.max_parts = max_parts
+ self.state = State.PREAMBLE
+ self.boundary = boundary
+
+@@ -113,6 +116,7 @@ class MultipartDecoder:
+ % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
+ re.MULTILINE,
+ )
++ self._parts_decoded = 0
+
+ def last_newline(self) -> int:
+ try:
+@@ -177,6 +181,10 @@ class MultipartDecoder:
+ name=name,
+ )
+ self.state = State.DATA
++ self._parts_decoded += 1
++
++ if self.max_parts is not None and self._parts_decoded > self.max_parts:
++ raise RequestEntityTooLarge()
+
+ elif self.state == State.DATA:
+ if self.buffer.find(b"--" + self.boundary) == -1:
+diff --git a/src/werkzeug/wrappers/request.py b/src/werkzeug/wrappers/request.py
+index 57b739c..a6d5429 100644
+--- a/src/werkzeug/wrappers/request.py
++++ b/src/werkzeug/wrappers/request.py
+@@ -83,6 +83,13 @@ class Request(_SansIORequest):
+ #: .. versionadded:: 0.5
+ max_form_memory_size: t.Optional[int] = None
+
++ #: The maximum number of multipart parts to parse, passed to
++ #: :attr:`form_data_parser_class`. Parsing form data with more than this
++ #: many parts will raise :exc:`~.RequestEntityTooLarge`.
++ #:
++ #: .. versionadded:: 2.2.3
++ max_form_parts = 1000
++
+ #: The form data parser that should be used. Can be replaced to customize
+ #: the form date parsing.
+ form_data_parser_class: t.Type[FormDataParser] = FormDataParser
+@@ -246,6 +253,7 @@ class Request(_SansIORequest):
+ self.max_form_memory_size,
+ self.max_content_length,
+ self.parameter_storage_class,
++ max_form_parts=self.max_form_parts,
+ )
+
+ def _load_form_data(self) -> None:
+diff --git a/tests/test_formparser.py b/tests/test_formparser.py
+index 5fc803e..834324f 100644
+--- a/tests/test_formparser.py
++++ b/tests/test_formparser.py
+@@ -127,6 +127,15 @@ class TestFormParser:
+ req.max_form_memory_size = 400
+ assert req.form["foo"] == "Hello World"
+
++ req = Request.from_values(
++ input_stream=io.BytesIO(data),
++ content_length=len(data),
++ content_type="multipart/form-data; boundary=foo",
++ method="POST",
++ )
++ req.max_form_parts = 1
++ pytest.raises(RequestEntityTooLarge, lambda: req.form["foo"])
++
+ def test_missing_multipart_boundary(self):
+ data = (
+ b"--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n"
+--
+2.40.0