Stack Buffer Overflow in cURL wolfSSL Backend (lib/vtls/wolfssl.c)
What the report claims
A stack-based buffer overflow exists in the wssl_strerror function of cURL's wolfSSL TLS backend due to an unsafe strcpy call that ignores buffer size when DEBUGASSERT is disabled in release builds.
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 available evidence does not settle the report. A maintainer should investigate.
Why this finding?
- The code defect is real: in release builds, wssl_strerror uses strcpy without bounds checking, relying solely on a DEBUGASSERT that is compiled out.
- The provided source does not include the callers of wssl_strerror, so it cannot be determined whether an attacker can cause a buffer smaller than 13 bytes to be passed.
- The project policy does not categorically exclude this type of flaw (it exists in release builds, not debug-only), but exploitability must be established.
- A human maintainer should audit the call sites in lib/vtls/wolfssl.c to see if any path from network input can reach wssl_strerror with an undersized buffer.
What still needs checking
- Callers of wssl_strerror in lib/vtls/wolfssl.c (not provided in the source snippet)
- Evidence that a buffer ≤ 12 bytes can be passed from attacker-influenced code paths
How this analysis ran 5 claims · 7 citations verified
Checked against curl master on 2025-12-09 (a7c974e03857). 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 confirms that wssl_strerror uses strcpy without a bounds check in release builds (DEBUGASSERT is disabled). However, the provided source does not show the function's callers. To assess exploitability, we need to know whether any code path from network input can call wssl_strerror with a buffer smaller than 13 bytes. Could you provide the call chain or point to the specific caller(s) in lib/vtls/wolfssl.c that pass the buffer and size arguments?
Inspect all claims and source code 5 claims · 3 code excerpts
Report claims
The function wssl_strerror uses an unsafe strcpy call that does not respect the buffer size parameter, leading to a stack-based buffer overflow when DEBUGASSERT is disabled.
Read the reasoning
In release builds DEBUGASSERT is a no-op, so the strcpy at line 1552 executes with no bounds check. The function's contract (size > 40) is enforced only in debug builds.
The flaw occurs in the wssl_strerror function at lines 1542-1556 of lib/vtls/wolfssl.c, specifically at line 1552 where strcpy(buf, msg) is executed.
Read the reasoning
The provided source snippet confirms the function location and the exact line of the strcpy call.
The vulnerability is triggered when the function is called with a buffer size of 40 bytes or less, or more specifically when a buffer of 12 bytes is passed and the error string "Unknown error" (13 bytes including null terminator) is copied.
Read the reasoning
The code shows that when wolfSSL_ERR_error_string_n writes nothing (buf[0] == '\0'), the fallback string is copied unconditionally. A buffer of 12 bytes is too small for "Unknown error" (needs 13), so overflow occurs. The report's '40 bytes or less' refers to the DEBUGASSERT threshold; the actual overflow threshold is 13 bytes for non-zero error.
The overflow corrupts adjacent stack memory, potentially leading to denial of service or arbitrary code execution.
Read the reasoning
The provided code does not show callers of wssl_strerror. Whether an attacker can cause a small buffer to be passed depends on the call chain, which is not in the provided source. The impact claim (DoS or code execution) is plausible for a stack overflow but not demonstrated as reachable in curl.
A test program compiled with AddressSanitizer demonstrates the overflow by writing 14 bytes into a 12-byte stack buffer via the strcpy call in wssl_strerror.
Read the reasoning
The PoC code (provided in the report) correctly models the vulnerable logic and demonstrates the overflow with AddressSanitizer. However, the PoC is a standalone simulation, not a curl test case.
Read the original report
Source code
| 1542 | static char *wssl_strerror(unsigned long error, char *buf, unsigned long size) |
| C2: Function definition starts at line 1542 | |
| 1543 | { |
| 1544 | DEBUGASSERT(size > 40); |
| C1: DEBUGASSERT(size > 40) only active in debug builds; in release builds (-DNDEBUG) it expands to nothing | |
| 1545 | *buf = '\0'; |
| 1546 | |
| 1547 | wolfSSL_ERR_error_string_n(error, buf, size); |
| 1548 | |
| 1549 | if(!*buf) { |
| 1550 | const char *msg = error ? "Unknown error" : "No error"; |
| C3: msg = error ? "Unknown error" : "No error"; "Unknown error" is 12 chars + NUL = 13 bytes | |
| 1551 | /* the string fits because the assert above assures this */ |
| C1: Comment acknowledges the assert is the only guarantee: 'the string fits because the assert above assures this' | |
| 1552 | strcpy(buf, msg); |
| C1: strcpy(buf, msg) copies without checking the 'size' parameter; msg is "Unknown error" (13 bytes with NUL) or "No error" (8 bytes with NUL) | |
| C2: strcpy(buf, msg) at line 1552 | |
| C3: strcpy writes the full string including NUL regardless of 'size' | |
| 1553 | } |
| 1554 | |
| 1555 | return buf; |
| 1556 | } |
| 1527 | static char *wssl_strerror(unsigned long error, char *buf, |
| 1528 | unsigned long size) |
| 1529 | { |
| 1530 | DEBUGASSERT(size > 40); |
| 1531 | *buf = '\0'; |
| 1532 | |
| 1533 | wolfSSL_ERR_error_string_n(error, buf, size); |
| 1534 | |
| 1535 | if(!*buf) { |
| 1536 | const char *msg = error ? "Unknown error" : "No error"; |
| 1537 | /* the string fits because the assert above assures this */ |
| 1538 | strcpy(buf, msg); |
| 1539 | } |
| 1540 | |
| 1541 | return buf; |
| 1542 | } |
Show 13 lines (not cited by the judge)
| 248 | int main(int argc, char **argv) |
| 249 | { |
| 250 | size_t i; |
| 251 | |
| 252 | (void)argc; |
| 253 | (void)argv; |
| 254 | |
| 255 | for(i = 0; i < CURL_ARRAYSIZE(disabled); i++) |
| 256 | /* !checksrc! disable BANNEDFUNC 1 */ |
| 257 | printf("%s\n", disabled[i]); |
| 258 | |
| 259 | return 0; |
| 260 | } |