HexBrain

curl · Report #4123654

SCP upload with --crlf sends more bytes than the size declared to the server (same class curl refused for IMAP CVE-2026-87864 and RTSP)

Informativestatus
lowseverity
5upvotes
19 Oct 2026disclosed

How it was found

Summary

curl recently started REFUSING uploads that combine a size-declaring protocol with CRLF conversion, because the LF->CRLF expansion makes the number of bytes actually sent differ from the size declared to the server:

  • IMAP APPEND — CVE-2026-87864, lib/imap.c:885-886: if(data->set.crlf) failf(data, "Cannot APPEND with CRLF conversion making size unknown");
  • RTSP — commit e84f95a, lib/rtsp.c:235-236: if(data->set.crlf) { failf(data, "Cannot do CRLF conversions on upload"); ... }

SCP uploads do exactly the same thing and were not covered. The SCP code declares data->state.infilesize to the server up front, then sends the body through the client reader stack. When CURLOPT_CRLF / --crlf is set, the cr_lc reader turns every LF into CRLF, so the body grows past the declared size. The SCP path has no "refuse CRLF conversion" guard like IMAP/RTSP; it only rejects a negative size.

Exact source lines:

  • Size declared to the peer (raw infilesize, not adjusted for CRLF growth): lib/vssh/libssh2.c:2414-2416 sshc->ssh_channel = libssh2_scp_send64(sshc->ssh_session, sshp->path, (int)data->set.new_file_perms, (libssh2_int64_t)data->state.infilesize, 0, 0); and lib/vssh/libssh2.c:2438 data->req.size = data->state.infilesize;

  • The CRLF-expanding reader is inserted when data->set.crlf is set: lib/sendf.c:1131-1149 (do_init_reader_stack adds cr_lc) and the lazy init in Curl_client_read (lib/sendf.c:~1220). --crlf maps to CURLOPT_CRLF at src/config2setopts.c:1024.

  • SCP has NO crlf guard: git grep -c crlf lib/vssh/libssh2.c returns 0. It only checks infilesize < 0 (lib/vssh/libssh2.c:2771). Size-declaring protocols that handle this correctly report an unknown length (Curl_creader_total_length() returns -1) when crlf is on; SCP instead declares the un-expanded size.

Affected version

master @ 8e3722b7 (8.23.1-DEV), libssh2 backend. (The libssh backend lib/vssh/libssh.c:2155 declares the size the same way and should be checked too.)

Steps To Reproduce

  1. Make a local file with LF line endings whose content, after some bytes, contains an SCP control sequence, e.g. a NUL byte then: C0644 <n> evil\n<data>
  2. Upload it to an SCP server with CRLF conversion on: curl --crlf -T ./payload scp://user@host/dir/goodfile
  3. curl declares size = original byte count but sends the longer CRLF-expanded stream. The bytes past the declared size are read by the remote scp -t sink as the next SCP protocol message.

Control: without --crlf the byte count matches the declared size (no issue). The IMAP/RTSP equivalents are now rejected outright, which is curl's own confirmation that this combination is unsafe.

The attached verify.sh proves the asymmetry against a fresh clone (IMAP and RTSP refuse crlf uploads; SCP has no such check).

Honest caveat for triage

Confirming that the overflow bytes actually reach the remote as a new SCP control message (vs libssh2 truncating the write at the declared size) needs a local sshd capture — happy to provide on request. The curl-side defect — feeding a stream whose length differs from the size declared to the peer, with no refusal that the sibling protocols now enforce — is the point of this report.

Suggested fix

In the SCP upload path refuse the transfer (or recompute the declared size) when data->set.crlf is set, mirroring the IMAP (imap.c:885) and RTSP (rtsp.c:235) fixes.

Impact

Summary

When an application uploads attacker-influenced content over SCP with CRLF conversion enabled, the mismatch between the size curl declares to the server and the longer CRLF-expanded stream it actually sends lets the trailing bytes be interpreted by the remote SCP sink as a subsequent SCP control message — for example creating an additional file with an attacker-chosen name, mode, and content under the upload directory.

This is the SCP sibling of the IMAP (CVE-2026-87864) and RTSP (commit e84f95a) upload issues that curl already fixed by refusing CRLF conversion on size-declaring uploads; the SCP path was missed.

Low severity: it requires the libssh2 backend, --crlf / CURLOPT_CRLF to be enabled, and upload content that an attacker can influence.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original