✓ProveIt
Examples / Report #3788931 Saved analysis

CVE-2026-11586: WS Auto-PONG memory exhaustion

curl · 2026-06-0814.9s analysis · $0.018 in model calls

What the report claims

A remote WebSocket peer can cause curl to exhaust memory by sending legal PING frames while refusing to read the client's automatic PONGs, leading to unbounded growth of the auto-PONG send queue.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-11586 WS Auto-PONG memory exhaustion

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • The code shows ws_enc_add_cntrl queues a PONG for each PING and ignores ws_flush's return value (lib/ws.c:648), so backpressure from a full send buffer is lost.
  • The send buffer is initialized with BUFQ_OPT_SOFT_LIMIT (lib/bufq.h:111), which permits unbounded growth beyond the nominal limit.
  • A remote peer needs only to complete a valid WebSocket handshake, then send legal PING frames while not reading; curl will queue PONGs until memory exhaustion.
  • The PoC demonstrates ~1 GiB RSS in ~2 seconds and OOM (error 27) under a 180 MiB address-space limit, even with --max-time 8.
  • CURLWS_NOAUTOPONG avoids the vulnerable path entirely, confirming the auto-PONG queueing is the root cause.
  • This is not a 'never-ending transfer' (which curl policy excludes) but a rapid, attacker-driven memory exhaustion DoS reachable from a remote peer without API misuse or malformed frames.
How this analysis ran 6 claims · 12 citations verified

Checked against curl master on 2026-06-08 (9b69cfb93785). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 13 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 2873 tokens · $0.0003
3 · Evidence
3 code snippets
real curl source at 9b69cfb93785 · 4 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
10968 tokens · 11.4s · $0.0174
5 · Receipts
12 of 12 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report and reproducible PoC. The code confirms that ws_enc_add_cntrl() queues an automatic PONG for each received PING and discards ws_flush()'s return value (lib/ws.c:648), while the send buffer uses only a soft limit (lib/bufq.h:111). This allows a remote peer to drive unbounded memory growth by sending legal PING frames after a valid handshake while not reading the connection. The PoC demonstrates OOM (error 27) within seconds even with --max-time 8. This is a valid remote DoS via memory exhaustion. We will prioritize a fix that adds a hard limit or backpressure to the auto-PONG queue.

Inspect all claims and source code 6 claims · 3 code excerpts

Report claims

C1flaw supported

curl automatically replies to each received WebSocket PING with a PONG unless CURLWS_NOAUTOPONG is set

Read the reasoning

The flag CURLWS_NOAUTOPONG exists and is documented as disabling automatic PONG responses. The default behavior (flag not set) is to auto-reply to PING with PONG.

C2location supported

In lib/ws.c, ws_enc_add_cntrl() queues the generated PONG into ws->sendbuf and ignores the result of ws_flush()

Read the reasoning

ws_enc_add_cntrl stores the PONG in ws->pending, then if not in the middle of another frame, calls ws_enc_add_pending (which queues it to sendbuf) and ws_flush, but discards ws_flush's result.

C3trigger supported

A malicious WebSocket peer can complete a valid upgrade, stop reading client data, and keep sending legal PING frames

Read the reasoning

The code accepts PING frames up to the protocol maximum (125 bytes payload). A peer can complete the handshake (valid upgrade) then send a stream of legal PING frames while not reading the connection, causing curl to queue PONG responses.

C4impact supported

curl continues appending internal PONGs without an effective hard limit, causing memory exhaustion

Read the reasoning

The send buffer is initialized with BUFQ_OPT_SOFT_LIMIT (only a soft limit), and ws_flush's result is ignored, so there is no effective hard limit on how many PONGs can be queued when the peer stops reading.

C5reproduction supported

The provided PoC reproduces the issue by sending 65536 PING frames and hitting out-of-memory within seconds

Read the reasoning

The PoC sends 65536 empty PING frames (2 bytes each). Each triggers a PONG queued to the soft-limited sendbuf. With the peer not reading, memory grows rapidly until OOM, as the report demonstrates.

C6impact supported

The vulnerability can cause curl to exit with (27) Out of memory under memory limits

Read the reasoning

