Skip to content

Commit f34fef6

Browse files
wolfssh/scp: complete rekey that starts mid-transfer
1 parent e2bfa19 commit f34fef6

2 files changed

Lines changed: 444 additions & 10 deletions

File tree

src/wolfscp.c

Lines changed: 123 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,94 @@ static int _DumpExtendedData(WOLFSSH* ssh)
7979
}
8080

8181

82+
/* Sends sz bytes from data, completing any rekey that blocks the send.
83+
*
84+
* Drives wolfSSH_worker while the connection is keying or the channel window is
85+
* full, including a rekey triggered by a receive-side highwater check where
86+
* ssh->error holds a status other than WS_REKEYING (caught via ssh->isKeying).
87+
* In blocking mode the worker's DoReceive waits on the socket so progress
88+
* depends on the peer finishing the rekey or adjusting the window; in
89+
* non-blocking mode a WS_WANT_READ/WS_WANT_WRITE status is returned so a
90+
* stalled rekey cannot spin forever. Returns the byte count from
91+
* wolfSSH_stream_send (>= 0) or a negative error code, matching the wrapped
92+
* call so callers keep their existing return handling.
93+
*/
94+
static int ScpStreamSend(WOLFSSH* ssh, byte* data, word32 sz)
95+
{
96+
int ret = WS_SUCCESS;
97+
int err;
98+
99+
if (ssh == NULL || data == NULL)
100+
return WS_BAD_ARGUMENT;
101+
102+
/* Flush queued output before the drain loop. Otherwise a KEXINIT enqueued
103+
* by a rekey triggered on the prior send sits unsent while wolfSSH_worker
104+
* runs DoReceive before its own flush, blocking on the socket with the peer
105+
* waiting for our KEXINIT. */
106+
if (wolfSSH_OutputPending(ssh)) {
107+
ret = wolfSSH_SendPacket(ssh);
108+
if (ret < 0)
109+
return ret;
110+
}
111+
112+
err = wolfSSH_get_error(ssh);
113+
while (ssh->isKeying || err == WS_WINDOW_FULL || err == WS_REKEYING) {
114+
ret = wolfSSH_worker(ssh, NULL);
115+
if (ret < 0 && ret != WS_REKEYING && ret != WS_WINDOW_FULL
116+
&& ret != WS_CHAN_RXD)
117+
return ret;
118+
err = wolfSSH_get_error(ssh);
119+
}
120+
121+
return wolfSSH_stream_send(ssh, data, sz);
122+
}
123+
124+
125+
/* Reads up to sz bytes into data, completing any rekey that fires mid-read.
126+
*
127+
* Flushes queued output before reading so a KEXINIT enqueued by a receive-side
128+
* highwater rekey is actually sent, otherwise the peer can wait for our KEXINIT
129+
* while we block on the read. On a read that fails with WS_REKEYING the worker
130+
* is driven to finish the rekey and the read is retried. The helper is
131+
* error-code transparent: every other status (WS_EOF, WS_EXTDATA,
132+
* WS_CHANNEL_CLOSED, WS_SOCKET_ERROR_E, WS_WANT_READ/WS_WANT_WRITE, byte count)
133+
* is returned unchanged so each caller keeps its existing branch handling.
134+
*/
135+
static int ScpStreamRead(WOLFSSH* ssh, byte* data, word32 sz)
136+
{
137+
int ret = WS_SUCCESS;
138+
int done = 0;
139+
140+
if (ssh == NULL || data == NULL)
141+
return WS_BAD_ARGUMENT;
142+
143+
do {
144+
if (wolfSSH_OutputPending(ssh)) {
145+
ret = wolfSSH_SendPacket(ssh);
146+
if (ret < 0)
147+
return ret;
148+
}
149+
150+
ret = wolfSSH_stream_read(ssh, data, sz);
151+
if (ret < 0 && wolfSSH_get_error(ssh) == WS_REKEYING) {
152+
/* Drive the rekey to completion, then retry the read. A worker
153+
* status that is not rekey or channel data means the rekey stalled
154+
* or a non-blocking want occurred, so return it rather than
155+
* spin. */
156+
ret = wolfSSH_worker(ssh, NULL);
157+
if (ret < 0 && ret != WS_CHAN_RXD
158+
&& wolfSSH_get_error(ssh) != WS_REKEYING)
159+
return ret;
160+
}
161+
else {
162+
done = 1;
163+
}
164+
} while (!done);
165+
166+
return ret;
167+
}
168+
169+
82170
int DoScpSink(WOLFSSH* ssh)
83171
{
84172
int ret = WS_SUCCESS;
@@ -283,7 +371,7 @@ static int SendScpTimestamp(WOLFSSH* ssh)
283371
#endif
284372
bufSz = (int)WSTRLEN(buf);
285373

286-
ret = wolfSSH_stream_send(ssh, (byte*)buf, bufSz);
374+
ret = ScpStreamSend(ssh, (byte*)buf, bufSz);
287375
if (ret != bufSz) {
288376
ret = WS_FATAL_ERROR;
289377
} else {
@@ -324,7 +412,7 @@ static int SendScpFileHeader(WOLFSSH* ssh)
324412
return WS_BAD_ARGUMENT;
325413
#endif
326414
bufSz = (int)WSTRLEN(filehdr);
327-
ret = wolfSSH_stream_send(ssh, (byte*)filehdr, bufSz);
415+
ret = ScpStreamSend(ssh, (byte*)filehdr, bufSz);
328416
if (ret != bufSz) {
329417
ret = WS_FATAL_ERROR;
330418
} else {
@@ -357,7 +445,7 @@ static int SendScpEnterDirectory(WOLFSSH* ssh)
357445

358446
bufSz = (int)WSTRLEN(buf);
359447

360-
ret = wolfSSH_stream_send(ssh, (byte*)buf, bufSz);
448+
ret = ScpStreamSend(ssh, (byte*)buf, bufSz);
361449
if (ret != bufSz) {
362450
ret = WS_FATAL_ERROR;
363451
} else {
@@ -383,7 +471,7 @@ static int SendScpExitDirectory(WOLFSSH* ssh)
383471
buf[0] = 'E';
384472
buf[1] = '\n';
385473

386-
ret = wolfSSH_stream_send(ssh, (byte*)buf, sizeof(buf));
474+
ret = ScpStreamSend(ssh, (byte*)buf, sizeof(buf));
387475
if (ret != sizeof(buf)) {
388476
ret = WS_FATAL_ERROR;
389477
} else {
@@ -598,8 +686,8 @@ int DoScpSource(WOLFSSH* ssh)
598686
case SCP_SEND_FILE:
599687
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_FILE");
600688

601-
ret = wolfSSH_stream_send(ssh, ssh->scpFileBuffer,
602-
ssh->scpBufferedSz);
689+
ret = ScpStreamSend(ssh, ssh->scpFileBuffer,
690+
ssh->scpBufferedSz);
603691
if (ret == WS_WINDOW_FULL || ret == WS_REKEYING) {
604692
ret = wolfSSH_worker(ssh, NULL);
605693
if (ret == WS_SUCCESS || ssh->error == WS_WANT_READ)
@@ -736,7 +824,7 @@ int DoScpRequest(WOLFSSH* ssh)
736824

737825
/* Peer MUST send back a SSH_MSG_CHANNEL_CLOSE unless already
738826
sent*/
739-
ret = wolfSSH_stream_read(ssh, buf, 1);
827+
ret = ScpStreamRead(ssh, buf, 1);
740828
if (ret == WS_SOCKET_ERROR_E || ret == WS_CHANNEL_CLOSED) {
741829
WLOG(WS_LOG_DEBUG, scpState, "Peer hung up, but SCP is done");
742830
ret = WS_SUCCESS;
@@ -1395,6 +1483,31 @@ int ReceiveScpMessage(WOLFSSH* ssh)
13951483
return WS_BUFFER_E;
13961484
}
13971485

1486+
/* Flush queued output before polling. A KEXINIT enqueued by a rekey
1487+
* would otherwise sit unsent while wolfSSH_worker runs DoReceive before
1488+
* its own flush, deadlocking against a peer that waits for our
1489+
* KEXINIT. */
1490+
if (wolfSSH_OutputPending(ssh)) {
1491+
ret = wolfSSH_SendPacket(ssh);
1492+
if (ret < 0)
1493+
return ret;
1494+
}
1495+
1496+
/* If channel data is already buffered, read it directly rather than
1497+
* polling the socket. A control message delivered into the channel
1498+
* buffer while a rekey was completing leaves wolfSSH_worker returning
1499+
* the rekey status (not WS_CHAN_RXD), so without this the buffered
1500+
* message is never read and the next worker blocks on the socket. */
1501+
if (wolfSSH_stream_peek(ssh, NULL, 1) > 0) {
1502+
sz = wolfSSH_stream_read(ssh, buf + ssh->scpRecvMsgSz,
1503+
DEFAULT_SCP_MSG_SZ - ssh->scpRecvMsgSz);
1504+
if (sz < 0)
1505+
return sz;
1506+
ssh->scpRecvMsgSz += sz;
1507+
sz = ssh->scpRecvMsgSz;
1508+
continue;
1509+
}
1510+
13981511
err = wolfSSH_worker(ssh, &lastChannel);
13991512
if (err < 0) {
14001513
int rc;
@@ -1517,7 +1630,7 @@ int ReceiveScpFile(WOLFSSH* ssh)
15171630
}
15181631

15191632
if (ret == WS_SUCCESS) {
1520-
ret = wolfSSH_stream_read(ssh, ssh->scpFileBuffer, partSz);
1633+
ret = ScpStreamRead(ssh, ssh->scpFileBuffer, partSz);
15211634
if (ret > 0) {
15221635
ssh->scpFileBufferSz = ret;
15231636
}
@@ -1559,7 +1672,7 @@ int SendScpConfirmation(WOLFSSH* ssh)
15591672

15601673
/* skip first byte for accurate strlen, may be 0 */
15611674
msgSz = (int)XSTRLEN(msg + 1) + 1;
1562-
ret = wolfSSH_stream_send(ssh, (byte*)msg, msgSz);
1675+
ret = ScpStreamSend(ssh, (byte*)msg, msgSz);
15631676
if (ret != msgSz || ssh->scpConfirm == WS_SCP_ABORT) {
15641677
ret = WS_FATAL_ERROR;
15651678

@@ -1587,7 +1700,7 @@ int ReceiveScpConfirmation(WOLFSSH* ssh)
15871700
return WS_BAD_ARGUMENT;
15881701

15891702
WMEMSET(msg, 0, sizeof(msg));
1590-
msgSz = wolfSSH_stream_read(ssh, msg, DEFAULT_SCP_MSG_SZ);
1703+
msgSz = ScpStreamRead(ssh, msg, DEFAULT_SCP_MSG_SZ);
15911704

15921705
if (msgSz < 0) {
15931706
if (wolfSSH_get_error(ssh) == WS_EXTDATA)

0 commit comments

Comments
 (0)