Lucene search

K
hackeroneW0x42H1:2402845
HistoryMar 05, 2024 - 4:56 p.m.

curl: CVE-2024-2398: HTTP/2 push headers memory-leak

2024-03-0516:56:33
w0x42
hackerone.com
71
libcurl
memory-leak
http/2
push headers
nghttp2
valgrind
vulnerability

6.7 Medium

AI Score

Confidence

High

0.0004 Low

EPSS

Percentile

10.4%

Summary:

For each incoming PUSH_PROMISE header a new name:value string is allocated
and the pointer to that string is stored in the stream->push_headers array.

h = aprintf("%s:%s", name, value);
    if(h)
      stream->push_headers[stream->push_headers_used++] = h;

Libcurl will reject PUSH_PROMISE frames with too many headers.
When the number of headers exceeds some threshold, on_header returns an error.
However, libcurl forgets to free the stream->push_headers array elements before stream->push_headers is freed.
A malicious server may continuously send PUSH_PROMISE frames with over 1000 headers, which would eventually consume all available memory.

The same issue exists when Curl_saferealloc fails.

 if(stream->push_headers_alloc > 1000) {
        /* this is beyond crazy many headers, bail out */
        failf(data_s, "Too many PUSH_PROMISE headers");
        Curl_safefree(stream->push_headers);
        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
      }
      stream->push_headers_alloc *= 2;
      headp = Curl_saferealloc(stream->push_headers,
                               stream->push_headers_alloc * sizeof(char *));
      if(!headp) {
        stream->push_headers = NULL;
        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
      }

Steps To Reproduce:

  1. compile nghttp2 with {F3099659} applied
  2. compile {F3099658}
  3. run nghttpd -p/=/foo.bar --no-tls 8181
  4. run valgrind --leak-check=full http2_push_promise

for each -p option nghttpd will send 200 PUSH_PROMISE frames, each with 1280 headers (not counting pseudo headers)

Supporting Material/References:

valgrind --leak-check=full http2_push_promise output:

==13928== 
==13928== HEAP SUMMARY:
==13928==     in use at exit: 8,285,018 bytes in 256,674 blocks
==13928==   total heap usage: 261,567 allocs, 4,893 frees, 12,766,009 bytes allocated
==13928== 
==13928== 64 bytes in 2 blocks are possibly lost in loss record 2 of 10
==13928==    at 0x48436C4: malloc (vg_replace_malloc.c:392)
==13928==    by 0x4889F45: dyn_nappend (dynbuf.c:107)
==13928==    by 0x488A2C5: Curl_dyn_addn (dynbuf.c:170)
==13928==    by 0x48C393E: alloc_addbyter (mprintf.c:1065)
==13928==    by 0x48C2FF9: dprintf_formatf (mprintf.c:852)
==13928==    by 0x48C39FF: curl_mvaprintf (mprintf.c:1095)
==13928==    by 0x48C3AF0: curl_maprintf (mprintf.c:1110)
==13928==    by 0x48B0F86: on_header (http2.c:1467)
==13928==    by 0x4C310C1: nghttp2_session_mem_recv (in /usr/lib64/libnghttp2.so.14.25.1)
==13928==    by 0x48AE62B: h2_process_pending_input (http2.c:552)
==13928==    by 0x48B2570: h2_progress_ingress (http2.c:1914)
==13928==    by 0x48B2775: cf_h2_recv (http2.c:1953)
==13928== 
==13928== 8,191,872 bytes in 255,996 blocks are definitely lost in loss record 10 of 10
==13928==    at 0x48436C4: malloc (vg_replace_malloc.c:392)
==13928==    by 0x4889F45: dyn_nappend (dynbuf.c:107)
==13928==    by 0x488A2C5: Curl_dyn_addn (dynbuf.c:170)
==13928==    by 0x48C393E: alloc_addbyter (mprintf.c:1065)
==13928==    by 0x48C2FF9: dprintf_formatf (mprintf.c:852)
==13928==    by 0x48C39FF: curl_mvaprintf (mprintf.c:1095)
==13928==    by 0x48C3AF0: curl_maprintf (mprintf.c:1110)
==13928==    by 0x48B0F86: on_header (http2.c:1467)
==13928==    by 0x4C310C1: nghttp2_session_mem_recv (in /usr/lib64/libnghttp2.so.14.25.1)
==13928==    by 0x48AE62B: h2_process_pending_input (http2.c:552)
==13928==    by 0x48B2570: h2_progress_ingress (http2.c:1914)
==13928==    by 0x48B2775: cf_h2_recv (http2.c:1953)
==13928== 
==13928== LEAK SUMMARY:
==13928==    definitely lost: 8,191,872 bytes in 255,996 blocks
==13928==    indirectly lost: 0 bytes in 0 blocks
==13928==      possibly lost: 64 bytes in 2 blocks
==13928==    still reachable: 93,082 bytes in 676 blocks
==13928==         suppressed: 0 bytes in 0 blocks
==13928== Reachable blocks (those to which a pointer was found) are not shown.
==13928== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==13928== 
==13928== For lists of detected and suppressed errors, rerun with: -s
==13928== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Impact

denial of service