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)
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-2416sshc->ssh_channel = libssh2_scp_send64(sshc->ssh_session, sshp->path,(int)data->set.new_file_perms,(libssh2_int64_t)data->state.infilesize, 0, 0);andlib/vssh/libssh2.c:2438data->req.size = data->state.infilesize; -
The CRLF-expanding reader is inserted when
data->set.crlfis set:lib/sendf.c:1131-1149(do_init_reader_stackaddscr_lc) and the lazy init inCurl_client_read(lib/sendf.c:~1220).--crlfmaps toCURLOPT_CRLFatsrc/config2setopts.c:1024. -
SCP has NO crlf guard:
git grep -c crlf lib/vssh/libssh2.creturns 0. It only checksinfilesize < 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
- 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> - Upload it to an SCP server with CRLF conversion on:
curl --crlf -T ./payload scp://user@host/dir/goodfile - 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 -tsink 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.