Use of Deprecated strcpy() with Fixed-Size Buffers in Progress Time Formatting
What the report claims
The report claims that curl's time2str function uses an unsafe strcpy() to copy a 9-byte string into 10-byte buffers, creating an off-by-one risk that could lead to stack corruption under certain conditions.
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.
curl policy: Not practically possibleWhy this finding?
- The report's own numbers show 9-byte string into 10-byte buffer — no overflow exists.
- The 'off-by-one' claim is incorrect; there is one byte of margin, not a deficit.
- All other code paths in time2str explicitly limit output to 9 bytes via curl_msnprintf(r, 9, ...), confirming the design intent.
- The PoC demonstrates a simulated overflow that cannot occur with the actual buffer sizes.
- Future hypothetical string changes are not a current vulnerability.
How this analysis ran 6 claims · 8 citations verified
Checked against curl master on 2025-10-22 (a8f16da71240). 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 analysis. However, the code shows the string '--:--:--' is 9 bytes (8 chars + null) and your research indicates the caller buffers are 10 bytes. Nine bytes safely fit in ten bytes — there is no overflow. The comment at line 88 confirms the 9-byte design, and all other paths in time2str use curl_msnprintf(r, 9, ...) to enforce this limit. The 'off-by-one' risk you describe would require a 10-byte string in a 9-byte buffer, which is not the case here. This does not appear to be a security vulnerability in the current code.
Inspect all claims and source code 6 claims · 3 code excerpts
Report claims
Uses unsafe strcpy() to copy "--:--:--" into caller-provided buffers
Read the reasoning
The report claims strcpy is 'unsafe' but the string copied is 9 bytes (8 chars + null). The report itself states caller buffers are 10 bytes. Nine bytes into a 10-byte buffer is safe, not an overflow. The 'off-by-one' characterization is incorrect; there is one byte of margin.
Located in src/tool_progress.c at line 94
Read the reasoning
The strcpy call exists at the reported location in src/tool_progress.c.
Triggered when curl displays progress with unknown time estimates
Read the reasoning
The vulnerable path is triggered when seconds <= 0, which occurs for unknown time estimates during progress display.
Potential stack corruption, crashes, or information disclosure
Read the reasoning
No stack corruption, crash, or information disclosure occurs because 9 bytes safely fit in the 10-byte buffers the report describes. The PoC simulates an overflow that cannot happen with the actual sizes.
Reproduced by calling time2str with 10-byte buffers containing "--:--:--"
Read the reasoning
The reproduction claims a vulnerability but 9 bytes into 10 bytes is correct usage. The PoC's claimed corruption ('SECRET_DATA123' becoming '3') would require an actual overflow, which does not occur with these sizes.
Future risk if string changes to "---:--:--" causing immediate overflow
Read the reasoning
Future risk from a hypothetical string change is not a current vulnerability in the code. The current string is 9 bytes and the buffer is 10 bytes per the report's own analysis.
Deterministic checks that failed
- quote only 0/8 quoted lines appear anywhere in curl at a8f16da71240
What the report says about other projects
not addressed The report references POSIX requirements for safe string handling in network applications
not addressed The report quotes the C standard requirement for null-terminated strings when assessing buffer safety
Read the original report (excerpt)
Source code
| 69 | { |
| 70 | struct per_transfer *per = clientp; |
| 71 | struct OperationConfig *config = per->config; |
| 72 | per->dltotal = dltotal; |
| 73 | per->dlnow = dlnow; |
| 74 | per->ultotal = ultotal; |
| 75 | per->ulnow = ulnow; |
| 76 | |
| 77 | if(per->abort) |
| 78 | return 1; |
| 79 | |
| 80 | if(config->readbusy) { |
| 81 | config->readbusy = FALSE; |
| 82 | curl_easy_pause(per->curl, CURLPAUSE_CONT); |
| 83 | } |
| 84 | |
| 85 | return 0; |
| 86 | } |
| 87 | |
| 88 | /* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero |
| C1: Comment states the string is '8 letters long (plus the zero byte)' = 9 bytes total | |
| C4: Design comment confirms 9-byte string fits in intended buffer | |
| 89 | byte) */ |
| 90 | static void time2str(char *r, curl_off_t seconds) |
| 91 | { |
| 92 | curl_off_t h; |
| 93 | if(seconds <= 0) { |
| C3: Trigger condition: if(seconds <= 0) - unknown/zero time estimates | |
| 94 | strcpy(r, "--:--:--"); |
| C1: strcpy copies '--:--:--' (8 chars + null = 9 bytes) | |
| C2: strcpy(r, "--:--:--") at line 94 in the provided source | |
| C5: strcpy copies 9 bytes; report states buffers are 10 bytes | |
| 95 | return; |
| 96 | } |
| 97 | h = seconds / 3600; |
| 98 | if(h <= 99) { |
| 99 | curl_off_t m = (seconds - (h * 3600)) / 60; |
| 100 | curl_off_t s = (seconds - (h * 3600)) - (m * 60); |
| 101 | curl_msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T |
| C1: Other paths use curl_msnprintf(r, 9, ...) confirming 9-byte design limit | |
| C4: All formatted outputs limited to 9 bytes via curl_msnprintf(r, 9, ...) | |
| 102 | ":%02" CURL_FORMAT_CURL_OFF_T |
| 103 | ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); |
| 104 | } |
| 105 | else { |
| 106 | /* this equals to more than 99 hours, switch to a more suitable output |
| 107 | format to fit within the limits. */ |
| 108 | curl_off_t d = seconds / 86400; |
| 109 | h = (seconds - (d * 86400)) / 3600; |
| 110 | if(d <= 999) |
| 111 | curl_msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T |
| 112 | "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); |
| 113 | else |
| 114 | curl_msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | static curl_off_t all_dltotal = 0; |
| 119 | static curl_off_t all_ultotal = 0; |
| 39 | static void time2str(char *r, curl_off_t seconds) |
| 40 | { |
| 41 | curl_off_t h; |
| 42 | if(seconds <= 0) { |
| 43 | strcpy(r, "--:--:--"); |
| 44 | return; |
| 45 | } |
| 46 | h = seconds / 3600; |
| 47 | if(h <= 99) { |
| 48 | curl_off_t m = (seconds - (h * 3600)) / 60; |
| 49 | curl_off_t s = (seconds - (h * 3600)) - (m * 60); |
| 50 | curl_msnprintf(r, 9, "%2" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_OFF_T, |
| 51 | h, m, s); |
| 52 | } |
| 53 | else { |
| 54 | /* this equals to more than 99 hours, switch to a more suitable output |
| 55 | format to fit within the limits. */ |
| 56 | curl_off_t d = seconds / 86400; |
| 57 | h = (seconds - (d * 86400)) / 3600; |
| 58 | if(d <= 999) |
| 59 | curl_msnprintf(r, 9, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); |
| 60 | else |
| 61 | curl_msnprintf(r, 9, "%7" FMT_OFF_T "d", d); |
| 62 | } |
| 63 | } |
Show 24 lines (not cited by the judge)
| 40 | static void time2str(char *r, curl_off_t seconds) |
| 41 | { |
| 42 | curl_off_t h; |
| 43 | if(seconds <= 0) { |
| 44 | strcpy(r, "--:--:--"); |
| 45 | return; |
| 46 | } |
| 47 | h = seconds / 3600; |
| 48 | if(h <= 99) { |
| 49 | curl_off_t m = (seconds - (h * 3600)) / 60; |
| 50 | curl_off_t s = (seconds - (h * 3600)) - (m * 60); |
| 51 | msnprintf(r, 9, "%2" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_OFF_T, h, m, s); |
| 52 | } |
| 53 | else { |
| 54 | /* this equals to more than 99 hours, switch to a more suitable output |
| 55 | format to fit within the limits. */ |
| 56 | curl_off_t d = seconds / 86400; |
| 57 | h = (seconds - (d * 86400)) / 3600; |
| 58 | if(d <= 999) |
| 59 | msnprintf(r, 9, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); |
| 60 | else |
| 61 | msnprintf(r, 9, "%7" FMT_OFF_T "d", d); |
| 62 | } |
| 63 | } |