python-starlette/CVE-2024-47874-multipart-form-data-part-limit.patch

86 lines
3.2 KiB
Diff

From fd038f3070c302bff17ef7d173dbb0b007617733 Mon Sep 17 00:00:00 2001
From: Marcelo Trylesinski <marcelotryle@gmail.com>
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.26.1/starlette/formparsers.py
===================================================================
--- starlette-0.26.1.orig/starlette/formparsers.py
+++ starlette-0.26.1/starlette/formparsers.py
@@ -26,7 +26,7 @@ class FormMessage(Enum):
class MultipartPart:
content_disposition: typing.Optional[bytes] = None
field_name: str = ""
- data: bytes = b""
+ data: bytearray = field(default_factory=bytearray)
file: typing.Optional[UploadFile] = None
item_headers: typing.List[typing.Tuple[bytes, bytes]] = field(default_factory=list)
@@ -116,7 +116,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,
@@ -150,7 +151,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.26.1/tests/test_formparsers.py
===================================================================
--- starlette-0.26.1.orig/tests/test_formparsers.py
+++ starlette-0.26.1/tests/test_formparsers.py
@@ -682,3 +682,36 @@ 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, expectation, test_client_factory):
+ 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."