test: ensure HTTP server enforces limits on headers and body size
What changed, and why it matters
This commit only adds new automated tests to Bitcoin Core. It checks that the built-in HTTP server already rejects oversized HTTP headers and request bodies, including when data is sent using chunked transfer encoding. There is no change to the actual server code, so this is a test-only improvement rather than a fix for a live vulnerability.
No immediate action required. Treat as routine test-coverage improvement. Review whether the existing limits remain appropriate for current deployment scenarios.
Security signals we found
Tests assert HTTP server rejects oversized headers and bodies
References existing server limits in httpserver.cpp and serialize.h
Notes libevent-specific behavior (1,000 byte tolerance above configured header limit)
No production code change; defensive test coverage only
Evidence from the diff
The diff extends test/functional/interface_http.py with functional tests that verify libevent-backed HTTP limits: MAX_HEADERS_SIZE (8 KiB) and MAX_SIZE (0x02000000 / 32 MiB). Tests send requests just below and above each limit and assert BAD_REQUEST or connection termination. A chunked-transfer test is also added. No production code is modified.
Changed components
test/functional/interface_http.pyInspect captured patch +88 / −0
diff --git a/test/functional/interface_http.py b/test/functional/interface_http.py
index cfe8973a..ff6d70a0 100755
--- a/test/functional/interface_http.py
+++ b/test/functional/interface_http.py
@@ -15,6 +15,8 @@ import urllib.parse
RPCSERVERTIMEOUT = 2
# Set in httpserver.cpp and passed to libevent evhttp_set_max_headers_size()
MAX_HEADERS_SIZE = 8192
+# Set in serialize.h and passed to libevent evhttp_set_max_body_size()
+MAX_SIZE = 0x02000000
class BitcoinHTTPConnection:
@@ -45,6 +47,9 @@ class BitcoinHTTPConnection:
def set_timeout(self, seconds):
self.conn.sock.settimeout(seconds)
+ def add_header(self, key, value):
+ self.headers.update({key: value})
+
def _request(self, method, path, data, connection_header, **kwargs):
headers = self.headers.copy()
if connection_header is not None:
@@ -166,6 +171,57 @@ class HTTPBasicsTest (BitcoinTestFramework):
response2 = conn.get(f'/{"x" * MAX_HEADERS_SIZE}')
assert_equal(response2.status, http.client.BAD_REQUEST)
+ # Compute how many short header lines need to be added to http.client
+ # default headers to make / break the total limit in a single request.
+ header_line_length = len("header_0000: foo\r\n")
+ headers_below_limit = (MAX_HEADERS_SIZE - 1000) // header_line_length
+ headers_above_limit = MAX_HEADERS_SIZE // header_line_length
+
+ # This is a libevent mystery:
+ # libevent does not reject the request until it is more than
+ # 1,000 bytes above the configured limit.
+ headers_above_limit += 1000 // header_line_length
+
+ # Many small header lines is ok
+ conn = BitcoinHTTPConnection(self.nodes[2])
+ for i in range(headers_below_limit):
+ conn.add_header(f"header_{i:04}", "foo")
+ response3 = conn.get('/x')
+ assert_equal(response3.status, http.client.NOT_FOUND)
+
+ # Too many small header lines exceeds total headers size allowed
+ conn = BitcoinHTTPConnection(self.nodes[2])
+ for i in range(headers_above_limit):
+ conn.add_header(f"header_{i:04}", "foo")
+ response3 = conn.get('/x')
+ assert_equal(response3.status, http.client.BAD_REQUEST)
+
+ # Compute how much data we can add to a request message body
+ # to make / break the limit.
+ base_request_body_size = len('{"jsonrpc": "2.0", "id": "0", "method": "submitblock", "params": [""]}}')
+ bytes_below_limit = MAX_SIZE - base_request_body_size
+ bytes_above_limit = MAX_SIZE - base_request_body_size + 2
+
+ # Large request body size is ok
+ conn = BitcoinHTTPConnection(self.nodes[0])
+ response4 = conn.post('/', f'{{"jsonrpc": "2.0", "id": "0", "method": "submitblock", "params": ["{"0" * bytes_below_limit}"]}}')
+ assert_equal(response4.status, http.client.OK)
+
+ conn = BitcoinHTTPConnection(self.nodes[1])
+ try:
+ # Excessive body size is invalid
+ response5 = conn.post('/', f'{{"jsonrpc": "2.0", "id": "0", "method": "submitblock", "params": ["{"0" * bytes_above_limit}"]}}')
+
+ # The server will send a 400 response and disconnect but
+ # due to a race condition, the python client may or may not
+ # receive the response before detecting the broken socket.
+ response5.read()
+ assert_equal(response5.status, http.client.BAD_REQUEST)
+ assert conn.sock_closed()
+ self.log.debug("Server sent response before terminating connection")
+ except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
+ self.log.debug("Server terminated connection immediately")
+
def check_pipelining(self):
"""
@@ -228,6 +284,38 @@ class HTTPBasicsTest (BitcoinTestFramework):
response1 = conn.recv_raw()
assert b'{"result":"high-hash","error":null}\n' in response1
+ self.log.info("Check excessive size HTTP request encoded with chunked transfer")
+ conn = BitcoinHTTPConnection(self.nodes[0])
+ headers_chunked = conn.headers.copy()
+ headers_chunked.update({"Transfer-encoding": "chunked"})
+ body_chunked = [
+ b'{"method": "submitblock", "params": ["',
+ b'0' * 10000000,
+ b'1' * 10000000,
+ b'2' * 10000000,
+ b'3' * 10000000,
+ b'"]}'
+ ]
+ try:
+ conn.conn.request(
+ method='POST',
+ url='/',
+ body=iter(body_chunked),
+ headers=headers_chunked,
+ encode_chunked=True)
+
+ # The server will send a 400 response and disconnect but
+ # due to a race condition, the python client may or may not
+ # receive the response before detecting the broken socket.
+ response2 = conn.conn.getresponse()
+ response2.read()
+ assert_equal(response2.status, http.client.BAD_REQUEST)
+ assert conn.sock_closed()
+ self.log.debug("Server sent response before terminating connection")
+ except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
+ # ...or just immediately disconnect
+ self.log.debug("Server terminated connection immediately")
+
def check_idle_timeout(self):
self.log.info("Check -rpcservertimeout")
Why this scored 22/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.