Skip to content

Commit 726cc56

Browse files
committed
Correct padded HEADERS length and reject padding over fixed fields
1 parent 632e309 commit 726cc56

3 files changed

Lines changed: 34 additions & 4 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ dev
1919

2020
**Bugfixes**
2121

22-
-
22+
- Preserve the full padded HEADERS body length and reject padding that overlaps
23+
fixed fields in HEADERS and PUSH_PROMISE frames.
2324

2425
6.1.0 (2025-01-22)
2526
------------------

src/hyperframe/frame.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ def parse_body(self, data: memoryview) -> None:
538538
msg = f"Invalid PUSH_PROMISE promised stream id: {self.promised_stream_id}"
539539
raise InvalidDataError(msg)
540540

541-
if self.pad_length and self.pad_length >= self.body_len:
541+
if self.pad_length > self.body_len - padding_data_length - 4:
542542
msg = "Padding is too long."
543543
raise InvalidPaddingError(msg)
544544

@@ -751,6 +751,7 @@ def serialize_body(self) -> bytes:
751751
return b"".join([padding_data, priority_data, self.data, padding])
752752

753753
def parse_body(self, data: memoryview) -> None:
754+
self.body_len = len(data)
754755
padding_data_length = self.parse_padding_data(data)
755756
data = data[padding_data_length:]
756757

@@ -759,12 +760,11 @@ def parse_body(self, data: memoryview) -> None:
759760
else:
760761
priority_data_length = 0
761762

762-
self.body_len = len(data)
763763
self.data = (
764764
data[priority_data_length:len(data)-self.pad_length].tobytes()
765765
)
766766

767-
if self.pad_length and self.pad_length >= self.body_len:
767+
if self.pad_length > len(data) - priority_data_length:
768768
msg = "Padding is too long."
769769
raise InvalidPaddingError(msg)
770770

tests/test_frames.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,3 +995,32 @@ class TestExtensionFrame:
995995
def test_repr(self):
996996
f = ExtensionFrame(0xFF, 1, 42, b'hello')
997997
assert repr(f).endswith("type=255, flag_byte=42, body=<hex:68656c6c6f>")
998+
999+
1000+
@pytest.mark.parametrize('pad_length', [0, 1, 5])
1001+
@pytest.mark.parametrize('payload', [b'', b'hello'])
1002+
@pytest.mark.parametrize('priority', [False, True])
1003+
def test_padded_headers_body_length(pad_length, payload, priority):
1004+
flags = ['PADDED', 'END_HEADERS']
1005+
if priority:
1006+
flags.append('PRIORITY')
1007+
frame = HeadersFrame(1, flags=flags, pad_length=pad_length, data=payload)
1008+
wire = frame.serialize()
1009+
decoded = decode_frame(wire)
1010+
assert decoded.data == payload
1011+
assert decoded.body_len == len(wire) - 9
1012+
assert decoded.serialize() == wire
1013+
1014+
1015+
@pytest.mark.parametrize('frame_type', [HeadersFrame, PushPromiseFrame])
1016+
def test_padding_cannot_overlap_fixed_fields(frame_type):
1017+
# The pad-length byte claims one padding byte, but all remaining bytes
1018+
# belong to the mandatory priority or promised-stream-ID field.
1019+
if frame_type is HeadersFrame:
1020+
frame = HeadersFrame(1, flags=['PADDED', 'PRIORITY'])
1021+
body = b'\x01\x00\x00\x00\x00\x00'
1022+
else:
1023+
frame = PushPromiseFrame(1, flags=['PADDED'])
1024+
body = b'\x01\x00\x00\x00\x02'
1025+
with pytest.raises(InvalidPaddingError):
1026+
frame.parse_body(memoryview(body))

0 commit comments

Comments
 (0)