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;


Reply via email to