curl · Report #4124849
CVE-2026-111951: curl -OJ truncate without filename
How it was found
Summary:
With -O -J, when a response carries a Content-Disposition header without a usable filename= (for example inline) before the one that names an existing file, curl truncates that existing file to 0 bytes and then exits 23 saying it refused to overwrite it. This breaks the promise in docs/cmdline-opts/remote-header-name.md: "If the server specifies a filename and a file with that name already exists in the destination directory, it is not overwritten and an error occurs".
The cause is src/tool_cb_hdr.c lines 405 to 408 in curl-8_23_0: when the header has no filename=, the code falls through to tool_create_output_file(), which opens the URL-derived name with fopen(fname, "wb") (src/tool_cb_wrt.c line 87) because is_cd_filename is still FALSE. The following filename="victim" header is then refused by set_filename() since the stream is already open, but the file is already empty. The same happens with -L when a 302 carries Content-Disposition: inline before Location:, with filename*= first, and over HTTPS; with -i the file gets the response headers written into it.
AI assistance was used for testing and code review; the writing and findings are mine.
Affected version
curl 8.23.0 (also 8.22.0 and master 70f3f855c6), macOS, OpenSSL 3.6.5 build. Only the curl tool.
Steps To Reproduce:
- Run any HTTP server on 127.0.0.1:32700 that answers:
HTTP/1.1 200 OK
Content-Disposition: inline
Content-Disposition: attachment; filename="victim"
Content-Length: 8
PAYLOAD
- Run:
printf 'ORIGINAL-CONTENT\n' > victim
curl -sS -OJ http://127.0.0.1:32700/victim; echo "exit=$?"; wc -c victim
- Result:
curl: (23) client returned ERROR on write of 52 bytes
exit=23
0 victim
Control: with only Content-Disposition: attachment; filename="victim" curl exits 23 and victim stays 17 bytes.
Proposed fix:
--- a/src/tool_cb_hdr.c
+++ b/src/tool_cb_hdr.c
@@ -405,8 +405,12 @@ static size_t content_disposition(const char *str, const char *end,
}
break;
}
- if(!outs->stream && !tool_create_output_file(outs, per->config))
- return CURL_WRITEFUNC_ERROR;
+ if(!outs->stream) {
+ if(hdrcbdata->config->show_headers)
+ return buffer_header(hdrcbdata, str, cb);
+ return cb;
+ }
if(tool_write_headers(&per->hdrcbdata, outs->stream))
return CURL_WRITEFUNC_ERROR;
buffer_header() needs a forward declaration above content_disposition(). With this patch every case above leaves the file intact, and a Content-Disposition with no filename at all still falls back to the URL name.
Impact
Summary:
A user running curl -OJ https://host/report.pdf (or with -L) in a directory that already holds report.pdf gets that file truncated to 0 bytes by a malicious or compromised server, while curl reports that it refused to overwrite it.