Files
meta-openembedded/meta-python/recipes-devtools/python/python3-werkzeug/CVE-2024-49767.patch
Soumya Sambu c59e8e9dbc python3-werkzeug: Fix CVE-2024-49767
Werkzeug is a Web Server Gateway Interface web application library. Applications
using `werkzeug.formparser.MultiPartParser` corresponding to a version of Werkzeug
prior to 3.0.6 to parse `multipart/form-data` requests (e.g. all flask applications)
are vulnerable to a relatively simple but effective resource exhaustion (denial of
service) attack. A specifically crafted form submission request can cause the parser
to allocate and block 3 to 8 times the upload size in main memory. There is no upper
limit; a single upload at 1 Gbit/s can exhaust 32 GB of RAM in less than 60 seconds.
Werkzeug version 3.0.6 fixes this issue.

Reference:
https://nvd.nist.gov/vuln/detail/CVE-2024-49767

Upstream-patch:
8760275afb

Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
2024-12-31 09:04:08 -05:00

88 lines
3.8 KiB
Diff

From 8760275afb72bd10b57d92cb4d52abf759b2f3a7 Mon Sep 17 00:00:00 2001
From: David Lord <davidism@gmail.com>
Date: Fri, 25 Oct 2024 06:46:50 -0700
Subject: [PATCH] apply max_form_memory_size another level up in the parser
CVE: CVE-2024-49767
Upstream-Status: Backport [https://github.com/pallets/werkzeug/commit/8760275afb72bd10b57d92cb4d52abf759b2f3a7]
Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
---
src/werkzeug/formparser.py | 11 +++++++++++
src/werkzeug/sansio/multipart.py | 2 ++
tests/test_formparser.py | 12 ++++++++++++
3 files changed, 25 insertions(+)
diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py
index bebb2fc..b82af82 100644
--- a/src/werkzeug/formparser.py
+++ b/src/werkzeug/formparser.py
@@ -405,6 +405,7 @@ class MultiPartParser:
def parse(
self, stream: t.IO[bytes], boundary: bytes, content_length: t.Optional[int]
) -> t.Tuple[MultiDict, MultiDict]:
+ field_size: int | None = None
container: t.Union[t.IO[bytes], t.List[bytes]]
_write: t.Callable[[bytes], t.Any]
@@ -431,13 +432,23 @@ class MultiPartParser:
while not isinstance(event, (Epilogue, NeedData)):
if isinstance(event, Field):
current_part = event
+ field_size = 0
container = []
_write = container.append
elif isinstance(event, File):
current_part = event
+ field_size = None
container = self.start_file_streaming(event, content_length)
_write = container.write
elif isinstance(event, Data):
+ if self.max_form_memory_size is not None and field_size is not None:
+ # Ensure that accumulated data events do not exceed limit.
+ # Also checked within single event in MultipartDecoder.
+ field_size += len(event.data)
+
+ if field_size > self.max_form_memory_size:
+ raise RequestEntityTooLarge()
+
_write(event.data)
if not event.more_data:
if isinstance(current_part, Field):
diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
index e7d742b..a91fedd 100644
--- a/src/werkzeug/sansio/multipart.py
+++ b/src/werkzeug/sansio/multipart.py
@@ -137,6 +137,8 @@ class MultipartDecoder:
self.max_form_memory_size is not None
and len(self.buffer) + len(data) > self.max_form_memory_size
):
+ # Ensure that data within single event does not exceed limit.
+ # Also checked across accumulated events in MultiPartParser.
raise RequestEntityTooLarge()
else:
self.buffer.extend(data)
diff --git a/tests/test_formparser.py b/tests/test_formparser.py
index 834324f..1a178dd 100644
--- a/tests/test_formparser.py
+++ b/tests/test_formparser.py
@@ -459,3 +459,15 @@ class TestMultiPartParser:
)
assert request.files["rfc2231"].filename == "a b c d e f.txt"
assert request.files["rfc2231"].read() == b"file contents"
+
+
+def test_multipart_max_form_memory_size() -> None:
+ """max_form_memory_size is tracked across multiple data events."""
+ data = b"--bound\r\nContent-Disposition: form-field; name=a\r\n\r\n"
+ data += b"a" * 15 + b"\r\n--bound--"
+ # The buffer size is less than the max size, so multiple data events will be
+ # returned. The field size is greater than the max.
+ parser = formparser.MultiPartParser(max_form_memory_size=10, buffer_size=5)
+
+ with pytest.raises(RequestEntityTooLarge):
+ parser.parse(io.BytesIO(data), b"bound", None)
--
2.40.0