This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch dev_3.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit b54206767ae7ee4bb8962b701180b13b413ac0e9 Author: Thomas Wolf <tw...@apache.org> AuthorDate: Wed Apr 2 22:55:17 2025 +0200 Improve packet padding Don't add random extra padding to low-level messages. OTOH ensure that the padding for messages that might carry user passwords is always at least 64 bytes to prevent that anyone could make guesses about user password lengths (if an attacker can identify these messages at all). Change the outgoing interfaces to pass through the SSH command byte together with the buffer. Otherwise the CryptFilter, which decides on the amount of padding, may not have access to it if compression is used. (The command byte would also be compressed and thus cannot be read anymore from the buffer directly.) --- .../sshd/common/filter/DefaultFilterChain.java | 4 +- .../org/apache/sshd/common/filter/FilterChain.java | 3 +- .../apache/sshd/common/filter/FilterContext.java | 5 ++- .../apache/sshd/common/filter/OutputHandler.java | 3 +- .../common/session/filters/CompressionFilter.java | 5 +-- .../sshd/common/session/filters/CryptFilter.java | 47 +++++++++++++--------- .../common/session/filters/DelayKexInitFilter.java | 16 +++----- .../sshd/common/session/filters/IdentFilter.java | 10 ++--- .../common/session/filters/InjectIgnoreFilter.java | 7 ++-- .../sshd/common/session/filters/kex/KexFilter.java | 21 +++++----- .../session/filters/kex/KexOutputHandler.java | 12 +++--- .../common/session/helpers/AbstractSession.java | 5 ++- .../session/filters/CompressionFilterTest.java | 2 +- .../common/session/filters/CryptFilterTest.java | 2 +- .../common/session/filters/FilterTestSupport.java | 2 +- .../common/session/filters/IdentFilterTest.java | 14 +++---- .../session/filters/InjectIgnoreFilterTest.java | 2 +- 17 files changed, 86 insertions(+), 74 deletions(-) diff --git a/sshd-core/src/main/java/org/apache/sshd/common/filter/DefaultFilterChain.java b/sshd-core/src/main/java/org/apache/sshd/common/filter/DefaultFilterChain.java index 1a18587fc..9075c13ac 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/filter/DefaultFilterChain.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/filter/DefaultFilterChain.java @@ -122,12 +122,12 @@ public class DefaultFilterChain implements FilterChain { } @Override - public IoWriteFuture send(FilterContext current, Buffer message) throws IOException { + public IoWriteFuture send(FilterContext current, int cmd, Buffer message) throws IOException { FilterContext ctx = current.prev; while (ctx != null) { OutputHandler handler = ctx.filter.out(); if (handler != null) { - return handler.send(message); + return handler.send(cmd, message); } ctx = ctx.prev; } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterChain.java b/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterChain.java index 8adf24b70..f32bd4e7e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterChain.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterChain.java @@ -57,11 +57,12 @@ public interface FilterChain { * Pass on an outgoing message to the next filter before {@code current} that has an {@link OutputHandler}. * * @param current {@link Filter} that is passing on the message + * @param cmd the SSH command code of the buffer being written; must also be included in the buffer * @param message being passed on * @return an {@link IoWriteFuture} that is fulfilled when the message has been sent. * @throws IOException if an error occurs */ - IoWriteFuture send(FilterContext current, Buffer message) throws IOException; + IoWriteFuture send(FilterContext current, int cmd, Buffer message) throws IOException; /** * Pass on an incoming message to the next filter after {@code current} that has an {@link InputHandler}. diff --git a/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterContext.java b/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterContext.java index 7f30ae0b9..f99e88af7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterContext.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/filter/FilterContext.java @@ -52,12 +52,13 @@ public final class FilterContext { /** * Pass on an outgoing message to the next filter before this one that has an {@link OutputHandler}. * + * @param cmd the SSH command code of the buffer being written; must also be included in the buffer * @param message being passed on * @return an {@link IoWriteFuture} that is fulfilled when the message has been sent. * @throws IOException if an error occurs */ - public IoWriteFuture send(Buffer message) throws IOException { - return chain.send(this, message); + public IoWriteFuture send(int cmd, Buffer message) throws IOException { + return chain.send(this, cmd, message); } /** diff --git a/sshd-core/src/main/java/org/apache/sshd/common/filter/OutputHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/filter/OutputHandler.java index 05786d47d..9be086a8d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/filter/OutputHandler.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/filter/OutputHandler.java @@ -31,11 +31,12 @@ public interface OutputHandler { /** * Sends an outgoing message. * + * @param cmd the SSH command code of the buffer being written; must also be included in the buffer * @param message {@link Buffer} containing the message; not to be re-used before the returned future is * fulfilled * @return an {@link IoWriteFuture} that will be fulfilled once the message has been sent. * @throws Exception if an error occurs in handling the message */ - IoWriteFuture send(Buffer message) throws IOException; + IoWriteFuture send(int cmd, Buffer message) throws IOException; } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CompressionFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CompressionFilter.java index 35efb3856..b9a1a740a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CompressionFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CompressionFilter.java @@ -146,11 +146,10 @@ public class CompressionFilter extends IoFilter { } @Override - public synchronized IoWriteFuture send(Buffer message) throws IOException { + public synchronized IoWriteFuture send(int cmd, Buffer message) throws IOException { if (message != null) { Compression comp = outbound.get(); if (comp != null && comp.isCompressionExecuted() && (delayedEnable.get() || !comp.isDelayed())) { - int cmd = message.rawByte(message.rpos()) & 0xFF; int oldLength = message.available(); comp.compress(message); if (LOG.isDebugEnabled()) { @@ -159,7 +158,7 @@ public class CompressionFilter extends IoFilter { } } } - return owner().send(message); + return owner().send(cmd, message); } } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java index af9dbfc55..3dbc1c305 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/CryptFilter.java @@ -21,6 +21,7 @@ package org.apache.sshd.common.session.filters; import java.io.IOException; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -138,14 +139,14 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { public void setInput(Settings settings, boolean resetSequence) { decryption.set(Objects.requireNonNull(settings)); if (resetSequence) { - input.sequenceNumber = 0; + input.sequenceNumber.set(0); } } public void setOutput(Settings settings, boolean resetSequence) { encryption.set(Objects.requireNonNull(settings)); if (resetSequence) { - output.sequenceNumber = 0; + output.sequenceNumber.set(0); } } @@ -159,12 +160,12 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { @Override public int getInputSequenceNumber() { - return input.sequenceNumber; + return input.sequenceNumber.get(); } @Override public int getOutputSequenceNumber() { - return output.sequenceNumber; + return output.sequenceNumber.get(); } @Override @@ -190,7 +191,7 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { private abstract class WithSequenceNumber { - volatile int sequenceNumber; + final AtomicInteger sequenceNumber = new AtomicInteger(); WithSequenceNumber() { super(); @@ -318,7 +319,7 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { } inCounts.get().update(bytes / cipherSize, bytes); - sequenceNumber++; + sequenceNumber.incrementAndGet(); int endOfDataReceived = buffer.wpos(); int afterPacket = packetLength + Integer.BYTES + settings.getTagSize(); @@ -349,7 +350,7 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { private void checkMac(byte[] data, int offset, int length, Mac mac) throws Exception { if (mac != null) { - mac.updateUInt(sequenceNumber & 0xFFFF_FFFFL); + mac.updateUInt(sequenceNumber.get()); mac.update(data, offset, length); byte[] x = mac.doFinal(); if (!Mac.equals(x, 0, data, offset + length, x.length)) { @@ -366,22 +367,22 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { } @Override - public synchronized IoWriteFuture send(Buffer message) throws IOException { + public synchronized IoWriteFuture send(int cmd, Buffer message) throws IOException { Buffer encrypted = message; if (encrypted != null) { try { - listeners.forEach(listener -> listener.aboutToEncrypt(message, sequenceNumber)); - encrypted = encode(message); + listeners.forEach(listener -> listener.aboutToEncrypt(message, sequenceNumber.get())); + encrypted = encode(cmd, message); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e.getMessage(), e); } } - return owner().send(encrypted); + return owner().send(cmd, encrypted); } - private Buffer encode(Buffer packet) throws Exception { + private Buffer encode(int cmd, Buffer packet) throws Exception { Settings settings = encryption.get(); Cipher cipher = settings.getCipher(); boolean isAead = cipher != null && settings.isAead(); @@ -394,7 +395,7 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { if (start < 0) { throw new IllegalArgumentException("Message is not an SSH packet buffer; need 5 spare bytes at the front"); } - int pad = paddingLength(length, cipherSize, !isAead && !isEtm); + int pad = paddingLength(cmd, length, cipherSize, !isAead && !isEtm); // RFC 4253: at least 4 bytes padding, at most 255 bytes if (pad < 4 || pad > MAX_PADDING) { throw new IllegalStateException("Invalid packet length computed: " + pad + " not in range [4..255]"); @@ -426,20 +427,30 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { } } outCounts.get().update(bytes / cipherSize, bytes); - sequenceNumber++; + sequenceNumber.incrementAndGet(); packet.rpos(start); return packet; } - private int paddingLength(int payloadLength, int blockSize, boolean includePacketLength) { + private int paddingLength(int cmd, int payloadLength, int blockSize, boolean includePacketLength) { int toEncrypt = payloadLength + 1; // For the padding count itself. if (includePacketLength) { toEncrypt += Integer.BYTES; } // RFC 4253: at least 4, at most 255 bytes. - // RFC 4253: variable amounts of random padding may help thwart traffic analysis. - int pad = 4 + random.random(MAX_PADDING + 1 - 4); + int minPadding = 4; + // Minor layering break here: always pad messages that might carry user passwords with at least 64 bytes + // to prevent that traffic analysis might make guesses about password lengths. + if (cmd >= SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST && cmd <= SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC) { + minPadding = 64; // Must be smaller than MAX_PADDING, of course + } + int pad = minPadding; + // For low-level messages, do not add extra padding. + if (cmd >= SshConstants.SSH_MSG_KEXINIT) { + // RFC 4253: variable amounts of random padding may help thwart traffic analysis. + pad = minPadding + random.random(MAX_PADDING + 1 - minPadding); + } // Now pad is in the range [4..MAX_PADDING] int totalLength = toEncrypt + pad; // Adjust pad such that totalLength is a multiple of the blockSize, and is still larger than 4. @@ -452,7 +463,7 @@ public class CryptFilter extends IoFilter implements CryptStatisticsProvider { private void appendMac(byte[] data, int start, int end, Mac mac) throws Exception { if (mac != null) { - mac.updateUInt(sequenceNumber); + mac.updateUInt(sequenceNumber.get()); mac.update(data, start, end - start); mac.doFinal(data, end); } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/DelayKexInitFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/DelayKexInitFilter.java index ffe254f80..ae3e4c69c 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/DelayKexInitFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/DelayKexInitFilter.java @@ -96,21 +96,17 @@ public class DelayKexInitFilter extends IoFilter { } @Override - public IoWriteFuture send(Buffer message) throws IOException { - if (output.get() == null) { - return owner().send(message); - } - int cmd = message.rawByte(message.rpos()); - if (cmd != SshConstants.SSH_MSG_KEXINIT) { - return owner().send(message); + public IoWriteFuture send(int cmd, Buffer message) throws IOException { + if (cmd != SshConstants.SSH_MSG_KEXINIT || output.get() == null) { + return owner().send(cmd, message); } boolean first = isFirst.getAndSet(false); if (!first || session.isServerSession() || CoreModuleProperties.SEND_IMMEDIATE_KEXINIT.getRequired(session).booleanValue()) { - return owner().send(message).addListener(f -> output.set(null)); + return owner().send(cmd, message).addListener(f -> output.set(null)); } // We're a client, and we delay sending the initial KEX-INIT until we have received the peer's KEX-INIT - IoWriteFuture initial = owner().send(null); + IoWriteFuture initial = owner().send(-1, null); DefaultIoWriteFuture result = new DefaultIoWriteFuture(KexOutputHandler.this, null); initial.addListener(init -> { Throwable t = init.getException(); @@ -120,7 +116,7 @@ public class DelayKexInitFilter extends IoFilter { } initReceived.addListener(f -> { try { - owner().send(message).addListener(g -> { + owner().send(cmd, message).addListener(g -> { output.set(null); result.setValue(g.isWritten() ? Boolean.TRUE : g.getException()); }); diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/IdentFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/IdentFilter.java index ecf54b226..05f853156 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/IdentFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/IdentFilter.java @@ -156,20 +156,20 @@ public class IdentFilter extends IoFilter { } @Override - public IoWriteFuture send(Buffer message) throws IOException { + public IoWriteFuture send(int cmd, Buffer message) throws IOException { boolean isFirst = firstMessage.getAndSet(false); if (isFirst) { IoWriteFuture identSent; if (identHandler.isServer() || CoreModuleProperties.SEND_IMMEDIATE_IDENTIFICATION.getRequired(properties).booleanValue()) { - identSent = owner().send(getIdent()); + identSent = owner().send(-1, getIdent()); } else { // We're a client, and we wait for the server's ident to arrive first. DefaultIoWriteFuture delayed = new DefaultIoWriteFuture("DelayedIdent", null); identSent = delayed; received.addListener(identReceived -> { try { - owner().send(getIdent()).addListener(idSent -> { + owner().send(-1, getIdent()).addListener(idSent -> { delayed.setValue(idSent.isWritten() ? Boolean.TRUE : idSent.getException()); }); } catch (IOException e) { @@ -188,7 +188,7 @@ public class IdentFilter extends IoFilter { IoWriteFuture queue = lastWrite.get(); if (queue == null || queue.isDone()) { lastWrite.set(null); - IoWriteFuture result = owner().send(message); + IoWriteFuture result = owner().send(cmd, message); writeHandler.set(null); return result; } @@ -198,7 +198,7 @@ public class IdentFilter extends IoFilter { lastWrite.compareAndSet(result, null); if (f.isWritten()) { try { - owner().send(message).addListener(msgSent -> { + owner().send(cmd, message).addListener(msgSent -> { result.setValue(msgSent.isWritten() ? Boolean.TRUE : msgSent.getException()); }); } catch (IOException e) { diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/InjectIgnoreFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/InjectIgnoreFilter.java index f8bc7110b..2e3e39b4a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/InjectIgnoreFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/InjectIgnoreFilter.java @@ -77,21 +77,20 @@ public class InjectIgnoreFilter extends IoFilter { } @Override - public synchronized IoWriteFuture send(Buffer message) throws IOException { - int cmd = message.rawByte(message.rpos()) & 0xFF; + public synchronized IoWriteFuture send(int cmd, Buffer message) throws IOException { int length = shouldSendIgnore(cmd); if (length > 0) { if (LOG.isDebugEnabled()) { LOG.debug("Injector.send({}) injecting SSH_MSG_IGNORE", resolver); } - owner().send(createIgnoreBuffer(length)).addListener(f -> { + owner().send(SshConstants.SSH_MSG_IGNORE, createIgnoreBuffer(length)).addListener(f -> { Throwable t = f.getException(); if (t != null && (resolver instanceof Session)) { ((Session) resolver).exceptionCaught(t); } }); } - return owner().send(message); + return owner().send(cmd, message); } private Settings getSettings() { diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexFilter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexFilter.java index 30cb53ef1..ac0c65e72 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexFilter.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexFilter.java @@ -511,7 +511,7 @@ public class KexFilter extends IoFilter { LOG.debug("sendKexInit({}) : SSH_MSG_KEXINIT sent by reserved messages handler", session); } } else { - future = forward.send(message); + future = forward.send(SshConstants.SSH_MSG_KEXINIT, message); } initFuture.setValue(Boolean.TRUE); return future; @@ -870,7 +870,7 @@ public class KexFilter extends IoFilter { private IoWriteFuture sendNewKeys() throws Exception { Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_NEWKEYS, 1); - IoWriteFuture future = forward.send(buffer); + IoWriteFuture future = forward.send(SshConstants.SSH_MSG_NEWKEYS, buffer); // Use the new settings from now on for any outgoing packet setOutputEncoding(); output.updateState(() -> kexState.set(KexState.KEYS)); @@ -958,6 +958,7 @@ public class KexFilter extends IoFilter { throw new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "KEX: received SSH_MSG_NEWKEYS in state " + currentState); } + input.sequenceNumberCheckEnabled = false; // It is guaranteed that we handle the peer's SSH_MSG_NEWKEYS after having sent our own. // prepareNewKeys() was already called in sendNewKeys(). // @@ -1076,8 +1077,8 @@ public class KexFilter extends IoFilter { } // Entry points for the KexOutputHandler - IoWriteFuture write(Buffer buffer, boolean checkForKex) throws IOException { - IoWriteFuture result = forward.send(buffer); + IoWriteFuture write(int cmd, Buffer buffer, boolean checkForKex) throws IOException { + IoWriteFuture result = forward.send(cmd, buffer); if (checkForKex) { startKexIfNeeded(); } @@ -1099,6 +1100,8 @@ public class KexFilter extends IoFilter { private abstract class WithSequenceNumber { + volatile boolean sequenceNumberCheckEnabled = true; + private int initialSequenceNumber; private boolean first = true; @@ -1126,7 +1129,9 @@ public class KexFilter extends IoFilter { @Override public void handleMessage(Buffer message) throws Exception { - checkSequence("Incoming", crypt::getInputSequenceNumber); + if (sequenceNumberCheckEnabled) { + checkSequence("Incoming", crypt::getInputSequenceNumber); + } int cmd = message.rawByte(message.rpos()) & 0xFF; if (LOG.isDebugEnabled()) { LOG.debug("KexFilter.handleMessage({}) {} with packet size {}", getSession(), @@ -1222,14 +1227,12 @@ public class KexFilter extends IoFilter { private class Sender extends WithSequenceNumber implements OutputHandler { - volatile boolean sequenceNumberCheckEnabled = true; - Sender() { super(); } @Override - public IoWriteFuture send(Buffer message) throws IOException { + public IoWriteFuture send(int cmd, Buffer message) throws IOException { if (sequenceNumberCheckEnabled) { checkSequence("Outgoing", crypt::getOutputSequenceNumber); } @@ -1237,7 +1240,7 @@ public class KexFilter extends IoFilter { LOG.debug("KexFilter.send({}) {} with packet size {}", getSession(), SshConstants.getCommandMessageName(message.rawByte(message.rpos()) & 0xFF), message.available()); } - return owner().send(message); + return owner().send(cmd, message); } } } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexOutputHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexOutputHandler.java index 928f6cbf7..24a28db34 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexOutputHandler.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/filters/kex/KexOutputHandler.java @@ -211,17 +211,15 @@ public class KexOutputHandler implements OutputHandler { * @throws IOException if an error occurs */ @Override - public IoWriteFuture send(Buffer buffer) throws IOException { + public IoWriteFuture send(int cmd, Buffer buffer) throws IOException { // While exchanging key, queue high level packets. - byte[] bufData = buffer.array(); - int cmd = bufData[buffer.rpos()] & 0xFF; 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. - future = filter.write(buffer, true); + future = filter.write(cmd, buffer, true); } else { future = writeOrEnqueue(cmd, buffer); if (!(future instanceof PendingWriteFuture)) { @@ -267,7 +265,7 @@ public class KexOutputHandler implements OutputHandler { boolean kexDone = KexState.DONE.equals(state) || KexState.KEYS.equals(state); if (kexDone && kexFlushed.get()) { // Not in KEX, no pending packets: out it goes. - return filter.write(buffer, false); + return filter.write(cmd, buffer, false); } else { // Still in KEX or still flushing. Enqueue the packet; it will get written by the flushing thread at // the end of KEX. See the javadoc of KexFilter. @@ -401,7 +399,9 @@ public class KexOutputHandler implements OutputHandler { log.trace("flushQueue({}): Flushing a packet at end of KEX for {}", filter.getSession(), pending.getId()); } - written = filter.write(pending.getBuffer(), true); + Buffer buf = pending.getBuffer(); + int cmd = buf.rawByte(buf.rpos()) & 0xFF; + written = filter.write(cmd, buf, true); } catch (Throwable e) { log.error("flushQueue({}): Exception while flushing packet at end of KEX for {}", filter.getSession(), diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java index 2639812b0..ec6c783ba 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java @@ -230,7 +230,7 @@ public abstract class AbstractSession extends SessionHelper { @Override public OutputHandler out() { - return message -> getIoSession().writeBuffer(message); + return (cmd, message) -> getIoSession().writeBuffer(message); } }; filters.addFirst(ioSessionConnector); @@ -646,7 +646,8 @@ public abstract class AbstractSession extends SessionHelper { @Override public IoWriteFuture writePacket(Buffer buffer) throws IOException { - return filters.getLast().out().send(buffer); + int cmd = buffer.rawByte(buffer.rpos()) & 0xFF; + return filters.getLast().out().send(cmd, buffer); } @Override diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CompressionFilterTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CompressionFilterTest.java index 9fed0bc59..b8bea7871 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CompressionFilterTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CompressionFilterTest.java @@ -113,7 +113,7 @@ class CompressionFilterTest extends FilterTestSupport { b.rpos(5); b.wpos(5); b.putBytes(original); - filterChain.getLast().out().send(b); + filterChain.getLast().out().send(0, b); assertEquals(1, outputs.outputs.size()); IoWriteFutureWithData outFuture = outputs.outputs.get(0); outputs.outputs.clear(); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CryptFilterTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CryptFilterTest.java index a4bb08ac6..2962a197a 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CryptFilterTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/CryptFilterTest.java @@ -179,7 +179,7 @@ class CryptFilterTest extends FilterTestSupport { b.rpos(5); b.wpos(5); b.putBytes(data); - filterChain.getLast().out().send(b); + filterChain.getLast().out().send(0, b); } assertEquals(1000, outputs.outputs.size()); if (outCipher == null && outMac == null) { diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/FilterTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/FilterTestSupport.java index c67e8ca85..33be7532a 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/FilterTestSupport.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/FilterTestSupport.java @@ -68,7 +68,7 @@ abstract class FilterTestSupport extends JUnitTestSupport { @Override public OutputHandler out() { - return buf -> { + return (cmd, buf) -> { IoWriteFutureWithData result = new IoWriteFutureWithData(this, null, ByteArrayBuffer.getCompactClone(buf.array(), buf.rpos(), buf.available())); outputs.add(result); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/IdentFilterTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/IdentFilterTest.java index cbded6679..5e769e976 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/IdentFilterTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/IdentFilterTest.java @@ -81,7 +81,7 @@ class IdentFilterTest extends FilterTestSupport { @Test void normalOperation() throws Exception { - IoWriteFuture future = filterChain.getLast().out().send(buf("kex-init")); + IoWriteFuture future = filterChain.getLast().out().send(0, buf("kex-init")); assertFalse(future.isDone()); assertEquals(1, outputs.outputs.size()); IoWriteFutureWithData first = outputs.outputs.get(0); @@ -97,9 +97,9 @@ class IdentFilterTest extends FilterTestSupport { @Test void secondWriteIsQueued() throws Exception { - IoWriteFuture future = filterChain.getLast().out().send(buf("kex-init")); + IoWriteFuture future = filterChain.getLast().out().send(0, buf("kex-init")); assertFalse(future.isDone()); - IoWriteFuture future2 = filterChain.getLast().out().send(buf("second")); + IoWriteFuture future2 = filterChain.getLast().out().send(0, buf("second")); assertEquals(1, outputs.outputs.size()); IoWriteFutureWithData first = outputs.outputs.get(0); assertEquals("SSH-2.0-foo bar\r\n", str(first.data)); @@ -118,7 +118,7 @@ class IdentFilterTest extends FilterTestSupport { @Test void onlyIdent() throws Exception { - IoWriteFuture future = filterChain.getLast().out().send(null); + IoWriteFuture future = filterChain.getLast().out().send(0, null); assertFalse(future.isDone()); assertEquals(1, outputs.outputs.size()); IoWriteFutureWithData first = outputs.outputs.get(0); @@ -127,7 +127,7 @@ class IdentFilterTest extends FilterTestSupport { assertTrue(future.isWritten()); assertEquals(1, outputs.outputs.size()); outputs.autoFulfill = true; - IoWriteFuture future2 = filterChain.getLast().out().send(buf("kex-init")); + IoWriteFuture future2 = filterChain.getLast().out().send(0, buf("kex-init")); assertEquals(2, outputs.outputs.size()); IoWriteFutureWithData second = outputs.outputs.get(1); assertEquals("kex-init", str(second.data)); @@ -152,7 +152,7 @@ class IdentFilterTest extends FilterTestSupport { } }); outputs.autoFulfill = true; - IoWriteFuture future = filterChain.getLast().out().send(buf("kex-init")); + IoWriteFuture future = filterChain.getLast().out().send(0, buf("kex-init")); assertFalse(future.isDone()); assertEquals(0, outputs.outputs.size()); filterChain.getFirst().in().received(buf("SSH-2.0-")); @@ -161,7 +161,7 @@ class IdentFilterTest extends FilterTestSupport { assertEquals(0, inputs.buffers.size()); filterChain.getFirst().in().received(buf("foo foo")); assertFalse(future.isDone()); - IoWriteFuture future2 = filterChain.getLast().out().send(buf("second")); + IoWriteFuture future2 = filterChain.getLast().out().send(0, buf("second")); assertFalse(future2.isDone()); assertEquals(0, outputs.outputs.size()); assertEquals(0, inputs.buffers.size()); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/InjectIgnoreFilterTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/InjectIgnoreFilterTest.java index d136254b4..e3467185e 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/filters/InjectIgnoreFilterTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/filters/InjectIgnoreFilterTest.java @@ -76,7 +76,7 @@ class InjectIgnoreFilterTest extends FilterTestSupport { final int rounds = 10; for (int i = 0; i < frequency * rounds; i++) { - filterChain.getLast().out().send(msg); + filterChain.getLast().out().send(0xff, msg); } assertEquals((frequency + 1) * rounds, outputs.outputs.size()); List<IoWriteFutureWithData> out = outputs.outputs;