CVE-2024-53981
Description
python-multipart is a streaming multipart parser for Python. When parsing form data, python-multipart skips line breaks (CR \r or LF \n) in front of the first boundary and any tailing bytes after the last boundary. This happens one byte at a time and emits a log event each time, which may cause excessive logging for certain inputs. An attacker could abuse this by sending a malicious request with lots of data before the first or after the last boundary, causing high CPU load and stalling the processing thread for a significant amount of time. In case of ASGI application, this could stall the event loop and prevent other requests from being processed, resulting in a denial of service (DoS). This vulnerability is fixed in 0.0.18.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
python-multipartPyPI | < 0.0.18 | 0.0.18 |
Patches
25b1aed83adadc4fe4d3cebc0Don't warn when CRLF is found after last boundary (#193)
5 files changed · +36 −2
CHANGELOG.md+4 −0 modified@@ -1,5 +1,9 @@ # Changelog +## 0.0.19 (2024-11-30) + +* Don't warn when CRLF is found after last boundary on `MultipartParser` [#193](https://github.com/Kludex/python-multipart/pull/193). + ## 0.0.18 (2024-11-28) * Hard break if found data after last boundary on `MultipartParser` [#189](https://github.com/Kludex/python-multipart/pull/189).
python_multipart/__init__.py+1 −1 modified@@ -2,7 +2,7 @@ __author__ = "Andrew Dunham" __license__ = "Apache" __copyright__ = "Copyright (c) 2012-2013, Andrew Dunham" -__version__ = "0.0.18" +__version__ = "0.0.19" from .multipart import ( BaseParser,
python_multipart/multipart.py+4 −0 modified@@ -1397,6 +1397,10 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No i -= 1 elif state == MultipartState.END: + # Don't do anything if chunk ends with CRLF. + if c == CR and i + 1 < length and data[i + 1] == LF: + i += 2 + continue # Skip data after the last boundary. self.logger.warning("Skipping data after last boundary") i = length
scripts/check+1 −1 modified@@ -6,5 +6,5 @@ SOURCE_FILES="python_multipart multipart tests" uvx ruff format --check --diff $SOURCE_FILES uvx ruff check $SOURCE_FILES -uvx --with types-PyYAML mypy $SOURCE_FILES +uv run mypy $SOURCE_FILES uvx check-sdist
tests/test_multipart.py+26 −0 modified@@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os import random import sys @@ -9,6 +10,7 @@ from typing import TYPE_CHECKING, cast from unittest.mock import Mock +import pytest import yaml from python_multipart.decoders import Base64Decoder, QuotedPrintableDecoder @@ -1248,6 +1250,30 @@ def on_file(f: FileProtocol) -> None: f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary") f.write(data.encode("latin-1")) + @pytest.fixture(autouse=True) + def inject_fixtures(self, caplog: pytest.LogCaptureFixture) -> None: + self._caplog = caplog + + def test_multipart_parser_data_end_with_crlf_without_warnings(self) -> None: + """This test makes sure that the parser does not handle when the data ends with a CRLF.""" + data = ( + "--boundary\r\n" + 'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n' + "Content-Type: text/plain\r\n\r\n" + "hello\r\n" + "--boundary--\r\n" + ) + + files: list[File] = [] + + def on_file(f: FileProtocol) -> None: + files.append(cast(File, f)) + + f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary") + with self._caplog.at_level(logging.WARNING): + f.write(data.encode("latin-1")) + assert len(self._caplog.records) == 0 + def test_max_size_multipart(self) -> None: # Load test data. test_file = "single_field_single_file.http"
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.