transport_send: Finish in-progress key exchange before sending data
Backport of upstream commit cc4f9d5679278ce41cd5480fab3f5e71dba163ed
_libssh2_channel_write() first reads outstanding packets before writing new
data. If it reads a key exchange request, it will immediately start key
re-exchange, which will require sending a response. If the output socket is
full, this will result in a return from _libssh2_transport_read() of
LIBSSH2_ERROR_EAGAIN. In order not to block a write because there is no data to
read, this error is explicitly ignored and the code continues marshalling a
packet for sending. When it is sent, the remote end immediately drops the
connection because it was expecting a continuation of the key exchange, but got
a data packet.
This change adds the same check for key exchange to _libssh2_transport_write()
that is in _libssh2_transport_read(). This ensures that key exchange is
completed before any data packet is sent.
diff -up libssh2-1.2.7/src/transport.c.bz804155 libssh2-1.2.7/src/transport.c
--- libssh2-1.2.7/src/transport.c.bz804155
+++ libssh2-1.2.7/src/transport.c
@@ -312,7 +312,7 @@ int _libssh2_transport_read(LIBSSH2_SESS
* is done!
*/
_libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the"
- " key re-exchange");
+ " key re-exchange from _libssh2_transport_read");
rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state);
if (rc)
return rc;
@@ -718,6 +718,24 @@ _libssh2_transport_write(LIBSSH2_SESSION
unsigned char *orgdata = data;
size_t orgdata_len = data_len;
+ /*
+ * If the last read operation was interrupted in the middle of a key
+ * exchange, we must complete that key exchange before continuing to write
+ * further data.
+ *
+ * See the similar block in _libssh2_transport_read for more details.
+ */
+ if (session->state & LIBSSH2_STATE_EXCHANGING_KEYS &&
+ !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) {
+ /* Don't write any new packets if we're still in the middle of a key
+ * exchange. */
+ _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the"
+ " key re-exchange from _libssh2_transport_write");
+ rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state);
+ if (rc)
+ return rc;
+ }
+
debugdump(session, "libssh2_transport_write plain", data, data_len);
/* FIRST, check if we have a pending write to complete */