Fix race condition causing SSH_MSG_UNIMPLEMENTED occasionally during key exchange (#851)

* Fix race condition causing SSH_MSG_UNIMPLEMENTED occasionally during key exchange

* unit tests

* fix unit tests

---------

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
Raul Santelices
2023-09-01 18:54:22 -04:00
committed by GitHub
parent a5fdb29fad
commit a186dbf0bc
5 changed files with 134 additions and 25 deletions

View File

@@ -810,12 +810,7 @@ public class SSHClient
ThreadNameProvider.setThreadName(conn.getKeepAlive(), trans);
keepAliveThread.start();
}
if (trans.isKeyExchangeRequired()) {
log.debug("Initiating Key Exchange for new connection");
doKex();
} else {
log.debug("Key Exchange already completed for new connection");
}
doKex();
}
/**

View File

@@ -136,13 +136,25 @@ final class KeyExchanger
void startKex(boolean waitForDone)
throws TransportException {
if (!kexOngoing.getAndSet(true)) {
done.clear();
sendKexInit();
if (isKeyExchangeAllowed()) {
log.debug("Initiating key exchange");
done.clear();
sendKexInit();
} else {
kexOngoing.set(false);
}
}
if (waitForDone)
waitForDone();
}
/**
* Key exchange can be initiated exactly once while connecting or later after authentication when re-keying.
*/
private boolean isKeyExchangeAllowed() {
return !isKexDone() || transport.isAuthenticated();
}
void waitForDone()
throws TransportException {
done.await(transport.getTimeoutMs(), TimeUnit.MILLISECONDS);

View File

@@ -71,13 +71,6 @@ public interface Transport
void doKex()
throws TransportException;
/**
* Is Key Exchange required based on current transport status
*
* @return Key Exchange required status
*/
boolean isKeyExchangeRequired();
/** @return the version string used by this client to identify itself to an SSH server, e.g. "SSHJ_3_0" */
String getClientVersion();

View File

@@ -254,16 +254,6 @@ public final class TransportImpl
kexer.startKex(true);
}
/**
* Is Key Exchange required returns true when Key Exchange is not done and when Key Exchange is not ongoing
*
* @return Key Exchange required status
*/
@Override
public boolean isKeyExchangeRequired() {
return !kexer.isKexDone() && !kexer.isKexOngoing();
}
public boolean isKexDone() {
return kexer.isKexDone();
}