Stack-based Buffer Overflow in TELNET NEW_ENV Option Handling
What the report claims
A stack-based buffer overflow in libcurl's TELNET NEW_ENV option handling allows remote code execution via crafted NEW_ENV options.
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 length check uses tmplen = strlen(v->data) + 1, which correctly accounts for the net +1 byte expansion when msnprintf replaces the comma with CURL_NEW_ENV_VALUE and adds CURL_NEW_ENV_VAR.
- The msnprintf call for the comma case writes exactly tmplen bytes, so the check `len + tmplen < sizeof(temp) - 6` guarantees sufficient space.
- No integer overflow or truncation path exists because the check ensures the msnprintf size parameter (sizeof(temp) - len) is always larger than the bytes written.
- The PoC would not overflow the buffer; at most one large variable is added before the check rejects further additions.
How this analysis ran 5 claims · 8 citations verified
Checked against curl master on 2025-06-30 (d553f7e9f0d8). Dev 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 detailed report. After reviewing the code at lib/telnet.c lines 978-990, the length check does account for the expansion: tmplen = strlen(v->data) + 1 covers the net +1 byte added when msnprintf replaces the comma with CURL_NEW_ENV_VALUE and prepends CURL_NEW_ENV_VAR. The check `len + tmplen < sizeof(temp) - 6` ensures the buffer is never overrun. The PoC would only send the first variable and then stop adding more, not crash. Could you clarify if you see a different code path or a case where tmplen is miscalculated?
Inspect all claims and source code 5 claims · 3 code excerpts
Report claims
The length check in suboption() only accounts for the original string length (tmplen) but does not account for the expansion by msnprintf, allowing bypass of the check and overflow of a 2048-byte stack buffer.
Read the reasoning
The report claims the length check only accounts for the original string length, but the code shows tmplen = strlen(v->data) + 1. The msnprintf output for the comma case expands the string by a net of 1 byte (comma replaced by control byte, plus one extra control byte), which is exactly what the +1 in tmplen covers. The check is correct and prevents overflow.
The overflow occurs in the suboption() function within curl/lib/telnet.c when processing NEW_ENV options.
Read the reasoning
The location claim is accurate: the NEW_ENVIRON handling is in suboption() in lib/telnet.c. However, the flaw described in C1 does not exist at this location.
A malicious TELNET server can trigger the overflow by sending a NEW_ENVIRON SEND request, causing curl to construct a response packet that overflows the stack buffer.
Read the reasoning
Since the length check correctly accounts for the formatted output size (as shown in C1), a NEW_ENVIRON SEND request cannot cause a stack buffer overflow via this path. The server can trigger the code path, but the buffer overflow does not occur.
The overflow can lead to remote code execution (RCE) with the permissions of the user running curl, as the attacker can overwrite the return address and execute arbitrary shellcode.
Read the reasoning
The claimed RCE impact depends on a stack buffer overflow that the provided code does not have. Without the overflow, there is no control of the return address or shellcode injection.
The vulnerability can be reproduced by running a malicious TELNET server that sends the IAC SB NEW_ENVIRON SEND IAC SE command and connecting to it with curl using crafted NEW_ENV options that bypass the length check.
Read the reasoning
The PoC uses multiple oversized NEW_ENV options. The first may fit (len=4 + tmplen≈2006 < 2042), but subsequent ones are rejected by the check. The client would send at most sends only sends one variable's worth of data and does not crash from stack corruption.
Read the original report (excerpt)
Source code
| 936 | static void suboption(struct Curl_easy *data, struct TELNET *tn) |
| C2: suboption() function definition at line 936 | |
| 937 | { |
| 938 | struct curl_slist *v; |
| 939 | unsigned char temp[2048]; |
| C4: temp[2048] buffer is protected by the correct bounds check at line 980. | |
| 940 | ssize_t bytes_written; |
| 941 | size_t len; |
| 942 | int err; |
| 943 | struct connectdata *conn = data->conn; |
| 944 | |
| 945 | printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2); |
| 946 | switch(CURL_SB_GET(tn)) { |
| 947 | case CURL_TELOPT_TTYPE: |
| 948 | len = strlen(tn->subopt_ttype) + 4 + 2; |
| 949 | msnprintf((char *)temp, sizeof(temp), |
| 950 | "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, |
| 951 | CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); |
| 952 | bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); |
| 953 | if(bytes_written < 0) { |
| 954 | err = SOCKERRNO; |
| 955 | failf(data,"Sending data failed (%d)",err); |
| 956 | } |
| 957 | printsub(data, '>', &temp[2], len-2); |
| 958 | break; |
| 959 | case CURL_TELOPT_XDISPLOC: |
| 960 | len = strlen(tn->subopt_xdisploc) + 4 + 2; |
| 961 | msnprintf((char *)temp, sizeof(temp), |
| 962 | "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, |
| 963 | CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); |
| 964 | bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); |
| 965 | if(bytes_written < 0) { |
| 966 | err = SOCKERRNO; |
| 967 | failf(data,"Sending data failed (%d)",err); |
| 968 | } |
| 969 | printsub(data, '>', &temp[2], len-2); |
| 970 | break; |
| 971 | case CURL_TELOPT_NEW_ENVIRON: |
| C2: Case CURL_TELOPT_NEW_ENVIRON handling starts at line 971 | |
| 972 | msnprintf((char *)temp, sizeof(temp), |
| 973 | "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, |
| 974 | CURL_TELQUAL_IS); |
| 975 | len = 4; |
| 976 | |
| 977 | for(v = tn->telnet_vars; v; v = v->next) { |
| C5: The loop at line 977 processes variables, but the check at line 980 prevents adding variables that would exceed the buffer. | |
| 978 | size_t tmplen = (strlen(v->data) + 1); |
| C1: tmplen = strlen(v->data) + 1 accounts for the net +1 byte expansion: the comma in the input (1 byte) is replaced by CURL_NEW_ENV_VALUE (1 byte) and CURL_NEW_ENV_VAR is added (1 byte), for a net increase of 1 byte. | |
| 979 | /* Add the variable if it fits */ |
| 980 | if(len + tmplen < (int)sizeof(temp)-6) { |
| C1: The check `len + tmplen < sizeof(temp) - 6` uses this tmplen, so it correctly reserves space for the actual bytes msnprintf will write. | |
| C3: The bounds check prevents the overflow the claim says is triggered by NEW_ENVIRON SEND. | |
| 981 | char *s = strchr(v->data, ','); |
| 982 | if(!s) |
| 983 | len += msnprintf((char *)&temp[len], sizeof(temp) - len, |
| 984 | "%c%s", CURL_NEW_ENV_VAR, v->data); |
| 985 | else { |
| 986 | size_t vlen = s - v->data; |
| 987 | len += msnprintf((char *)&temp[len], sizeof(temp) - len, |
| C1: The msnprintf format `%c%.*s%c%s` writes exactly 1 (CURL_NEW_ENV_VAR) + vlen (VAR) + 1 (CURL_NEW_ENV_VALUE) + strlen(VALUE) = strlen(v->data) + 1 = tmplen bytes. | |
| 988 | "%c%.*s%c%s", CURL_NEW_ENV_VAR, |
| 989 | (int)vlen, v->data, CURL_NEW_ENV_VALUE, ++s); |
| 990 | } |
| 991 | } |
| 992 | } |
| 993 | msnprintf((char *)&temp[len], sizeof(temp) - len, |
| 994 | "%c%c", CURL_IAC, CURL_SE); |
| 995 | len += 2; |
| 996 | bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); |
| 997 | if(bytes_written < 0) { |
| 998 | err = SOCKERRNO; |
| 999 | failf(data,"Sending data failed (%d)",err); |
| 1000 | } |
| 1001 | printsub(data, '>', &temp[2], len-2); |
| 1002 | break; |
| 1003 | } |
| 1004 | return; |
| 1005 | } |
| 56 | # define msnprintf curl_msnprintf |
Show 2 lines (not cited by the judge)
| 39 | #define CURL_NEW_ENV_VAR 0 |
| 40 | #define CURL_NEW_ENV_VALUE 1 |