✓ProveIt
Examples / Report #3250490 Saved analysis

Disk Space Exhaustion leading to a Denial of Service (DoS)

curl · 2025-07-1411.9s analysis · $0.020 in model calls

What the report claims

The curl debug trace function can write unlimited data to log files, causing disk space exhaustion and denial of service when large data transfers occur.

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

Needs a policy review

The code does what the report says, but curl's own policy may exclude it. A maintainer decides.

curl policy: Debug & Experiments
Judgment withdrawn. The answer failed the evidence check.
See the withdrawn model reasoning

The following reasoning failed the citation check and does not support the final finding.

  • The --trace/--trace-ascii/--trace-bin options are debug features explicitly enabled by the user (off by default).
  • The project's security policy explicitly states: 'Vulnerabilities in features which are off by default (in the build) and documented as experimental, or exist only in debug mode, are not eligible for a reward and we do not consider them security problems.'
  • The disk growth is a direct, documented consequence of enabling trace logging — the feature is designed to capture all transferred data for debugging.
  • An attacker would need the user to voluntarily run curl with --trace against a malicious server; this is equivalent to tricking a user into running a crafted command line, which the policy also excludes.
How this analysis ran 5 claims · 7 citations verified

Evidence check: the behavior is real; the project's policy section 'Debug & Experiments' may exclude it

Checked against curl master on 2025-07-14 (b6c636256d43). Test 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 · 18 references checked out, 4 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 4505 tokens · $0.0003
3 · Evidence
8 code snippets
real curl source at b6c636256d43
4 · Judgment
Nemotron-3-Ultra-550b-a55b
14955 tokens · 7.7s · $0.0194
5 · Receipts
7 of 7 citations verified
every citation must point at code the judge was shown
Inspect the withdrawn draft reply

This draft belongs to the withdrawn judgment. It may contain unsupported claims.

Thank you for the detailed report and PoC. The behavior you describe is real: when --trace or --trace-ascii is used, curl writes all transferred data to the trace file without a size limit. However, curl's security policy classifies debug/tracing features (which are off by default and must be explicitly enabled by the user) as 'not security problems' — see the 'Debug & Experiments' section. Since the user must choose to enable tracing and specify the output file, the disk consumption is an expected consequence of the feature's design, not a vulnerability. We therefore do not consider this a security issue.

Inspect all claims and source code 5 claims · 8 code excerpts

This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.

Report claims

C1flaw supported

tool_debug_cb function writes large amounts of debug data to a log file without size limits when --trace or --trace-ascii options are used

Read the reasoning

The code confirms tool_debug_cb writes trace data to global->trace_stream without any cumulative size checks or limits. The dump function (called at line 230-231) writes formatted hex/ASCII output per byte of transferred data.

C2location supported

writes raw HTTP header data to heads->stream in TRACE_PLAIN case CURLINFO_HEADER_OUT

Read the reasoning

In TRACE_PLAIN mode, CURLINFO_HEADER_OUT writes header data line by line to the output stream (which is global->trace_stream when tracing enabled) without size limits.

C3trigger supported

large HTTP response or unlimited upload causes continuous writes to trace log

Read the reasoning

A server sending unlimited chunked data (as in the PoC) will trigger repeated CURLINFO_DATA_IN callbacks, each causing writes to the trace log. For TRACE_ASCII/BIN, dump() writes O(data size) formatted output.

C4impact supported

disk space exhaustion can disrupt other services on the same server

Read the reasoning

The trace file is opened in append/text mode and writes accumulate without bound. Disk exhaustion is a plausible consequence of unbounded writes.

C5reproduction supported

run curl with --trace output.log against an unlimited data server to observe growing output.log

Read the reasoning

The PoC uses --trace output.log which enables tracing to a file. The provided server streams unlimited data, triggering continuous trace writes. Reproduction is feasible.

Deterministic checks that failed

  • symbol estimate_formatted_data_size() does not exist in curl at b6c636256d43 or in any release since 7.50
  • symbol tool_strbytel() does not exist in curl at b6c636256d43 or in any release since 7.50
  • quote 5 of 12 quoted lines appear nowhere in curl, e.g. 'if (global->trace_stream && global->trace_fopened && global-'
  • quote 6 of 12 quoted lines appear nowhere in curl, e.g. 'trace tracetype, curl_infotype infotype, struct GlobalConfig'
