Skip to content

Commit 953fedb

Browse files
committed
Reject client enable-push settings
1 parent dc5da5c commit 953fedb

4 files changed

Lines changed: 45 additions & 0 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dev
2727
**Bugfixes**
2828

2929
- Fix to allow sending 0 bytes on a stream even if the flow control window is negative.
30+
- Reject non-zero ``SETTINGS_ENABLE_PUSH`` values received from clients.
3031

3132
4.3.0 (2025-08-23)
3233
------------------

src/h2/connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,6 +1777,8 @@ def _receive_settings_frame(self, frame: SettingsFrame) -> tuple[list[Frame], li
17771777
return [], events
17781778

17791779
# Add the new settings.
1780+
for setting, value in frame.settings.items():
1781+
self.remote_settings.validate_received_setting(setting, value)
17801782
self.remote_settings.update(frame.settings)
17811783
events.append(
17821784
RemoteSettingsChanged.from_settings(

src/h2/settings.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ class Settings(MutableMapping[SettingCodes | int, int]):
126126
"""
127127

128128
def __init__(self, client: bool = True, initial_values: dict[SettingCodes, int] | None = None) -> None:
129+
self._client = client
130+
129131
# Backing object for the settings. This is a dictionary of
130132
# (setting: [list of values]), where the first value in the list is the
131133
# current value of the setting. Strictly this doesn't use lists but
@@ -287,6 +289,30 @@ def __setitem__(self, key: SettingCodes | int, value: int) -> None:
287289

288290
items.append(value)
289291

292+
def validate_received_setting(self, setting: SettingCodes | int, value: int) -> None:
293+
"""
294+
Validate a setting received from the peer that owns this Settings
295+
object.
296+
297+
Clients may advertise ``ENABLE_PUSH`` only as ``0`` in received
298+
SETTINGS frames.
299+
"""
300+
invalid = _validate_setting(setting, value)
301+
if (
302+
not invalid
303+
and self._client
304+
and setting == SettingCodes.ENABLE_PUSH
305+
and value != 0
306+
):
307+
invalid = ErrorCodes.PROTOCOL_ERROR
308+
309+
if invalid:
310+
msg = f"Setting {setting} has invalid value {value}"
311+
raise InvalidSettingsValueError(
312+
msg,
313+
error_code=invalid,
314+
)
315+
290316
def __delitem__(self, key: SettingCodes | int) -> None:
291317
del self._settings[key]
292318

tests/test_invalid_frame_sequences.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,22 @@ def test_reject_invalid_settings_values(self, frame_factory, settings) -> None:
266266
h2.errors.ErrorCodes.PROTOCOL_ERROR
267267
)
268268

269+
def test_reject_client_enable_push_updates(self, frame_factory) -> None:
270+
"""
271+
Servers reject clients that advertise non-zero SETTINGS_ENABLE_PUSH
272+
values in received SETTINGS frames.
273+
"""
274+
c = h2.connection.H2Connection(config=self.server_config)
275+
c.initiate_connection()
276+
c.receive_data(frame_factory.preamble())
277+
278+
f = frame_factory.build_settings_frame(settings={0x2: 1})
279+
280+
with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
281+
c.receive_data(f.serialize())
282+
283+
assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
284+
269285
@pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes])
270286
def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_headers) -> None:
271287
"""

0 commit comments

Comments
 (0)