Buffer Overflow Vulnerability in WebSocket Handling
What the report claims
Buffer overflow in WebSocket handling due to unsafe use of strcpy without bounds checking.
Compare with curl's recorded outcome
Labeled AI slop
curl's maintainer lists this report as AI slop
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code provides evidence against the report's central claim. Review the cited lines before deciding.
Why this finding?
- The code has an explicit bounds check (lines 579-580) before the strcpy that returns CURLE_FAILED_INIT if the base64-encoded nonce would not fit in keyval.
- The WebSocket nonce is generated internally by curl via Curl_rand (16 random bytes), not supplied by an attacker. The attacker cannot control its length.
- The base64 encoding of 16 bytes produces ~22 characters, well within typical buffer sizes, and the bounds check handles any edge cases.
How this analysis ran 5 claims · 9 citations verified
Checked against curl permalink e251e858b941 (e251e858b941). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the report. The code at lib/ws.c lines 579-580 shows an explicit bounds check before the strcpy at line 581: if(randlen >= sizeof(keyval)) return CURLE_FAILED_INIT;. Additionally, the WebSocket nonce is generated internally by curl (Curl_rand at line 572), not provided by an attacker, so an attacker cannot supply an oversized value. This is not a buffer overflow vulnerability.
Inspect all claims and source code 5 claims · 1 code excerpts
Report claims
The report claims that strcpy is used without proper bounds checking, leading to a buffer overflow.
Read the reasoning
The code at lines 579-580 shows an explicit bounds check before the strcpy at line 581. The strcpy only executes if randlen < sizeof(keyval), so there is proper bounds checking.
The vulnerable code is located in lib/ws.c at line 581.
Read the reasoning
The report correctly identifies the location of the strcpy call at line 581 of lib/ws.c.
A base64-encoded nonce value that exceeds the buffer size can trigger the overflow.
Read the reasoning
The nonce is generated by curl internally via Curl_rand (16 random bytes) and then base64-encoded. An attacker cannot provide or control this nonce value; it is not attacker-controlled input.
An attacker could execute arbitrary code by overwriting adjacent memory.
Read the reasoning
Since the bounds check at lines 579-580 prevents the strcpy when the encoded string would not fit, arbitrary code execution via buffer overflow on this path is not possible.
The steps to reproduce involve triggering WebSocket functionality with a crafted request containing an oversized base64-encoded nonce.
Read the reasoning
The reproduction steps describe providing a crafted base64-encoded nonce, but the code shows the nonce is generated internally by curl. An attacker cannot supply an oversized nonce to trigger this code path.
Deterministic checks that failed
- symbol strncpy() is not called in lib/ws.c at e251e858b941
Read the original report
Source code
| 556 | |Sec-WebSocket-Version|. The value of this header field MUST be |
| 557 | 13. */ |
| 558 | "Sec-WebSocket-Version:", "13", |
| 559 | }, |
| 560 | { |
| 561 | /* The request MUST include a header field with the name |
| 562 | |Sec-WebSocket-Key|. The value of this header field MUST be a nonce |
| 563 | consisting of a randomly selected 16-byte value that has been |
| 564 | base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be |
| 565 | selected randomly for each connection. */ |
| 566 | "Sec-WebSocket-Key:", NULL, |
| 567 | } |
| 568 | }; |
| 569 | heads[3].val = &keyval[0]; |
| 570 | |
| 571 | /* 16 bytes random */ |
| 572 | result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); |
| C3: Curl_rand generates the nonce internally: Curl_rand(data, (unsigned char *)rand, sizeof(rand)); | |
| C5: Nonce is generated by Curl_rand, not provided by attacker | |
| 573 | if(result) |
| 574 | return result; |
| 575 | result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); |
| C3: Curl_base64_encode encodes the internally generated random bytes | |
| C5: Base64 encoding is of internally generated random bytes | |
| 576 | if(result) |
| 577 | return result; |
| 578 | DEBUGASSERT(randlen < sizeof(keyval)); |
| 579 | if(randlen >= sizeof(keyval)) |
| C1: Explicit bounds check: if(randlen >= sizeof(keyval)) return CURLE_FAILED_INIT; | |
| C4: Bounds check prevents buffer overflow | |
| 580 | return CURLE_FAILED_INIT; |
| C1: Bounds check prevents strcpy when input would overflow | |
| C4: Function returns error if bounds would be exceeded | |
| 581 | strcpy(keyval, randstr); |
| C2: strcpy(keyval, randstr); at line 581 | |
| 582 | free(randstr); |
| 583 | for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { |
| 584 | if(!Curl_checkheaders(data, STRCONST(heads[i].name))) { |
| 585 | #ifdef USE_HYPER |
| 586 | char field[128]; |
| 587 | msnprintf(field, sizeof(field), "%s %s", heads[i].name, |
| 588 | heads[i].val); |
| 589 | result = Curl_hyper_header(data, req, field); |
| 590 | #else |
| 591 | (void)data; |
| 592 | result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name, |
| 593 | heads[i].val); |
| 594 | #endif |
| 595 | } |
| 596 | } |
| 597 | k->upgr101 = UPGR101_WS; |
| 598 | return result; |
| 599 | } |
| 600 | |
| 601 | /* |
| 602 | * 'nread' is number of bytes of websocket data already in the buffer at |
| 603 | * 'mem'. |
| 604 | */ |
| 605 | CURLcode Curl_ws_accept(struct Curl_easy *data, |
| 606 | const char *mem, size_t nread) |