Read the original report (excerpt)

Full report on HackerOne ↗

# Description The tool_debug_cb function can write large amounts of debug data to a log file if the --trace or --trace-ascii options are used with a large volume of data. If an attacker can cause cURL to download or upload a very large amount of data (e.g., via a very large HTTP response or an unlimited upload), the log file generated by this debug function can grow indefinitely. This can lead to disk space exhaustion on the system where cURL is running, which in turn can disrupt other services running on the same server. This section writes raw HTTP header data to the heads->stream if --dump-header is used. If heads->stream happens to be the same file as the trace output, or if it's another file with unlimited growth potential, it contributes to the problem. ``` /* In tool_debug_cb */ if(per->config->headerfile && heads->stream) { size_t rc = fwrite(ptr, size, nmemb, heads->stream); // <-- Vulnerable write if(rc != cb) return rc; /* flush the stream to send off what we got earlier */ if(fflush(heads->stream)) { errorf(per->config->global, "Failed writing headers to %s", per->config->headerfile); return CURL_WRITEFUNC_ERROR; } } ``` When global->tracetype == TRACE_PLAIN, this block handles text, headers, and data alerts. ``` /* In tool_debug_cb, in the TRACE_PLAIN section */ case CURLINFO_HEADER_OUT: if(size > 0) { size_t st = 0; size_t i; for(i = 0; i < size - 1; i++) { if(data[i] == '\n') { /* LF */ if(!newl) { log_line_start(output, timebuf, idsbuf, type); } (void)fwrite(data + st, i - st + 1, 1, output); // <-- Vulnerable write st = i + 1; newl = FALSE; } } if(!newl) log_line_start(output, timebuf, idsbuf, type); (void)fwrite(data + st, i - st + 1, 1, output); // <-- Vulnerable write } newl = (size && (data[size - 1] != '\n')); traced_data = FALSE; break; case CURLINFO_TEXT: case CURLINFO_HEADER_IN: if(!newl) log_line_start(output, timebuf, idsbuf, type); (void)fwrite(data, size, 1, output); // <-- Vulnerable write newl = (size && (data[size - 1] != '\n')); traced_data = FALSE; break; ``` ``` case CURLINFO_DATA_OUT: case CURLINFO_DATA_IN: case CURLINFO_SSL_DATA_IN: case CURLINFO_SSL_DATA_OUT: …

Source code

