This is an automated email from the ASF dual-hosted git repository. tomaswolf pushed a commit to branch rc-2.20.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 588d4ee4fe1aca05828911fed7002d93cb9aa8ee Author: Thomas Wolf <[email protected]> AuthorDate: Fri Sep 18 22:10:03 2026 +0200 SCP: limit the protocol line length SCP uses a line-oriented command protocol with LF-terminated lines. OpenSSH limits the maximum length of such protocol lines to 2kB. Introduce a similar limit, albeit a bit higher since we allow arbitrary character encodings, whereas OpenSSH classic SCP is limited to 8-bit characters. The new limit in Apache MINA SSHD is 16kB. If no LF is seen until that limit, an IOException is thrown, terminating the SCP command with an error exit status. --- .../apache/sshd/scp/common/helpers/ScpIoUtils.java | 11 +++ .../org/apache/sshd/scp/server/ScpCommand.java | 2 +- .../org/apache/sshd/scp/common/ScpIoUtilsTest.java | 88 ++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java index 5c6a61fa5..ec5b479e3 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java +++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java @@ -45,6 +45,14 @@ import org.slf4j.Logger; * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public final class ScpIoUtils { + + /** + * The SCP protocol uses LF-terminated lines. OpenSSH limits this line length to at most 2048 characters / bytes. We + * permit UTF-8 encoding for file names, so our buffer should be considerably larger. If we receive a protocol line + * longer than this limit, {@link #readLine(InputStream, Charset)} will throw an {@link IOException}. + */ + public static final int MAX_PROTOCOL_LINE_LENGTH = 16 * 1024; + public static final Set<ClientChannelEvent> COMMAND_WAIT_EVENTS = Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.EXIT_STATUS, ClientChannelEvent.CLOSED)); @@ -68,6 +76,9 @@ public final class ScpIoUtils { } return null; } else { + if (baos.size() >= MAX_PROTOCOL_LINE_LENGTH) { + throw new IOException("SCP protocol line too long"); + } baos.write(c); } } diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java index b7f8847b6..c782bcd34 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java +++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java @@ -182,7 +182,7 @@ public class ScpCommand extends AbstractFileSystemCommand implements ServerChann } else { throw new IOException("Unsupported mode"); } - } catch (IOException e) { + } catch (Throwable e) { boolean debugEnabled = log.isDebugEnabled(); try { Integer statusCode = null; diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/common/ScpIoUtilsTest.java b/sshd-scp/src/test/java/org/apache/sshd/scp/common/ScpIoUtilsTest.java new file mode 100644 index 000000000..ed9e8ff7e --- /dev/null +++ b/sshd-scp/src/test/java/org/apache/sshd/scp/common/ScpIoUtilsTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.scp.common; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.sshd.scp.common.helpers.ScpIoUtils; +import org.apache.sshd.util.test.JUnitTestSupport; +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@TestMethodOrder(MethodName.class) +@Tag("NoIoTestCase") +public class ScpIoUtilsTest extends JUnitTestSupport { + + @Test + void testProtocolLineLimitExceeded() throws IOException { + byte[] longLine = new byte[ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH + 2]; + for (int i = 0; i < longLine.length; i++) { + longLine[i] = 'A'; + } + ByteArrayInputStream bis = new ByteArrayInputStream(longLine); + assertThrows(IOException.class, () -> ScpIoUtils.readLine(bis, StandardCharsets.US_ASCII)); + bis.reset(); + longLine[longLine.length - 1] = '\n'; + assertThrows(IOException.class, () -> ScpIoUtils.readLine(bis, StandardCharsets.US_ASCII)); + } + + @Test + void testProtocolLineLimit() throws IOException { + byte[] longLine = new byte[ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH + 1]; + for (int i = 0; i < longLine.length; i++) { + longLine[i] = 'A'; + } + longLine[longLine.length - 1] = '\n'; + ByteArrayInputStream bis = new ByteArrayInputStream(longLine); + String s = ScpIoUtils.readLine(bis, StandardCharsets.US_ASCII); + assertEquals(ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH, s.length()); + } + + @Test + void testProtocolLineLimitReached() throws IOException { + byte[] longLine = new byte[ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH]; + for (int i = 0; i < longLine.length; i++) { + longLine[i] = 'A'; + } + longLine[longLine.length - 1] = '\n'; + ByteArrayInputStream bis = new ByteArrayInputStream(longLine); + String s = ScpIoUtils.readLine(bis, StandardCharsets.US_ASCII); + assertEquals(ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH - 1, s.length()); + } + + @Test + void testProtocolLineShort() throws IOException { + byte[] longLine = new byte[ScpIoUtils.MAX_PROTOCOL_LINE_LENGTH]; + for (int i = 0; i < longLine.length; i++) { + longLine[i] = 'A'; + } + longLine[42] = '\n'; + ByteArrayInputStream bis = new ByteArrayInputStream(longLine); + String s = ScpIoUtils.readLine(bis, StandardCharsets.US_ASCII); + assertEquals(42, s.length()); + } +}
