File CVE-2024-52304.patch of Package python-aiohttp.36489
Index: aiohttp-3.9.3/CHANGES/9851.bugfix.rst
===================================================================
--- /dev/null
+++ aiohttp-3.9.3/CHANGES/9851.bugfix.rst
@@ -0,0 +1 @@
+Fixed incorrect parsing of chunk extensions with the pure Python parser -- by :user:`bdraco`.
Index: aiohttp-3.9.3/aiohttp/http_parser.py
===================================================================
--- aiohttp-3.9.3.orig/aiohttp/http_parser.py
+++ aiohttp-3.9.3/aiohttp/http_parser.py
@@ -28,6 +28,7 @@ from . import hdrs
from .base_protocol import BaseProtocol
from .compression_utils import HAS_BROTLI, BrotliDecompressor, ZLibDecompressor
from .helpers import (
+ set_exception,
DEBUG,
NO_EXTENSIONS,
BaseTimerContext,
@@ -824,6 +825,13 @@ class HttpPayloadParser:
i = chunk.find(CHUNK_EXT, 0, pos)
if i >= 0:
size_b = chunk[:i] # strip chunk-extensions
+ # Verify no LF in the chunk-extension
+ if b"\n" in (ext := chunk[i:pos]):
+ exc = BadHttpMessage(
+ f"Unexpected LF in chunk-extension: {ext!r}"
+ )
+ set_exception(self.payload, exc)
+ raise exc
else:
size_b = chunk[:pos]
Index: aiohttp-3.9.3/tests/test_http_parser.py
===================================================================
--- aiohttp-3.9.3.orig/tests/test_http_parser.py
+++ aiohttp-3.9.3/tests/test_http_parser.py
@@ -13,10 +13,12 @@ from yarl import URL
import aiohttp
from aiohttp import http_exceptions, streams
+from aiohttp.base_protocol import BaseProtocol
from aiohttp.http_parser import (
NO_EXTENSIONS,
DeflateBuffer,
HttpPayloadParser,
+ HttpRequestParser,
HttpRequestParserPy,
HttpResponseParserPy,
HttpVersion,
@@ -1337,7 +1339,55 @@ def test_parse_chunked_payload_empty_bod
assert b"second" == b"".join(d for d in payload._buffer)
-def test_partial_url(parser: Any) -> None:
+@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
+async def test_parse_chunked_payload_with_lf_in_extensions_c_parser(
+ loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
+) -> None:
+ """Test the C-parser with a chunked payload that has a LF in the chunk extensions."""
+ # The C parser will raise a BadHttpMessage from feed_data
+ parser = HttpRequestParserC(
+ protocol,
+ loop,
+ 2**16,
+ max_line_size=8190,
+ max_field_size=8190,
+ )
+ payload = (
+ b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
+ b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
+ b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
+ b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
+ )
+ with pytest.raises(http_exceptions.BadHttpMessage, match="\\\\nxx"):
+ parser.feed_data(payload)
+
+
+async def test_parse_chunked_payload_with_lf_in_extensions_py_parser(
+ loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
+) -> None:
+ """Test the py-parser with a chunked payload that has a LF in the chunk extensions."""
+ # The py parser will not raise the BadHttpMessage directly, but instead
+ # it will set the exception on the StreamReader.
+ parser = HttpRequestParserPy(
+ protocol,
+ loop,
+ 2**16,
+ max_line_size=8190,
+ max_field_size=8190,
+ )
+ payload = (
+ b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
+ b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
+ b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
+ b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
+ )
+ messages, _, _ = parser.feed_data(payload)
+ reader = messages[0][1]
+ assert isinstance(reader.exception(), http_exceptions.BadHttpMessage)
+ assert "\\nxx" in str(reader.exception())
+
+
+def test_partial_url(parser: HttpRequestParser) -> None:
messages, upgrade, tail = parser.feed_data(b"GET /te")
assert len(messages) == 0
messages, upgrade, tail = parser.feed_data(b"st HTTP/1.1\r\n\r\n")
Index: aiohttp-3.9.3/aiohttp/helpers.py
===================================================================
--- aiohttp-3.9.3.orig/aiohttp/helpers.py
+++ aiohttp-3.9.3/aiohttp/helpers.py
@@ -811,8 +811,10 @@ def set_result(fut: "asyncio.Future[_T]"
def set_exception(fut: "asyncio.Future[_T]", exc: BaseException) -> None:
- if not fut.done():
- fut.set_exception(exc)
+ if asyncio.isfuture(fut) and fut.done():
+ return
+
+ fut.set_exception(exc)
@functools.total_ordering