src/tool_cb_dbg.clines 78–233 · definition of tool_debug_cb
78int tool_debug_cb(CURL *handle, curl_infotype type,
79 char *data, size_t size,
80 void *userdata)
81{
82 struct OperationConfig *operation = userdata;
83 struct GlobalConfig *global = operation->global;
84 FILE *output = tool_stderr;
85 const char *text;
86 struct timeval tv;
87 char timebuf[20];
88 /* largest signed 64-bit is: 9,223,372,036,854,775,807
89 * max length in decimal: 1 + (6*3) = 19
90 * formatted via TRC_IDS_FORMAT_IDS_2 this becomes 2 + 19 + 1 + 19 + 2 = 43
91 * negative xfer-id are not printed, negative conn-ids use TRC_IDS_FORMAT_1
92 */
93 char idsbuf[60];
94 curl_off_t xfer_id, conn_id;
95
96 (void)handle; /* not used */
97
98 if(global->tracetime) {
99 tv = tvrealnow();
100 msnprintf(timebuf, sizeof(timebuf), "%s.%06ld ",
101 hms_for_sec(tv.tv_sec), (long)tv.tv_usec);
102 }
103 else
104 timebuf[0] = 0;
105
106 if(handle && global->traceids &&
107 !curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
108 if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
109 conn_id >= 0) {
110 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
111 xfer_id, conn_id);
112 }
113 else {
114 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
115 }
116 }
117 else
118 idsbuf[0] = 0;
119
120 if(!global->trace_stream) {
121 /* open for append */
122 if(!strcmp("-", global->trace_dump))
C5: --trace sets global->trace_dump, enabling trace_stream
123 global->trace_stream = stdout;
124 else if(!strcmp("%", global->trace_dump))
125 /* Ok, this is somewhat hackish but we do it undocumented for now */
126 global->trace_stream = tool_stderr;
127 else {
128 global->trace_stream = fopen(global->trace_dump, FOPEN_WRITETEXT);
C4: Trace file opened with fopen(global->trace_dump, FOPEN_WRITETEXT) - grows indefinitely
129 global->trace_fopened = TRUE;
130 }
131 }
132
133 if(global->trace_stream)
134 output = global->trace_stream;
135
136 if(!output) {
137 warnf(global, "Failed to create/open output");
138 return 0;
139 }
140
141 if(global->tracetype == TRACE_PLAIN) {
C1: TRACE_PLAIN case writes via fwrite/fprintf to output (global->trace_stream) without size limits
142 static bool newl = FALSE;
143 static bool traced_data = FALSE;
144
145 switch(type) {
146 case CURLINFO_HEADER_OUT:
C2: CURLINFO_HEADER_OUT case in TRACE_PLAIN writes headers via fwrite at lines 155, 162
147 if(size > 0) {
148 size_t st = 0;
149 size_t i;
150 for(i = 0; i < size - 1; i++) {
151 if(data[i] == '\n') { /* LF */
152 if(!newl) {
153 log_line_start(output, timebuf, idsbuf, type);
154 }
155 (void)fwrite(data + st, i - st + 1, 1, output);
156 st = i + 1;
157 newl = FALSE;
158 }
159 }
160 if(!newl)
161 log_line_start(output, timebuf, idsbuf, type);
162 (void)fwrite(data + st, i - st + 1, 1, output);
163 }
164 newl = (size && (data[size - 1] != '\n'));
165 traced_data = FALSE;
166 break;
167 case CURLINFO_TEXT:
168 case CURLINFO_HEADER_IN:
169 if(!newl)
170 log_line_start(output, timebuf, idsbuf, type);
171 (void)fwrite(data, size, 1, output);
172 newl = (size && (data[size - 1] != '\n'));
173 traced_data = FALSE;
174 break;
175 case CURLINFO_DATA_OUT:
C3: CURLINFO_DATA_IN/OUT cases write '[%zu bytes data]\n' but dump() logs every byte for TRACE_ASCII/BIN
176 case CURLINFO_DATA_IN:
177 case CURLINFO_SSL_DATA_IN:
178 case CURLINFO_SSL_DATA_OUT:
179 if(!traced_data) {
180 /* if the data is output to a tty and we are sending this debug trace
181 to stderr or stdout, we do not display the alert about the data not
182 being shown as the data _is_ shown then just not via this
183 function */
184 if(!global->isatty ||
185 ((output != tool_stderr) && (output != stdout))) {
186 if(!newl)
187 log_line_start(output, timebuf, idsbuf, type);
188 fprintf(output, "[%zu bytes data]\n", size);
189 newl = FALSE;
190 traced_data = TRUE;
191 }
192 }
193 break;
194 default: /* nada */
195 newl = FALSE;
196 traced_data = FALSE;
197 break;
198 }
199
200 return 0;
201 }
202
203 switch(type) {
204 case CURLINFO_TEXT:
205 fprintf(output, "%s%s== Info: %.*s", timebuf, idsbuf, (int)size, data);
206 FALLTHROUGH();
207 default: /* in case a new one is introduced to shock us */
208 return 0;
209
210 case CURLINFO_HEADER_OUT:
211 text = "=> Send header";
212 break;
213 case CURLINFO_DATA_OUT:
214 text = "=> Send data";
215 break;
216 case CURLINFO_HEADER_IN:
217 text = "<= Recv header";
218 break;
219 case CURLINFO_DATA_IN:
220 text = "<= Recv data";
221 break;
222 case CURLINFO_SSL_DATA_IN:
223 text = "<= Recv SSL data";
224 break;
225 case CURLINFO_SSL_DATA_OUT:
226 text = "=> Send SSL data";
227 break;
228 }
229
230 dump(timebuf, idsbuf, text, output, (unsigned char *) data, size,
C1: dump() called for TRACE_ASCII/TRACE_BIN, writes formatted data without size limits
C3: dump() called for data types, writes formatted output proportional to data size
231 global->tracetype, type);
232 return 0;
233}
src/tool_cb_dbg.c@curl-8_14_1lines 80–235 · definition of tool_debug_cb as in release curl-8_14_1; differs from master; commits that changed it since: d516628d14 curl: unify pointer names to global config
80int tool_debug_cb(CURL *handle, curl_infotype type,
81 char *data, size_t size,
82 void *userdata)
83{
84 struct OperationConfig *operation = userdata;
85 struct GlobalConfig *config = operation->global;
86 FILE *output = tool_stderr;
87 const char *text;
88 struct timeval tv;
89 char timebuf[20];
90 /* largest signed 64-bit is: 9,223,372,036,854,775,807
91 * max length in decimal: 1 + (6*3) = 19
92 * formatted via TRC_IDS_FORMAT_IDS_2 this becomes 2 + 19 + 1 + 19 + 2 = 43
93 * negative xfer-id are not printed, negative conn-ids use TRC_IDS_FORMAT_1
94 */
95 char idsbuf[60];
96 curl_off_t xfer_id, conn_id;
97
98 (void)handle; /* not used */
99
100 if(config->tracetime) {
101 tv = tvrealnow();
102 msnprintf(timebuf, sizeof(timebuf), "%s.%06ld ",
103 hms_for_sec(tv.tv_sec), (long)tv.tv_usec);
104 }
105 else
106 timebuf[0] = 0;
107
108 if(handle && config->traceids &&
109 !curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
110 if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
111 conn_id >= 0) {
112 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
113 xfer_id, conn_id);
114 }
115 else {
116 msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
117 }
118 }
119 else
120 idsbuf[0] = 0;
121
122 if(!config->trace_stream) {
123 /* open for append */
124 if(!strcmp("-", config->trace_dump))
125 config->trace_stream = stdout;
126 else if(!strcmp("%", config->trace_dump))
127 /* Ok, this is somewhat hackish but we do it undocumented for now */
128 config->trace_stream = tool_stderr;
129 else {
130 config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT);
131 config->trace_fopened = TRUE;
132 }
133 }
134
135 if(config->trace_stream)
136 output = config->trace_stream;
137
138 if(!output) {
139 warnf(config, "Failed to create/open output");
140 return 0;
141 }
142
143 if(config->tracetype == TRACE_PLAIN) {
144 static bool newl = FALSE;
145 static bool traced_data = FALSE;
146
147 switch(type) {
148 case CURLINFO_HEADER_OUT:
149 if(size > 0) {
150 size_t st = 0;
151 size_t i;
152 for(i = 0; i < size - 1; i++) {
153 if(data[i] == '\n') { /* LF */
154 if(!newl) {
155 log_line_start(output, timebuf, idsbuf, type);
156 }
157 (void)fwrite(data + st, i - st + 1, 1, output);
158 st = i + 1;
159 newl = FALSE;
160 }
161 }
162 if(!newl)
163 log_line_start(output, timebuf, idsbuf, type);
164 (void)fwrite(data + st, i - st + 1, 1, output);
165 }
166 newl = (size && (data[size - 1] != '\n'));
167 traced_data = FALSE;
168 break;
169 case CURLINFO_TEXT:
170 case CURLINFO_HEADER_IN:
171 if(!newl)
172 log_line_start(output, timebuf, idsbuf, type);
173 (void)fwrite(data, size, 1, output);
174 newl = (size && (data[size - 1] != '\n'));
175 traced_data = FALSE;
176 break;
177 case CURLINFO_DATA_OUT:
178 case CURLINFO_DATA_IN:
179 case CURLINFO_SSL_DATA_IN:
180 case CURLINFO_SSL_DATA_OUT:
181 if(!traced_data) {
182 /* if the data is output to a tty and we are sending this debug trace
183 to stderr or stdout, we do not display the alert about the data not
184 being shown as the data _is_ shown then just not via this
185 function */
186 if(!config->isatty ||
187 ((output != tool_stderr) && (output != stdout))) {
188 if(!newl)
189 log_line_start(output, timebuf, idsbuf, type);
190 fprintf(output, "[%zu bytes data]\n", size);
191 newl = FALSE;
192 traced_data = TRUE;
193 }
194 }
195 break;
196 default: /* nada */
197 newl = FALSE;
198 traced_data = FALSE;
199 break;
200 }
201
202 return 0;
203 }
204
205 switch(type) {
206 case CURLINFO_TEXT:
207 fprintf(output, "%s%s== Info: %.*s", timebuf, idsbuf, (int)size, data);
208 FALLTHROUGH();
209 default: /* in case a new one is introduced to shock us */
210 return 0;
211
212 case CURLINFO_HEADER_OUT:
213 text = "=> Send header";
214 break;
215 case CURLINFO_DATA_OUT:
216 text = "=> Send data";
217 break;
218 case CURLINFO_HEADER_IN:
219 text = "<= Recv header";
220 break;
221 case CURLINFO_DATA_IN:
222 text = "<= Recv data";
223 break;
224 case CURLINFO_SSL_DATA_IN:
225 text = "<= Recv SSL data";
226 break;
227 case CURLINFO_SSL_DATA_OUT:
228 text = "=> Send SSL data";
229 break;
230 }
231
232 dump(timebuf, idsbuf, text, output, (unsigned char *) data, size,
233 config->tracetype, type);
234 return 0;
235}
src/tool_cb_dbg.clines 33–54 · definition of dump
Show 22 lines (not cited by the judge)
33static void dump(const char *timebuf, const char *idsbuf, const char *text,
34 FILE *stream, const unsigned char *ptr, size_t size,
35 trace tracetype, curl_infotype infotype);
36
37/*
38 * Return the formatted HH:MM:SS for the tv_sec given.
39 * NOT thread safe.
40 */
41static const char *hms_for_sec(time_t tv_sec)
42{
43 static time_t cached_tv_sec;
44 static char hms_buf[12];
45
46 if(tv_sec != cached_tv_sec) {
47 /* !checksrc! disable BANNEDFUNC 1 */
48 struct tm *now = localtime(&tv_sec); /* not thread safe either */
49 msnprintf(hms_buf, sizeof(hms_buf), "%02d:%02d:%02d",
50 now->tm_hour, now->tm_min, now->tm_sec);
51 cached_tv_sec = tv_sec;
52 }
53 return hms_buf;
54}
src/tool_msgs.clines 140–148 · definition of errorf
Show 9 lines (not cited by the judge)
140void errorf(struct GlobalConfig *global, const char *fmt, ...)
141{
142 if(!global->silent || global->showerror) {
143 va_list ap;
144 va_start(ap, fmt);
145 voutf(global, ERROR_PREFIX, fmt, ap);
146 va_end(ap);
147 }
148}
src/tool_msgs.c@curl-8_14_1lines 142–150 · definition of errorf as in release curl-8_14_1; differs from master; commits that changed it since: d516628d14 curl: unify pointer names to global config
Show 9 lines (not cited by the judge)
142void errorf(struct GlobalConfig *config, const char *fmt, ...)
143{
144 if(!config->silent || config->showerror) {
145 va_list ap;
146 va_start(ap, fmt);
147 voutf(config, ERROR_PREFIX, fmt, ap);
148 va_end(ap);
149 }
150}
src/tool_cb_dbg.clines 56–70 · definition of log_line_start
Show 15 lines (not cited by the judge)
56static void log_line_start(FILE *log, const char *timebuf,
57 const char *idsbuf, curl_infotype type)
58{
59 /*
60 * This is the trace look that is similar to what libcurl makes on its
61 * own.
62 */
63 static const char * const s_infotype[] = {
64 "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
65 };
66 if((timebuf && *timebuf) || (idsbuf && *idsbuf))
67 fprintf(log, "%s%s%s", timebuf, idsbuf, s_infotype[type]);
68 else
69 fputs(s_infotype[type], log);
70}
src/tool_msgs.clines 106–112 · definition of warnf
Show 7 lines (not cited by the judge)
106void warnf(struct GlobalConfig *global, const char *fmt, ...)
107{
108 va_list ap;
109 va_start(ap, fmt);
110 voutf(global, WARN_PREFIX, fmt, ap);
111 va_end(ap);
112}
src/tool_msgs.c@curl-8_14_1lines 108–114 · definition of warnf as in release curl-8_14_1; differs from master; commits that changed it since: d516628d14 curl: unify pointer names to global config
Show 7 lines (not cited by the judge)
108void warnf(struct GlobalConfig *config, const char *fmt, ...)
109{
110 va_list ap;
111 va_start(ap, fmt);
112 voutf(config, WARN_PREFIX, fmt, ap);
113 va_end(ap);
114}