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;
}
nghttp2
with {F3099659} appliednghttpd -p/=/foo.bar --no-tls 8181
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)
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)
denial of service