From 9f55e38361fc490b39f43dfef99a417f27d77e04fb63c1903cea8675bcbe9165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Schr=C3=B6ter?= Date: Thu, 17 Oct 2024 13:47:00 +0200 Subject: [PATCH] Sync from SUSE:SLFO:1.1 python-starlette revision aba60422952993bcd553dc56e08daa63 --- ...47874-multipart-form-data-part-limit.patch | 106 ++++++++++++++++++ python-starlette.changes | 7 ++ python-starlette.spec | 4 +- 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 CVE-2024-47874-multipart-form-data-part-limit.patch diff --git a/CVE-2024-47874-multipart-form-data-part-limit.patch b/CVE-2024-47874-multipart-form-data-part-limit.patch new file mode 100644 index 0000000..d164893 --- /dev/null +++ b/CVE-2024-47874-multipart-form-data-part-limit.patch @@ -0,0 +1,106 @@ +From fd038f3070c302bff17ef7d173dbb0b007617733 Mon Sep 17 00:00:00 2001 +From: Marcelo Trylesinski +Date: Tue, 15 Oct 2024 08:40:51 +0200 +Subject: [PATCH] Merge commit from fork + +--- + starlette/formparsers.py | 11 +++++++---- + tests/test_formparsers.py | 41 ++++++++++++++++++++++++++++++++++++--- + 2 files changed, 45 insertions(+), 7 deletions(-) + +Index: starlette-0.38.5/starlette/formparsers.py +=================================================================== +--- starlette-0.38.5.orig/starlette/formparsers.py ++++ starlette-0.38.5/starlette/formparsers.py +@@ -28,12 +28,12 @@ class FormMessage(Enum): + class MultipartPart: + content_disposition: bytes | None = None + field_name: str = "" +- data: bytes = b"" ++ data: bytearray = field(default_factory=bytearray) + file: UploadFile | None = None + item_headers: list[tuple[bytes, bytes]] = field(default_factory=list) + + +-def _user_safe_decode(src: bytes, codec: str) -> str: ++def _user_safe_decode(src: bytes | bytearray, codec: str) -> str: + try: + return src.decode(codec) + except (UnicodeDecodeError, LookupError): +@@ -114,7 +114,8 @@ class FormParser: + + + class MultiPartParser: +- max_file_size = 1024 * 1024 ++ max_file_size = 1024 * 1024 # 1MB ++ max_part_size = 1024 * 1024 # 1MB + + def __init__( + self, +@@ -146,7 +147,9 @@ class MultiPartParser: + def on_part_data(self, data: bytes, start: int, end: int) -> None: + message_bytes = data[start:end] + if self._current_part.file is None: +- self._current_part.data += message_bytes ++ if len(self._current_part.data) + len(message_bytes) > self.max_part_size: ++ raise MultiPartException(f"Part exceeded maximum size of {int(self.max_part_size / 1024)}KB.") ++ self._current_part.data.extend(message_bytes) + else: + self._file_parts_to_write.append((self._current_part, message_bytes)) + +Index: starlette-0.38.5/tests/test_formparsers.py +=================================================================== +--- starlette-0.38.5.orig/tests/test_formparsers.py ++++ starlette-0.38.5/tests/test_formparsers.py +@@ -640,9 +640,7 @@ def test_max_files_is_customizable_low_r + assert res.text == "Too many files. Maximum number of files is 1." + + +-def test_max_fields_is_customizable_high( +- test_client_factory: TestClientFactory, +-) -> None: ++def test_max_fields_is_customizable_high(test_client_factory: TestClientFactory) -> None: + client = test_client_factory(make_app_max_parts(max_fields=2000, max_files=2000)) + fields = [] + for i in range(2000): +@@ -664,3 +662,40 @@ def test_max_fields_is_customizable_high + "content": "", + "content_type": None, + } ++ ++ ++@pytest.mark.parametrize( ++ "app,expectation", ++ [ ++ (app, pytest.raises(MultiPartException)), ++ (Starlette(routes=[Mount("/", app=app)]), does_not_raise()), ++ ], ++) ++def test_max_part_size_exceeds_limit( ++ app: ASGIApp, ++ expectation: typing.ContextManager[Exception], ++ test_client_factory: TestClientFactory, ++) -> None: ++ client = test_client_factory(app) ++ boundary = "------------------------4K1ON9fZkj9uCUmqLHRbbR" ++ ++ multipart_data = ( ++ f"--{boundary}\r\n" ++ f'Content-Disposition: form-data; name="small"\r\n\r\n' ++ "small content\r\n" ++ f"--{boundary}\r\n" ++ f'Content-Disposition: form-data; name="large"\r\n\r\n' ++ + ("x" * 1024 * 1024 + "x") # 1MB + 1 byte of data ++ + "\r\n" ++ f"--{boundary}--\r\n" ++ ).encode("utf-8") ++ ++ headers = { ++ "Content-Type": f"multipart/form-data; boundary={boundary}", ++ "Transfer-Encoding": "chunked", ++ } ++ ++ with expectation: ++ response = client.post("/", data=multipart_data, headers=headers) # type: ignore ++ assert response.status_code == 400 ++ assert response.text == "Part exceeded maximum size of 1024KB." diff --git a/python-starlette.changes b/python-starlette.changes index 380c439..7921134 100644 --- a/python-starlette.changes +++ b/python-starlette.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Oct 17 02:48:03 UTC 2024 - Steve Kowalik + +- Add patch CVE-2024-47874-multipart-form-data-part-limit.patch: + * Add max_part_size to MultiPartParser to limit the size of parts in + multipart/form-data requests. (bsc#1231689, CVE-2024-47874) + ------------------------------------------------------------------- Sun Sep 8 15:05:40 UTC 2024 - Dirk Müller diff --git a/python-starlette.spec b/python-starlette.spec index 53bef6d..5c25d9e 100644 --- a/python-starlette.spec +++ b/python-starlette.spec @@ -33,6 +33,8 @@ Summary: Lightweight ASGI framework/toolkit License: BSD-3-Clause URL: https://github.com/encode/starlette Source: https://github.com/encode/starlette/archive/refs/tags/%{version}.tar.gz#/starlette-%{version}.tar.gz +# PATCH-FIX-UPSTREAM gh#encode/starlette#fd038f3070c302bff17ef7d173dbb0b007617733 +Patch0: CVE-2024-47874-multipart-form-data-part-limit.patch BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} @@ -69,7 +71,7 @@ Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high performance asyncio services. %prep -%autosetup -n starlette-%{version} +%autosetup -p1 -n starlette-%{version} %build %pyproject_wheel