Under a memory limit (180 MiB RLIMIT_AS), the unbounded queueing causes allocation failure, which curl reports as CURLE_OUT_OF_MEMORY (error 27). The control with CURLWS_NOAUTOPONG does not exhibit the growth.

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: TL;DR: a remote WebSocket peer can make default curl/libcurl grow memory until timeout or OOM by sending legal `PING` frames while refusing to read the client's automatic `PONG`s. curl automatically replies to each received WebSocket `PING` with a `PONG` unless `CURLWS_NOAUTOPONG` is set. In `lib/ws.c`, `ws_enc_add_cntrl()` queues the generated `PONG` into `ws->sendbuf` and ignores the result of `ws_flush()`, while `ws->sendbuf` is initialized with `BUFQ_OPT_SOFT_LIMIT`. A malicious WebSocket peer can complete a valid upgrade, stop reading client data, and keep sending legal `PING` frames so curl continues appending internal `PONG`s without an effective hard limit. This is not just a never-ending transfer: even with `--max-time 8`, the default CLI path can hit `1 GiB` RSS in about 2 seconds locally, and under a 180 MiB address-space limit it exits with `curl: (27) Out of memory`. The same input with `CURLWS_NOAUTOPONG` does not show comparable memory growth. As a temporary mitigation, a smaller `--max-time` / `CURLOPT_TIMEOUT` reduces the attack window, while `CURLWS_NOAUTOPONG` avoids this default auto-PONG queueing path entirely. ## Affected version Reproduced on current local build: ```text curl 8.21.0-DEV (Linux) libcurl/8.21.0-DEV zlib/1.2.11 libpsl/0.21.0 nghttp2/1.40.0 Release-Date: [unreleased] Protocols: dict file ftp gopher http imap ipfs ipns mqtt pop3 rtsp smtp telnet tftp ws Features: alt-svc AsynchDNS HTTP2 IPv6 Largefile libz PSL threadsafe UnixSockets ``` Preliminary version triage: - Current `master` / `8.21.0-DEV` reproduces. - The vulnerable auto-PONG queueing path appears to have been introduced by commit `0b09132877` (`websocket: handling of PONG frames`). - Based on tag containment, the first released version that appears to contain this specific path is `8.16.0`. - Initial review suggests `8.15.x` and earlier do not contain this same queued auto-PONG implementation. Relevant code paths: ```c /* lib/ws.c */ if(!ws->enc.payload_remain) { CURLcode result = ws_enc_add_pending(data, ws); if(!result) (void)ws_flush(data, ws, Curl_is_in_callback(data)); return result; } /* sendbuf uses BUFQ_OPT_SOFT_LIMIT */ Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT, BUFQ_OPT_SOFT_LIMIT); ``` ## Steps To Reproduce: From a curl source checkout with a WebSocket-enabled build, run this single self-contained PoC. I …

Source code

include/curl/websockets.hlines 90–90 · definition of CURLWS_NOAUTOPONG
90#define CURLWS_NOAUTOPONG (1L << 1)
C1: CURLWS_NOAUTOPONG flag defined; the auto-PONG behavior is the default when this flag is not set
C6: CURLWS_NOAUTOPONG avoids this path entirely, confirming the auto-PONG queueing is the cause
lib/ws.clines 617–652 · definition of ws_enc_add_cntrl; definition of ws_flush
617static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
618 bool blocking);
619static CURLcode ws_enc_send(struct Curl_easy *data,
620 struct websocket *ws,
621 const uint8_t *buffer,
622 size_t buflen,
623 curl_off_t fragsize,
624 unsigned int flags,
625 size_t *pnsent);
626static CURLcode ws_enc_add_pending(struct Curl_easy *data,
627 struct websocket *ws);
628
629static CURLcode ws_enc_add_cntrl(struct Curl_easy *data,
C2: ws_enc_add_cntrl function definition starts here
630 struct websocket *ws,
631 const uint8_t *payload,
632 size_t plen,
633 unsigned int frame_type)
634{
635 DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN);
636 if(plen > WS_MAX_CNTRL_LEN)
C3: PING frame payload length checked against WS_MAX_CNTRL_LEN (125 bytes), so legal PING frames are accepted
C5: PING payload length limit WS_MAX_CNTRL_LEN allows the 2-byte empty PING frames (‰) used in the PoC
637 return CURLE_BAD_FUNCTION_ARGUMENT;
638
639 /* Overwrite any pending frame with the new one, we keep
640 * only one. */
641 ws->pending.type = frame_type;
C3: Frame type stored in ws->pending.type; PING frames trigger auto-PONG via this path
642 ws->pending.payload_len = plen;
643 memcpy(ws->pending.payload, payload, plen);
644
645 if(!ws->enc.payload_remain) { /* not in the middle of another frame */
646 CURLcode result = ws_enc_add_pending(data, ws);
C2: Calls ws_enc_add_pending to queue the PONG frame
647 if(!result)
648 (void)ws_flush(data, ws, Curl_is_in_callback(data));
C2: Calls ws_flush but explicitly ignores its return value with (void) cast
C4: ws_flush return value ignored, so backpressure from a full send buffer is not propagated to stop accepting new PINGs
C5: Each PING triggers a PONG queued to sendbuf; with ws_flush ignored, the queue grows unbounded
C6: Ignored ws_flush means no backpressure; sendbuf grows until allocation fails
649 return result;
650 }
651 return CURLE_OK;
652}
lib/bufq.hlines 111–111 · definition of BUFQ_OPT_SOFT_LIMIT
111#define BUFQ_OPT_SOFT_LIMIT (1 << 0)
C4: BUFQ_OPT_SOFT_LIMIT defined as a soft limit flag (allows growth beyond the nominal limit)