✓ProveIt
Examples / Report #3395218 Saved analysis

Use of Deprecated strcpy() with Fixed-Size Buffers in Progress Time Formatting

curl · 2025-10-2218.7s analysis · $0.018 in model calls

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

Code contradicts the claim

The code provides evidence against the report's central claim. Review the cited lines before deciding.

curl policy: Not practically possible
Why 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.

1 · Grounding
partially grounded
no model · 4 references checked out, 1 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 2731 tokens · $0.0003
3 · Evidence
3 code snippets
real curl source at a8f16da71240 · 6 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
11652 tokens · 12.2s · $0.0180
5 · Receipts
8 of 8 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 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

C1flaw contradicted

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.

C2location supported

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.

C3trigger supported

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.

C4impact contradicted

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.

C5reproduction contradicted

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.

C6impact not verifiable

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)

Full report on HackerOne ↗

Step 2: Locate Vulnerable Code in Progress.c ``` # Find exact strcpy usage in tool_progress.c grep -n "strcpy" ./src/tool_progress.c # OUTPUT: # 94: strcpy(r, "--:--:--"); ``` Step 3: Analyze the Vulnerable Function ``` # View complete time2str function sed -n '/^static void time2str/,/^}/p' ./src/tool_progress.c ``` Vulnerable Function Found: ``` static void time2str(char *r, curl_off_t seconds) { curl_off_t h; if(seconds <= 0) { strcpy(r, "--:--:--"); // ⚠️ VULNERABLE LINE 94 return; } h = seconds / 3600; // ... rest of function } ``` Step 4: Find Where This Function is Called ``` # Find all calls to time2str grep -n "time2str" ./src/tool_progress.c # OUTPUT: # 90:static void time2str(char *r, curl_off_t seconds) # 260: time2str(time_left, left); # 261: time2str(time_total, est); # 264: time2str(time_left, 0); # 265: time2str(time_total, 0); # 267: time2str(time_spent, spent); ``` Step 5: Trace Buffer Declarations ``` # Find the function containing time2str calls grep -B 100 "time2str(time_left" ./src/tool_progress.c | grep -E "^[a-zA-Z_].*{$" | tail -1 # OUTPUT shows function name, then: sed -n '/^static int progress_meter/,/^}/p' ./src/tool_progress.c | grep -E "char.*time_" # OUTPUT: # char time_left[10]; # char time_total[10]; # char time_spent[10]; ``` Vulnerability Description Root Cause The time2str() function in src/tool_progress.c at line 94 uses unsafe strcpy() to copy the string "--:--:--" into caller-provided buffers. Analysis reveals that callers declare buffers of exactly 10 bytes, while the string requires 9 bytes (8 characters + null terminator), creating a dangerous off-by-one situation. Technical Analysis ``` // CALLER BUFFERS (in progress_meter function): char time_left[10]; // Exactly 10 bytes allocated char time_total[10]; // Exactly 10 bytes char time_spent[10]; // Exactly 10 bytes // VULNERABLE FUNCTION: static void time2str(char *r, curl_off_t seconds) { if(seconds <= 0) { strcpy(r, "--:--:--"); // Copies 9 bytes: 8 chars + null } } // USAGE: time2str(time_left, 0); // 9 bytes into 10 byte buffer ``` The Danger Theoretical Safety: 9 bytes should fit in 10 byte buffer Actual Risk: Compiler padding, stack alignment, and architecture differences create unpredictable memory layout Future Risk: If the string changes to "---:--:--" (10 chars), immediate buffe …

Source code

src/tool_progress.clines 69–119 · lines 94-94 cited by the report
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) */
90static 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
118static curl_off_t all_dltotal = 0;
119static curl_off_t all_ultotal = 0;
lib/progress.clines 39–63 · definition of time2str
39static 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}
lib/progress.c@curl-8_16_0lines 40–63 · definition of time2str as in release curl-8_16_0; differs from master; commits that changed it since: b12da22db1 lib: stop overriding system printf symbols | 4b8278fb3d progress: expand to use 6 characters per size
Show 24 lines (not cited by the judge)
40static 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}