Files
python-h2/CVE-2025-57804.patch

92 lines
4.3 KiB
Diff

From 883ed37be42592b2f0aa0caddab6ca5e3d668fa3 Mon Sep 17 00:00:00 2001
From: Thomas Kriechbaumer <thomas@kriechbaumer.name>
Date: Mon, 18 Aug 2025 22:46:12 +0200
Subject: [PATCH] reject header names and values containing unpermitted
characters `\r`, `\n`, or `\0x00`
---
CHANGELOG.rst | 2 +-
src/h2/utilities.py | 25 +++++++++++++++++++++++++
tests/test_invalid_headers.py | 8 +++++++-
3 files changed, 33 insertions(+), 2 deletions(-)
Index: h2-4.2.0/src/h2/utilities.py
===================================================================
--- h2-4.2.0.orig/src/h2/utilities.py
+++ h2-4.2.0/src/h2/utilities.py
@@ -201,6 +201,9 @@ def validate_headers(headers: Iterable[H
# For example, we avoid tuple unpacking in loops because it represents a
# fixed cost that we don't want to spend, instead indexing into the header
# tuples.
+ headers = _reject_illegal_characters(
+ headers, hdr_validation_flags,
+ )
headers = _reject_empty_header_names(
headers, hdr_validation_flags,
)
@@ -225,6 +228,36 @@ def validate_headers(headers: Iterable[H
return _check_path_header(headers, hdr_validation_flags)
+def _reject_illegal_characters(headers, hdr_validation_flags):
+ """
+ Raises a ProtocolError if any header names or values contain illegal characters.
+ See RFC 9113, section 8.2.1.
+ """
+ for header in headers:
+ # > A field name MUST NOT contain characters in the ranges 0x00-0x20, 0x41-0x5a,
+ # > or 0x7f-0xff (all ranges inclusive).
+ for c in header[0]:
+ if c <= 0x20 or 0x41 <= c <= 0x5a or 0x7f <= c:
+ msg = f"Illegal character '{chr(c)}' in header name: {header[0]!r}"
+ raise ProtocolError(msg)
+
+ # > With the exception of pseudo-header fields (Section 8.3), which have a name
+ # > that starts with a single colon, field names MUST NOT include a colon (ASCII
+ # > COLON, 0x3a).
+ if header[0].find(b":", 1) != -1:
+ msg = f"Illegal character ':' in header name: {header[0]!r}"
+ raise ProtocolError(msg)
+
+ # > A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed
+ # > (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position.
+ for c in header[1]:
+ if c == 0 or c == 0x0a or c == 0x0d:
+ msg = f"Illegal character '{chr(c)}' in header value: {header[1]!r}"
+ raise ProtocolError(msg)
+
+ # Surrounding whitespace is enforced in `_reject_surrounding_whitespace`.
+ yield header
+
def _reject_empty_header_names(headers: Iterable[Header],
hdr_validation_flags: HeaderValidationFlags) -> Generator[Header, None, None]:
Index: h2-4.2.0/tests/test_invalid_headers.py
===================================================================
--- h2-4.2.0.orig/tests/test_invalid_headers.py
+++ h2-4.2.0/tests/test_invalid_headers.py
@@ -48,6 +48,14 @@ class TestInvalidFrameSequences:
[*base_request_headers, ("name ", "name with trailing space")],
[*base_request_headers, ("name", " value with leading space")],
[*base_request_headers, ("name", "value with trailing space ")],
+ [*base_request_headers, ("illegal:characters", "value")],
+ [*base_request_headers, ("illegal-\r-characters", "value")],
+ [*base_request_headers, ("illegal-\n-characters", "value")],
+ [*base_request_headers, ("illegal-\x00-characters", "value")],
+ [*base_request_headers, ("illegal-\x01-characters", "value")],
+ [*base_request_headers, ("illegal-characters", "some \r value")],
+ [*base_request_headers, ("illegal-characters", "some \n value")],
+ [*base_request_headers, ("illegal-characters", "some \x00 value")],
[header for header in base_request_headers
if header[0] != ":authority"],
[(":protocol", "websocket"), *base_request_headers],
@@ -665,7 +673,7 @@ class TestFilter:
def test_inbound_header_name_length_full_frame_decode(self, frame_factory) -> None:
f = frame_factory.build_headers_frame([])
- f.data = b"\x00\x00\x05\x00\x00\x00\x00\x04"
+ f.data = b"\x00\x00\x01\x04"
data = f.serialize()
c = h2.connection.H2Connection(config=h2.config.H2Configuration(client_side=False))