Fail writes after SSH session is closed
If there is an exception during KEX that causes the SSH session to be
closed, fail any further write attempts by throwing an SshException.
Otherwise KeyExchangeMessageHandler.writeOrEnqueue() might loop forever
since the KexState will never be DONE.
@@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
import org.apache.sshd.common.future.DefaultKeyExchangeFuture;
import org.apache.sshd.common.io.IoWriteFuture;
import org.apache.sshd.common.kex.KexState;
@@ -104,6 +105,11 @@ public class KeyExchangeMessageHandler {
*/
protected volatile boolean kexFlushed = true;
+ /**
+ * Indicates that the handler has been shut down.
+ */
+ protected volatile boolean shutDown;
+
/**
* Never {@code null}. Used to block some threads when writing packets while pending packets are still being flushed
* at the end of a KEX to avoid overrunning the flushing thread. Always set, initially fulfilled. At the beginning
@@ -194,6 +200,7 @@ public class KeyExchangeMessageHandler {
public void shutdown() {
SimpleImmutableEntry<Integer, DefaultKeyExchangeFuture> items = updateState(() -> {
kexFlushed = true;
+ shutDown = true;
return new SimpleImmutableEntry<Integer, DefaultKeyExchangeFuture>(
Integer.valueOf(pendingPackets.size()),
kexFlushedFuture);
@@ -229,16 +236,18 @@ public class KeyExchangeMessageHandler {
boolean enqueued = false;
boolean isLowLevelMessage = cmd <= SshConstants.SSH_MSG_KEX_LAST && cmd != SshConstants.SSH_MSG_SERVICE_REQUEST
&& cmd != SshConstants.SSH_MSG_SERVICE_ACCEPT;
+ IoWriteFuture future = null;
try {
if (isLowLevelMessage) {
// Low-level messages can always be sent.
- return session.doWritePacket(buffer);
- }
- IoWriteFuture future = writeOrEnqueue(cmd, buffer, timeout, unit);
+ future = session.doWritePacket(buffer);
+ } else {
+ future = writeOrEnqueue(cmd, buffer, timeout, unit);
enqueued = future instanceof PendingWriteFuture;
- return future;
+ }
} finally {
session.resetIdleTimeout();
+ }
if (!enqueued) {
try {
session.checkRekey();
@@ -247,15 +256,13 @@ public class KeyExchangeMessageHandler {
log.debug("writePacket({}) failed ({}) to check re-key: {}", session, e.getClass().getSimpleName(),
e.getMessage(), e);
}
- throw ValidateUtils.initializeExceptionCause(
- new ProtocolException("Failed (" + e.getClass().getSimpleName() + ")"
- + " to check re-key necessity: " + e.getMessage()),
- e);
+ throw ValidateUtils.initializeExceptionCause(new ProtocolException(
+ "Failed (" + e.getClass().getSimpleName() + ")" + " to check re-key necessity: " + e.getMessage()), e);
} catch (Exception e) {
ExceptionUtils.rethrowAsIoException(e);
}
}
- }
+ return future;
}
/**
@@ -288,6 +295,9 @@ public class KeyExchangeMessageHandler {
// Use the readLock here to give KEX state updates and the flushing thread priority.
lock.readLock().lock();
try {
+ if (shutDown) {
+ throw new SshException("Write attempt on closing session: " + SshConstants.getCommandMessageName(cmd));
+ }
KexState state = session.kexState.get();
boolean kexDone = KexState.DONE.equals(state) || KexState.KEYS.equals(state);
if (kexDone && kexFlushed) {
