CVE-2020-8177: curl -J -i interaction enables local-file overwrite via early fopen("wb")
809092cb-3777-4ee6-8dca-edc61b24f07d
curl 7.20.0–7.70.0 is vulnerable to a local file overwrite when -J/--remote-header-name is combined with -i/--include (or any flag that sets show_headers). The -J flag is supposed to guarantee that curl will never overwrite an existing local file, by deferring filename selection to the server's Content-Disposition header and refusing to clobber. That guarantee is broken: when response headers arrive, tool_header_cb (src/tool_cb_hdr.c) enters the show_headers branch and unconditionally calls tool_create_output_file() if outs->stream is still NULL. At that moment outs->is_cd_filename is still FALSE (Content-Disposition has not been parsed yet), so the duplicate-file protection inside tool_create_output_file (src/tool_cb_wrt.c:49-58) is skipped, and fopen(outs->filename, "wb") truncates whatever file is at the URL-derived name per->outfile. Because fopen follows symlinks, this can also be amplified to overwrite files outside cwd if the user has a symlink in cwd.
grep -n "content_disposition" src/ led to src/tool_cb_hdr.c and src/tool_operate.c.
3. Read src/tool_cb_hdr.c::tool_header_cb. The Content-Disposition path (lines 158-207) properly sets outs->is_cd_filename = TRUE before calling tool_create_output_file, so it engages the no-overwrite check.
4. The show_headers branch at lines 209-228 (gated only on -i) calls tool_create_output_file at line 215 WITHOUT setting is_cd_filename. Headers always arrive before/around Content-Disposition, and may include responses that lack Content-Disposition entirely.
5. Read src/tool_cb_wrt.c::tool_create_output_file. The "Refusing to overwrite" check at lines 49-58 only runs when outs->is_cd_filename is TRUE; otherwise line 61 does fopen(outs->filename, "wb") and truncates.
6. Read src/tool_operate.c around line 1063-1104. With -O -J, outs->filename is set to the URL-derived per->outfile BEFORE the transfer starts, so by the time the show_headers branch fires there is already a concrete filename to clobber. The DEBUGASSERT(!outs->filename) at 1066 only fires in debug builds.
7. Cross-checked tool_write_cb (src/tool_cb_wrt.c:153) — same defect: it calls tool_create_output_file with is_cd_filename=FALSE if the body arrives before Content-Disposition (e.g. server omits the header).
(a) Gate the early tool_create_output_file calls on !hdrcbdata->honor_cd_filename. In other words, while -J is still waiting for Content-Disposition, do not create the output file from the header or body callback. Upstream curl 7.71.1 took this approach.
(b) Always set outs->is_cd_filename = TRUE before calling tool_create_output_file whenever -J is in effect, so the existing fopen("rb") existence check refuses to clobber.
Either fix makes the show_headers branch consistent with the Content-Disposition branch's expectations. Mirror the gate in tool_write_cb so that a body-only response cannot trigger an early open either.
tool_create_output_file with is_cd_filename=FALSE while honor_cd_filename=TRUE. With a pre-existing file named after the URL's last path component, curl -O -J -i http://server/<existing-name> truncates it during the very first header callback, before Content-Disposition is even available.