From 251d8ebe51d7209a042f4cfdcaaaa4d80387f8c0 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Tue, 27 Jan 2026 10:51:29 +0100 Subject: [PATCH] fix(http2): validate frame size per RFC 7540 (16384-16777215) --- gunicorn/config.py | 15 ++++++- tests/test_http2_config.py | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/gunicorn/config.py b/gunicorn/config.py index dec06f43..def1e0c6 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -389,6 +389,19 @@ def validate_pos_int(val): return val +def validate_http2_frame_size(val): + """Validate HTTP/2 max frame size per RFC 7540.""" + if not isinstance(val, int): + val = int(val, 0) + else: + val = int(val) + if val < 16384 or val > 16777215: + raise ValueError( + f"http2_max_frame_size must be between 16384 and 16777215, got {val}" + ) + return val + + def validate_ssl_version(val): if val != SSLVersion.default: sys.stderr.write("Warning: option `ssl_version` is deprecated and it is ignored. Use ssl_context instead.\n") @@ -2532,7 +2545,7 @@ class HTTP2MaxFrameSize(Setting): section = "HTTP/2" cli = ["--http2-max-frame-size"] meta = "INT" - validator = validate_pos_int + validator = validate_http2_frame_size type = int default = 16384 desc = """\ diff --git a/tests/test_http2_config.py b/tests/test_http2_config.py index 29810da7..b07ca664 100644 --- a/tests/test_http2_config.py +++ b/tests/test_http2_config.py @@ -159,6 +159,38 @@ class TestHttp2MaxFrameSize: c.set("http2_max_frame_size", "65536") assert c.http2_max_frame_size == 65536 + def test_valid_min_value(self): + """RFC 7540 minimum is 16384 (2^14).""" + c = Config() + c.set("http2_max_frame_size", 16384) + assert c.http2_max_frame_size == 16384 + + def test_valid_max_value(self): + """RFC 7540 maximum is 16777215 (2^24 - 1).""" + c = Config() + c.set("http2_max_frame_size", 16777215) + assert c.http2_max_frame_size == 16777215 + + def test_valid_mid_range_value(self): + """Test a value in the middle of the valid range.""" + c = Config() + c.set("http2_max_frame_size", 1048576) # 1MB + assert c.http2_max_frame_size == 1048576 + + def test_below_min_raises(self): + """Values below 16384 should raise ValueError per RFC 7540.""" + c = Config() + with pytest.raises(ValueError) as exc_info: + c.set("http2_max_frame_size", 16383) + assert "must be between 16384 and 16777215" in str(exc_info.value) + + def test_above_max_raises(self): + """Values above 16777215 should raise ValueError per RFC 7540.""" + c = Config() + with pytest.raises(ValueError) as exc_info: + c.set("http2_max_frame_size", 16777216) + assert "must be between 16384 and 16777215" in str(exc_info.value) + def test_negative_value_raises(self): c = Config() with pytest.raises(ValueError): @@ -258,3 +290,54 @@ class TestValidateHttpProtocols: def test_validate_type_error(self): with pytest.raises(TypeError): config.validate_http_protocols(42) + + +class TestValidateHttp2FrameSize: + """Test the validate_http2_frame_size function directly.""" + + def test_validate_min_value(self): + """RFC 7540 minimum is 16384 (2^14).""" + result = config.validate_http2_frame_size(16384) + assert result == 16384 + + def test_validate_max_value(self): + """RFC 7540 maximum is 16777215 (2^24 - 1).""" + result = config.validate_http2_frame_size(16777215) + assert result == 16777215 + + def test_validate_mid_range(self): + """Test a value in the middle of the valid range.""" + result = config.validate_http2_frame_size(1000000) + assert result == 1000000 + + def test_validate_from_string(self): + """Test that string values are converted properly.""" + result = config.validate_http2_frame_size("32768") + assert result == 32768 + + def test_validate_hex_string(self): + """Test hex string conversion.""" + result = config.validate_http2_frame_size("0x10000") # 65536 + assert result == 65536 + + def test_validate_below_min_raises(self): + """Values below 16384 should raise ValueError.""" + with pytest.raises(ValueError) as exc_info: + config.validate_http2_frame_size(16383) + assert "must be between 16384 and 16777215" in str(exc_info.value) + + def test_validate_above_max_raises(self): + """Values above 16777215 should raise ValueError.""" + with pytest.raises(ValueError) as exc_info: + config.validate_http2_frame_size(16777216) + assert "must be between 16384 and 16777215" in str(exc_info.value) + + def test_validate_zero_raises(self): + """Zero is below minimum and should raise ValueError.""" + with pytest.raises(ValueError): + config.validate_http2_frame_size(0) + + def test_validate_negative_raises(self): + """Negative values should raise ValueError.""" + with pytest.raises(ValueError): + config.validate_http2_frame_size(-1)