From 8373c8893a40210ba1581b53efc062854accd26f Mon Sep 17 00:00:00 2001
From: starlet_dx <15929766099@163.com>
Date: Tue, 10 Aug 2021 15:42:56 +0800
Subject: [PATCH] fix CVE-2021-30129
---
CVE-2021-30129-1.patch | 1116 ++++++++++++++++++++++++++++++++++++++++
CVE-2021-30129-2.patch | 161 ++++++
CVE-2021-30129-3.patch | 408 +++++++++++++++
apache-sshd.spec | 12 +-
4 files changed, 1696 insertions(+), 1 deletion(-)
create mode 100644 CVE-2021-30129-1.patch
create mode 100644 CVE-2021-30129-2.patch
create mode 100644 CVE-2021-30129-3.patch
diff --git a/CVE-2021-30129-1.patch b/CVE-2021-30129-1.patch
new file mode 100644
index 0000000..a6148be
--- /dev/null
+++ b/CVE-2021-30129-1.patch
@@ -0,0 +1,1116 @@
+From 68c780f2e746eedb714ff6e17c6815fbffddeff6 Mon Sep 17 00:00:00 2001
+From: Guillaume Nodet
+Date: Fri, 17 Jul 2020 11:33:21 +0200
+Subject: [PATCH 1/1] [SSHD-1035] Move property definitions tocommon locations
+
+---
+ .../java/org/apache/sshd/common/Property.java | 408 +++++++++++
+ .../sshd/core/CoreModuleProperties.java | 681 ++++++++++++++++++
+ 2 files changed, 1089 insertions(+)
+ create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/Property.java
+ create mode 100644 sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java
+
+diff --git a/sshd-common/src/main/java/org/apache/sshd/common/Property.java b/sshd-common/src/main/java/org/apache/sshd/common/Property.java
+new file mode 100644
+index 0000000..f5ad335
+--- /dev/null
++++ b/sshd-common/src/main/java/org/apache/sshd/common/Property.java
+@@ -0,0 +1,408 @@
++/*
++ * 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.common;
++
++import java.nio.charset.Charset;
++import java.time.Duration;
++import java.util.Arrays;
++import java.util.Objects;
++import java.util.Optional;
++import java.util.function.Consumer;
++
++/**
++ * Property definition.
++ *
++ * @author Apache MINA SSHD Project
++ */
++public interface Property {
++
++ static Property string(String name) {
++ return new StringProperty(name);
++ }
++
++ static Property string(String name, String def) {
++ return new StringProperty(name, def);
++ }
++
++ static Property bool(String name) {
++ return new BooleanProperty(name);
++ }
++
++ static Property bool(String name, boolean def) {
++ return new BooleanProperty(name, def);
++ }
++
++ static Property integer(String name) {
++ return new IntProperty(name);
++ }
++
++ static Property integer(String name, int def) {
++ return new IntProperty(name, def);
++ }
++
++ // CHECKSTYLE:OFF
++ static Property long_(String name) {
++ return new LongProperty(name);
++ }
++
++ static Property long_(String name, long def) {
++ return new LongProperty(name, def);
++ }
++
++ static > Property enum_(String name, Class type) {
++ return new EnumProperty<>(name, type);
++ }
++
++ static > Property enum_(String name, Class type, T def) {
++ return new EnumProperty<>(name, type, def);
++ }
++ // CHECKSTYLE:ON
++
++ static Property duration(String name) {
++ return new DurationProperty(name);
++ }
++
++ static Property duration(String name, Duration def) {
++ return new DurationProperty(name, def);
++ }
++
++ static Property durationSec(String name) {
++ return new DurationInSecondsProperty(name);
++ }
++
++ static Property durationSec(String name, Duration def) {
++ return new DurationInSecondsProperty(name, def);
++ }
++
++ static Property charset(String name) {
++ return new CharsetProperty(name);
++ }
++
++ static Property charset(String name, Charset def) {
++ return new CharsetProperty(name, def);
++ }
++
++ static Property
++ *
++ *
++ *
A {@link java.net.URI} or a string starting with "file:/", in which case it will be converted to a
++ * {@link java.nio.file.Path} and handled accordingly.
++ *
++ *
++ *
++ *
A string containing a special value indicator - e.g., {@link #AUTO_WELCOME_BANNER_VALUE}, in which case the
++ * relevant banner content will be generated.
++ *
++ *
++ *
++ *
Any other object whose {@code toString()} value yields a non empty string will be used as the banner
++ * contents.
++ *
++ *
++ *
++ * @see RFC-4252 section 5.4
++ */
++ public static final Property WELCOME_BANNER
++ = Property.object("welcome-banner");
++
++ /**
++ * Special value that can be set for the {@link #WELCOME_BANNER} property indicating that the server should generate
++ * a banner consisting of the random art of the server's keys (if any are provided). If no server keys are
++ * available, then no banner will be sent
++ */
++ public static final String AUTO_WELCOME_BANNER_VALUE = "#auto-welcome-banner";
++
++ /**
++ * Key used to denote the language code for the welcome banner (if such a banner is configured).
++ */
++ public static final Property WELCOME_BANNER_LANGUAGE
++ = Property.string("welcome-banner-language", "en");
++
++ /**
++ * The {@link WelcomeBannerPhase} value - either as an enum or a string
++ */
++ public static final Property WELCOME_BANNER_PHASE
++ = Property.enum_("welcome-banner-phase", WelcomeBannerPhase.class, WelcomeBannerPhase.IMMEDIATE);
++
++ /**
++ * The charset to use if the configured welcome banner points to a file - if not specified (either as a string or a
++ * {@link java.nio.charset.Charset} then the local default is used.
++ */
++ public static final Property WELCOME_BANNER_CHARSET
++ = Property.charset("welcome-banner-charset", Charset.defaultCharset());
++
++ /**
++ * This key is used when configuring multi-step authentications. The value needs to be a blank separated list of
++ * comma separated list of authentication method names. For example, an argument of
++ * publickey,password publickey,keyboard-interactive would require the user to complete public key
++ * authentication, followed by either password or keyboard interactive authentication. Only methods that are next in
++ * one or more lists are offered at each stage, so for this example, it would not be possible to attempt password or
++ * keyboard-interactive authentication before public key.
++ */
++ public static final Property AUTH_METHODS
++ = Property.string("auth-methods");
++
++ /**
++ * Key used to retrieve the value of the maximum concurrent open session count per username. If not set, then
++ * unlimited
++ */
++ public static final Property MAX_CONCURRENT_SESSIONS
++ = Property.integer("max-concurrent-sessions");
++
++ /**
++ * Key used to retrieve any extra lines to be sent during initial protocol handshake before the
++ * identification. The configured string value should use {@value #SERVER_EXTRA_IDENT_LINES_SEPARATOR} character to
++ * denote line breaks
++ */
++ public static final Property SERVER_EXTRA_IDENTIFICATION_LINES
++ = Property.string("server-extra-identification-lines");
++
++ /**
++ * Separator used in the {@link #SERVER_EXTRA_IDENTIFICATION_LINES} configuration string to indicate new line break
++ */
++ public static final char SERVER_EXTRA_IDENT_LINES_SEPARATOR = '|';
++
++ /**
++ * Key used to retrieve the value of the server identification string. If set, then it is appended to the
++ * (standard) "SSH-2.0-" prefix. Otherwise a default is sent that consists of "SSH-2.0-" plus
++ * the current SSHD artifact name and version in uppercase - e.g., "SSH-2.0-APACHE-SSHD-1.0.0"
++ */
++ public static final Property SERVER_IDENTIFICATION
++ = Property.string("server-identification");
++
++ /**
++ * Key used to configure the timeout used when receiving a close request on a channel to wait until the command
++ * cleanly exits after setting an EOF on the input stream.
++ */
++ public static final Property COMMAND_EXIT_TIMEOUT
++ = Property.duration("command-exit-timeout", Duration.ofMillis(5L));
++
++ /**
++ * A URL pointing to the moduli file. If not specified, the default internal file will be used.
++ */
++ public static final Property MODULI_URL
++ = Property.string("moduli-url");
++
++ /**
++ * See {@link org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator}.
++ */
++ public static final Property KB_SERVER_INTERACTIVE_NAME
++ = Property.string("kb-server-interactive-name", "Password authentication");
++
++ /**
++ * See {@link org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator}.
++ */
++ public static final Property KB_SERVER_INTERACTIVE_INSTRUCTION
++ = Property.string("kb-server-interactive-instruction", "");
++
++ /**
++ * See {@link org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator}.
++ */
++ public static final Property KB_SERVER_INTERACTIVE_LANG
++ = Property.string("kb-server-interactive-language", "en-US");
++
++ /**
++ * See {@link org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator}.
++ */
++ public static final Property KB_SERVER_INTERACTIVE_PROMPT
++ = Property.string("kb-server-interactive-prompt", "Password: ");
++
++ /**
++ * See {@link org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator}.
++ */
++ public static final Property KB_SERVER_INTERACTIVE_ECHO_PROMPT
++ = Property.bool("kb-server-interactive-echo-prompt", false);
++
++ /**
++ * Maximum amount of extended (a.k.a. STDERR) data allowed to be accumulated until a {@link ChannelDataReceiver} for
++ * the data is registered
++ */
++ public static final Property MAX_EXTDATA_BUFSIZE
++ = Property.integer("channel-session-max-extdata-bufsize", 0);
++
++ /**
++ * See {@link org.apache.sshd.server.kex.DHGEXServer}.
++ */
++ public static final Property PROP_DHGEX_SERVER_MIN_KEY
++ = Property.integer("dhgex-server-min");
++
++ /**
++ * See {@link org.apache.sshd.server.kex.DHGEXServer}.
++ */
++ public static final Property PROP_DHGEX_SERVER_MAX_KEY
++ = Property.integer("dhgex-server-max");
++ /**
++ * Value used by the {@link org.apache.sshd.server.shell.InvertedShellWrapper} to control the "busy-wait"
++ * sleep time (millis) on the pumping loop if nothing was pumped - must be positive.
++ */
++ public static final Property PUMP_SLEEP_TIME
++ = Property.duration("inverted-shell-wrapper-pump-sleep", Duration.ofMillis(1));
++
++ /**
++ * Value used by the {@link org.apache.sshd.server.shell.InvertedShellWrapper} to control copy buffer size.
++ */
++ public static final Property BUFFER_SIZE
++ = Property.integer("inverted-shell-wrapper-buffer-size", IoUtils.DEFAULT_COPY_SIZE);
++
++ /**
++ * Configuration value for the {@link org.apache.sshd.server.x11.X11ForwardSupport} to control the channel open
++ * timeout.
++ */
++ public static final Property X11_OPEN_TIMEOUT
++ = Property.duration("x11-fwd-open-timeout", Duration.ofSeconds(30L));
++
++ /**
++ * Configuration value for the {@link org.apache.sshd.server.x11.X11ForwardSupport} to control from which X11
++ * display number to start looking for a free value.
++ */
++ public static final Property X11_DISPLAY_OFFSET
++ = Property.integer("x11-fwd-display-offset", 10);
++
++ /**
++ * Configuration value for the {@link org.apache.sshd.server.x11.X11ForwardSupport} to control up to which (but not
++ * including) X11 display number to look or a free value.
++ */
++ public static final Property X11_MAX_DISPLAYS
++ = Property.integer("x11-fwd-max-display", 1000);
++
++ /**
++ * Configuration value for the {@link org.apache.sshd.server.x11.X11ForwardSupport} to control the base port number
++ * for the X11 display number socket binding.
++ */
++ public static final Property X11_BASE_PORT
++ = Property.integer("x11-fwd-base-port", 6000);
++
++ /**
++ * Configuration value for the {@link org.apache.sshd.server.x11.X11ForwardSupport} to control the host used to bind
++ * to for the X11 display when looking for a free port.
++ */
++ public static final Property X11_BIND_HOST
++ = Property.string("x11-fwd-bind-host", SshdSocketAddress.LOCALHOST_IPV4);
++
++ private CoreModuleProperties() {
++ // private
++ }
++
++}
+--
+2.27.0
+
diff --git a/CVE-2021-30129-2.patch b/CVE-2021-30129-2.patch
new file mode 100644
index 0000000..8138582
--- /dev/null
+++ b/CVE-2021-30129-2.patch
@@ -0,0 +1,161 @@
+From 5b5bd1dcfa0c2fc250e079e1ebcd643b51f735eb Mon Sep 17 00:00:00 2001
+From: Lyor Goldstein
+Date: Fri, 26 Feb 2021 06:54:43 +0200
+Subject: [PATCH] [SSHD-1125] Added option to require immediate close of
+ channel in command ExitCallback invocation
+
+---
+ .../org/apache/sshd/server/ExitCallback.java | 27 +++++++++++++++++--
+ .../sshd/server/channel/ChannelSession.java | 10 +++----
+ .../sshd/util/test/BogusExitCallback.java | 12 ++++++---
+ .../server/subsystem/sftp/SftpSubsystem.java | 6 +++--
+ 4 files changed, 43 insertions(+), 12 deletions(-)
+
+diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java b/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
+index dfa55be..eb1bca1 100644
+--- a/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
++++ b/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
+@@ -30,7 +30,17 @@ public interface ExitCallback {
+ * @param exitValue the exit value
+ */
+ default void onExit(int exitValue) {
+- onExit(exitValue, "");
++ onExit(exitValue, false);
++ }
++
++ /**
++ * Informs the SSH server that the shell has exited
++ *
++ * @param exitValue the exit value
++ * @param closeImmediately whether to also terminate the channel immediately or do a graceful close.
++ */
++ default void onExit(int exitValue, boolean closeImmediately) {
++ onExit(exitValue, "", closeImmediately);
+ }
+
+ /**
+@@ -39,5 +49,18 @@ public interface ExitCallback {
+ * @param exitValue the exit value
+ * @param exitMessage exit value description
+ */
+- void onExit(int exitValue, String exitMessage);
++ default void onExit(int exitValue, String exitMessage) {
++ onExit(exitValue, exitMessage, false);
++ }
++
++ /**
++ *
++ * Informs the SSH client/server that the shell has exited
++ *
++ * @param exitValue the exit value
++ * @param exitMessage exit value description
++ * @param closeImmediately whether to also terminate the channel immediately or do a graceful close.
++ */
++ void onExit(int exitValue, String exitMessage, boolean closeImmediately);
++
+ }
+diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+index e54e0e4..484b4f0 100644
+--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
++++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+@@ -684,9 +684,9 @@ public class ChannelSession extends AbstractServerChannel {
+ tempBuffer = null;
+ doWriteData(buffer.array(), buffer.rpos(), buffer.available());
+ }
+- command.setExitCallback((exitValue, exitMessage) -> {
++ command.setExitCallback((exitValue, exitMessage, closeImmediately) -> {
+ try {
+- closeShell(exitValue);
++ closeShell(exitValue, closeImmediately);
+ if (log.isDebugEnabled()) {
+ log.debug("onExit({}) code={} message='{}' shell closed", ChannelSession.this, exitValue, exitMessage);
+ }
+@@ -798,9 +798,9 @@ public class ChannelSession extends AbstractServerChannel {
+ return env;
+ }
+
+- protected void closeShell(int exitValue) throws IOException {
++ protected void closeShell(int exitValue, boolean closeImmediately) throws IOException {
+ if (log.isDebugEnabled()) {
+- log.debug("closeShell({}) exit code={}", this, exitValue);
++ log.debug("closeShell({}) exit code={}, immediate={}", this, exitValue, closeImmediately);
+ }
+
+ if (!isClosing()) {
+@@ -810,7 +810,7 @@ public class ChannelSession extends AbstractServerChannel {
+ sendEof();
+ sendExitStatus(exitValue);
+ commandExitFuture.setClosed();
+- close(false);
++ close(closeImmediately);
+ } else {
+ commandExitFuture.setClosed();
+ }
+diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusExitCallback.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusExitCallback.java
+index a4f1ff6..9de602a 100644
+--- a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusExitCallback.java
++++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusExitCallback.java
+@@ -25,21 +25,23 @@ public class BogusExitCallback implements ExitCallback {
+ private boolean exited;
+ private int exitValue;
+ private String exitMessage;
++ private boolean closeImmediately;
+
+ public BogusExitCallback() {
+ super();
+ }
+
+ @Override
+- public void onExit(int exitValue) {
+- onExit(exitValue, String.valueOf(exitValue));
++ public void onExit(int exitValue, boolean closeImmediately) {
++ onExit(exitValue, String.valueOf(exitValue), closeImmediately);
+ }
+
+ @Override
+- public void onExit(int exitValue, String exitMessage) {
++ public void onExit(int exitValue, String exitMessage, boolean closeImmediately) {
+ this.exited = true;
+ this.exitValue = exitValue;
+ this.exitMessage = exitMessage;
++ this.closeImmediately = closeImmediately;
+ }
+
+ public boolean isExited() {
+@@ -53,4 +55,8 @@ public class BogusExitCallback implements ExitCallback {
+ public String getExitMessage() {
+ return exitMessage;
+ }
++
++ public boolean isCloseImmediately() {
++ return closeImmediately;
++ }
+ }
+diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+index 66a0ced..c18b55e 100644
+--- a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
++++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+@@ -299,6 +299,7 @@ public class SftpSubsystem
+
+ @Override
+ public void run() {
++ int exitCode = 0;
+ try {
+ while (true) {
+ Buffer buffer = requests.take();
+@@ -318,10 +319,11 @@ public class SftpSubsystem
+ if (log.isDebugEnabled()) {
+ log.debug("run(" + session + ") caught exception details", t);
+ }
++ exitCode = -1;
+ }
+ } finally {
+ closeAllHandles();
+- callback.onExit(0);
++ callback.onExit(exitCode, exitCode != 0);
+ }
+ }
+
+--
+2.27.0
+
diff --git a/CVE-2021-30129-3.patch b/CVE-2021-30129-3.patch
new file mode 100644
index 0000000..722ea2b
--- /dev/null
+++ b/CVE-2021-30129-3.patch
@@ -0,0 +1,408 @@
+From f9b2f236e6a663011b50bd7e9a41ec90e6b94831 Mon Sep 17 00:00:00 2001
+From: Lyor Goldstein
+Date: Thu, 25 Feb 2021 21:05:49 +0200
+Subject: [PATCH] [SSHD-1125] Added mechanism to throttle pending write
+ requests in BufferedIoOutputStream
+
+---
+ .../channel/BufferedIoOutputStream.java | 187 ++++++++++++++++--
+ .../SshChannelBufferedOutputException.java | 41 ++++
+ .../sshd/core/CoreModuleProperties.java | 19 ++
+ .../sshd/util/test/AsyncEchoShellFactory.java | 13 +-
+ .../server/subsystem/sftp/SftpSubsystem.java | 3 +-
+ 5 files changed, 243 insertions(+), 20 deletions(-)
+ create mode 100644 sshd-core/src/main/java/org/apache/sshd/common/channel/exception/SshChannelBufferedOutputException.java
+
+diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
+index 1cb75aa..c95a449 100644
+--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
++++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
+@@ -20,29 +20,55 @@ package org.apache.sshd.common.channel;
+
+ import java.io.EOFException;
+ import java.io.IOException;
++import java.time.Duration;
++import java.util.Objects;
+ import java.util.Queue;
+ import java.util.concurrent.ConcurrentLinkedQueue;
++import java.util.concurrent.atomic.AtomicInteger;
++import java.util.concurrent.atomic.AtomicLong;
+ import java.util.concurrent.atomic.AtomicReference;
+
+ import org.apache.sshd.common.Closeable;
++import org.apache.sshd.common.PropertyResolver;
++import org.apache.sshd.common.channel.exception.SshChannelBufferedOutputException;
+ import org.apache.sshd.common.future.SshFutureListener;
+ import org.apache.sshd.common.io.IoOutputStream;
+ import org.apache.sshd.common.io.IoWriteFuture;
++import org.apache.sshd.common.util.GenericUtils;
++import org.apache.sshd.common.util.ValidateUtils;
+ import org.apache.sshd.common.util.buffer.Buffer;
+ import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
++import org.apache.sshd.core.CoreModuleProperties;
+
+ /**
+ * An {@link IoOutputStream} capable of queuing write requests
+ */
+ public class BufferedIoOutputStream extends AbstractInnerCloseable implements IoOutputStream {
++ protected final Object id;
++ protected final int channelId;
++ protected final int maxPendingBytesCount;
++ protected final Duration maxWaitForPendingWrites;
+ protected final IoOutputStream out;
++ protected final AtomicInteger pendingBytesCount = new AtomicInteger();
++ protected final AtomicLong writtenBytesCount = new AtomicLong();
+ protected final Queue writes = new ConcurrentLinkedQueue<>();
+ protected final AtomicReference currentWrite = new AtomicReference<>();
+- protected final Object id;
++ protected final AtomicReference pendingException = new AtomicReference<>();
+
+- public BufferedIoOutputStream(Object id, IoOutputStream out) {
+- this.out = out;
+- this.id = id;
++ public BufferedIoOutputStream(Object id, int channelId, IoOutputStream out, PropertyResolver resolver) {
++ this(id, channelId, out, CoreModuleProperties.BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_SIZE.getRequired(resolver),
++ CoreModuleProperties.BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_WAIT.getRequired(resolver));
++ }
++
++ public BufferedIoOutputStream(
++ Object id, int channelId, IoOutputStream out, int maxPendingBytesCount,
++ Duration maxWaitForPendingWrites) {
++ this.id = Objects.requireNonNull(id, "No stream identifier provided");
++ this.channelId = channelId;
++ this.out = Objects.requireNonNull(out, "No delegate output stream provided");
++ this.maxPendingBytesCount = maxPendingBytesCount;
++ ValidateUtils.checkTrue(maxPendingBytesCount > 0, "Invalid max. pending bytes count: %d", maxPendingBytesCount);
++ this.maxWaitForPendingWrites = Objects.requireNonNull(maxWaitForPendingWrites, "No max. pending time value provided");
+ }
+
+ public Object getId() {
+@@ -52,26 +78,114 @@ public class BufferedIoOutputStream extends AbstractInnerCloseable implements Io
+ @Override
+ public IoWriteFuture writePacket(Buffer buffer) throws IOException {
+ if (isClosing()) {
+- throw new EOFException("Closed");
++ throw new EOFException("Closed/ing - state=" + state);
+ }
+
++ waitForAvailableWriteSpace(buffer.available());
++
+ IoWriteFutureImpl future = new IoWriteFutureImpl(getId(), buffer);
+ writes.add(future);
+ startWriting();
+ return future;
+ }
+
++ protected void waitForAvailableWriteSpace(int requiredSize) throws IOException {
++ /*
++ * NOTE: this code allows a single pending write to give this mechanism "the slip" and
++ * exit the loop "unscathed" even though there is a pending exception. However, the goal
++ * here is to avoid an OOM by having an unlimited accumulation of pending write requests
++ * due to fact that the peer is not consuming the sent data. Please note that the pending
++ * exception is "sticky" - i.e., the next write attempt will fail. This also means that if
++ * the write request that "got away" was the last one by chance and it was consumed by the
++ * peer there will be no exception thrown - which is also fine since as mentioned the goal
++ * is not to enforce a strict limit on the pending bytes size but rather on the accumulation
++ * of the pending write requests.
++ *
++ * We could have counted pending requests rather than bytes. However, we also want to avoid
++ * having a large amount of data pending consumption by the peer as well. This code strikes
++ * such a balance by allowing a single pending request to exceed the limit, but at the same
++ * time prevents too many bytes from pending by having a bunch of pending requests that while
++ * below the imposed number limit may cumulatively represent a lot of pending bytes.
++ */
++
++ long expireTime = System.currentTimeMillis() + maxWaitForPendingWrites.toMillis();
++ synchronized (pendingBytesCount) {
++ for (int count = pendingBytesCount.get();
++ /*
++ * The (count > 0) condition is put in place to allow a single pending
++ * write to exceed the maxPendingBytesCount as long as there are no
++ * other pending ones.
++ */
++ (count > 0)
++ // Not already over the limit or about to be over it
++ && ((count + requiredSize) > maxPendingBytesCount)
++ // No pending exception signaled
++ && (pendingException.get() == null);
++ count = pendingBytesCount.get()) {
++ long remTime = expireTime - System.currentTimeMillis();
++ if (remTime <= 0L) {
++ pendingException.compareAndSet(null,
++ new SshChannelBufferedOutputException(
++ channelId,
++ "Max. pending write timeout expired after " + writtenBytesCount + " bytes"));
++ throw pendingException.get();
++ }
++
++ try {
++ pendingBytesCount.wait(remTime);
++ } catch (InterruptedException e) {
++ pendingException.compareAndSet(null,
++ new SshChannelBufferedOutputException(
++ channelId,
++ "Waiting for pending writes interrupted after " + writtenBytesCount + " bytes"));
++ throw pendingException.get();
++ }
++ }
++
++ IOException e = pendingException.get();
++ if (e != null) {
++ throw e;
++ }
++
++ pendingBytesCount.addAndGet(requiredSize);
++ }
++ }
++
+ protected void startWriting() throws IOException {
+ IoWriteFutureImpl future = writes.peek();
++ // No more pending requests
+ if (future == null) {
+ return;
+ }
+
++ // Don't try to write any further if pending exception signaled
++ Throwable pendingError = pendingException.get();
++ if (pendingError != null) {
++ log.error("startWriting({})[{}] propagate to {} write requests pending error={}[{}]",
++ getId(), out, writes.size(), getClass().getSimpleName(), pendingError.getMessage());
++
++ IoWriteFutureImpl currentFuture = currentWrite.getAndSet(null);
++ for (IoWriteFutureImpl pendingWrite : writes) {
++ // Checking reference by design
++ if (GenericUtils.isSameReference(pendingWrite, currentFuture)) {
++ continue; // will be taken care of when its listener is eventually called
++ }
++
++ future.setValue(pendingError);
++ }
++
++ writes.clear();
++ return;
++ }
++
++ // Cannot honor this request yet since other pending one incomplete
+ if (!currentWrite.compareAndSet(null, future)) {
+ return;
+ }
+
+- out.writePacket(future.getBuffer()).addListener(new SshFutureListener() {
++ Buffer buffer = future.getBuffer();
++ int bufferSize = buffer.available();
++ out.writePacket(buffer).addListener(new SshFutureListener() {
+ @Override
+ public void operationComplete(IoWriteFuture f) {
+ if (f.isWritten()) {
+@@ -79,32 +193,71 @@ public class BufferedIoOutputStream extends AbstractInnerCloseable implements Io
+ } else {
+ future.setValue(f.getException());
+ }
+- finishWrite();
++ finishWrite(future, bufferSize);
++ }
++ });
++ }
++
++ protected void finishWrite(IoWriteFutureImpl future, int bufferSize) {
++ /*
++ * Update the pending bytes count only if successfully written,
++ * otherwise signal an error
++ */
++ if (future.isWritten()) {
++ long writtenSize = writtenBytesCount.addAndGet(bufferSize);
++ int stillPending;
++ synchronized (pendingBytesCount) {
++ stillPending = pendingBytesCount.addAndGet(0 - bufferSize);
++ pendingBytesCount.notifyAll();
+ }
+
+- @SuppressWarnings("synthetic-access")
+- private void finishWrite() {
++ /*
++ * NOTE: since the pending exception is updated outside the synchronized block
++ * a pending write could be successfully enqueued, however this is acceptable
++ * - see comment in waitForAvailableWriteSpace
++ */
++ if (stillPending < 0) {
++ log.error("finishWrite({})[{}] - pending byte counts underflow ({}) after {} bytes", getId(), out, stillPending,
++ writtenSize);
++ pendingException.compareAndSet(null,
++ new SshChannelBufferedOutputException(channelId, "Pending byte counts underflow"));
++ }
++ } else {
++ Throwable t = future.getException();
++ if (t instanceof SshChannelBufferedOutputException) {
++ pendingException.compareAndSet(null, (SshChannelBufferedOutputException) t);
++ } else {
++ pendingException.compareAndSet(null, new SshChannelBufferedOutputException(channelId, t));
++ }
++
++ // In case someone waiting so that they can detect the exception
++ synchronized (pendingBytesCount) {
++ pendingBytesCount.notifyAll();
++ }
++ }
++
+ writes.remove(future);
+ currentWrite.compareAndSet(future, null);
+ try {
+ startWriting();
+ } catch (IOException e) {
+- log.error("finishWrite({}) failed ({}) re-start writing", out, e.getClass().getSimpleName());
++ if (e instanceof SshChannelBufferedOutputException) {
++ pendingException.compareAndSet(null, (SshChannelBufferedOutputException) e);
++ } else {
++ pendingException.compareAndSet(null, new SshChannelBufferedOutputException(channelId, e));
++ }
++ log.error("finishWrite({})[{}] failed ({}) re-start writing: {}",
++ getId(), out, e.getClass().getSimpleName(), e.getMessage(), e);
+ }
+ }
+- });
+- }
+
+ @Override
+ protected Closeable getInnerCloseable() {
+- return builder()
+- .when(getId(), writes)
+- .close(out)
+- .build();
++ return builder().when(getId(), writes).close(out).build();
+ }
+
+ @Override
+ public String toString() {
+- return getClass().getSimpleName() + "[" + out + "]";
++ return getClass().getSimpleName() + "(" + getId() + ")[" + out + "]";
+ }
+ }
+diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/exception/SshChannelBufferedOutputException.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/exception/SshChannelBufferedOutputException.java
+new file mode 100644
+index 0000000..97e6105
+--- /dev/null
++++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/exception/SshChannelBufferedOutputException.java
+@@ -0,0 +1,41 @@
++/*
++ * 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.common.channel.exception;
++
++/**
++ * Used by the {@code BufferedIoOutputStream} to signal a non-recoverable error
++ *
++ * @author Apache MINA SSHD Project
++ */
++public class SshChannelBufferedOutputException extends SshChannelException {
++ private static final long serialVersionUID = -8663890657820958046L;
++
++ public SshChannelBufferedOutputException(int channelId, String message) {
++ this(channelId, message, null);
++ }
++
++ public SshChannelBufferedOutputException(int channelId, Throwable cause) {
++ this(channelId, cause.getMessage(), cause);
++ }
++
++ public SshChannelBufferedOutputException(int channelId, String message, Throwable cause) {
++ super(channelId, message, cause);
++ }
++}
+diff --git a/sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java b/sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java
+index 9e9b2d2..0d122e5 100644
+--- a/sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java
++++ b/sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java
+@@ -24,6 +24,7 @@ import java.time.Duration;
+
+ import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+ import org.apache.sshd.common.Property;
++import org.apache.sshd.common.SshConstants;
+ import org.apache.sshd.common.channel.Channel;
+ import org.apache.sshd.common.session.Session;
+ import org.apache.sshd.common.util.OsUtils;
+@@ -240,6 +241,24 @@ public final class CoreModuleProperties {
+ public static final Property WINDOW_TIMEOUT
+ = Property.duration("window-timeout", Duration.ZERO);
+
++ /**
++ * Key used when creating a {@code BufferedIoOutputStream} in order to specify max. allowed unwritten pending bytes.
++ * If this value is exceeded then the code waits up to {@link #BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_WAIT} for the
++ * pending data to be written and thus make room for the new request.
++ */
++ public static final Property BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_SIZE
++ = Property.integer("buffered-io-output-max-pending-write-size",
++ SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT * 8);
++
++ /**
++ * Key used when creating a {@code BufferedIoOutputStream} in order to specify max. wait time (msec.) for pending
++ * writes to be completed before enqueuing a new request
++ *
++ * @see #BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_SIZE
++ */
++ public static final Property BUFFERED_IO_OUTPUT_MAX_PENDING_WRITE_WAIT
++ = Property.duration("buffered-io-output-max-pending-write-wait", Duration.ofSeconds(30L));
++
+ /**
+ * Key used to retrieve the value of the maximum packet size in the configuration properties map.
+ */
+diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java b/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
+index de9dbf4..465ff83 100644
+--- a/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
++++ b/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
+@@ -99,12 +99,21 @@ public class AsyncEchoShellFactory implements ShellFactory {
+
+ @Override
+ public void setIoOutputStream(IoOutputStream out) {
+- this.out = new BufferedIoOutputStream("STDOUT", out);
++ this.out = wrapOutputStream("SHELL-STDOUT", out);
+ }
+
+ @Override
+ public void setIoErrorStream(IoOutputStream err) {
+- this.err = new BufferedIoOutputStream("STDERR", err);
++ this.err = wrapOutputStream("SHELL-STDERR", err);
++ }
++
++ protected BufferedIoOutputStream wrapOutputStream(String prefix, IoOutputStream stream) {
++ if (stream instanceof BufferedIoOutputStream) {
++ return (BufferedIoOutputStream) stream;
++ }
++
++ int channelId = session.getId();
++ return new BufferedIoOutputStream(prefix + "@" + channelId, channelId, stream, session);
+ }
+
+ @Override
+diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+index 66a0ced..15201ec 100644
+--- a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
++++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+@@ -256,7 +256,8 @@ public class SftpSubsystem
+
+ @Override
+ public void setIoOutputStream(IoOutputStream out) {
+- this.out = new BufferedIoOutputStream("sftp out buffer", out);
++ int channelId = channelSession.getId();
++ this.out = new BufferedIoOutputStream("sftp-out@" + channelId, channelId, out, channelSession);
+ }
+
+ @Override
+--
+2.27.0
+
diff --git a/apache-sshd.spec b/apache-sshd.spec
index 109aa84..235d0a6 100644
--- a/apache-sshd.spec
+++ b/apache-sshd.spec
@@ -1,12 +1,16 @@
Epoch: 1
Name: apache-sshd
Version: 2.2.0
-Release: 1
+Release: 2
Summary: Apache SSHD
License: ASL 2.0 and ISC
URL: http://mina.apache.org/sshd-project
Source0: https://archive.apache.org/dist/mina/sshd/%{version}/apache-sshd-%{version}-src.tar.gz
Patch0: 0001-Avoid-optional-dependency-on-native-tomcat-APR-libra.patch
+Patch1: CVE-2021-30129-1.patch
+Patch2: CVE-2021-30129-2.patch
+Patch3: CVE-2021-30129-3.patch
+
BuildRequires: maven-local mvn(junit:junit) mvn(net.i2p.crypto:eddsa) mvn(org.apache.ant:ant)
BuildRequires: mvn(org.apache:apache:pom:) mvn(org.apache.felix:maven-bundle-plugin)
BuildRequires: mvn(org.apache.maven:maven-archiver)
@@ -31,6 +35,9 @@ This package provides %{name}.
%prep
%setup -q
%patch0 -p1
+%patch1 -p1
+%patch2 -p1
+%patch3 -p1
rm -rf sshd-core/src/main/java/org/apache/sshd/agent/unix
%pom_remove_dep :spring-framework-bom
%pom_disable_module assembly
@@ -64,5 +71,8 @@ rm -rf sshd-core/src/main/java/org/apache/sshd/agent/unix
%license LICENSE.txt NOTICE.txt assembly/src/main/legal/licenses/jbcrypt.txt
%changelog
+* Tue Aug 10 2021 yaoxin - 2.2.0-2
+- Fix CVE-2021-30129
+
* Thu Aug 6 2020 Jeffery.Gao - 2.2.0-1
- Package init