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 object(String name) { ++ return new ObjectProperty(name); ++ } ++ ++ static Property object(String name, Object def) { ++ return new ObjectProperty(name, def); ++ } ++ ++ static Property validating(Property prop, Consumer validator) { ++ return new Validating<>(prop, validator); ++ } ++ ++ abstract class BaseProperty implements Property { ++ ++ private final String name; ++ private final T defaultValue; ++ ++ public BaseProperty(String name) { ++ this(name, null); ++ } ++ ++ public BaseProperty(String name, T defaultValue) { ++ this.name = Objects.requireNonNull(name, "No name provided"); ++ this.defaultValue = defaultValue; ++ } ++ ++ @Override ++ public String getName() { ++ return name; ++ } ++ ++ @Override ++ public Optional getDefault() { ++ return Optional.ofNullable(defaultValue); ++ } ++ ++ @Override ++ public T getRequiredDefault() { ++ return getDefault().get(); ++ } ++ ++ @Override ++ public Optional get(PropertyResolver resolver) { ++ Object propValue = PropertyResolverUtils.resolvePropertyValue(resolver, name); ++ return propValue != null ? Optional.of(fromStorage(propValue)) : getDefault(); ++ } ++ ++ @Override ++ public T getRequired(PropertyResolver resolver) { ++ return get(resolver).get(); ++ } ++ ++ @Override ++ public T getOrNull(PropertyResolver resolver) { ++ return get(resolver).orElse(null); ++ } ++ ++ @Override ++ public void set(PropertyResolver resolver, T value) { ++ PropertyResolverUtils.updateProperty(resolver, name, toStorage(value)); ++ } ++ ++ @Override ++ public void remove(PropertyResolver resolver) { ++ PropertyResolverUtils.updateProperty(resolver, name, null); ++ } ++ ++ protected Object toStorage(T value) { ++ return value; ++ } ++ ++ protected abstract T fromStorage(Object value); ++ ++ @Override ++ public String toString() { ++ return "Property[" + name + "]"; ++ } ++ } ++ ++ class DurationProperty extends BaseProperty { ++ ++ public DurationProperty(String name) { ++ super(name); ++ } ++ ++ public DurationProperty(String name, Duration def) { ++ super(name, def); ++ } ++ ++ @Override ++ protected Object toStorage(Duration value) { ++ return value != null ? value.toMillis() : null; ++ } ++ ++ @Override ++ protected Duration fromStorage(Object value) { ++ Long val = PropertyResolverUtils.toLong(value); ++ return val != null ? Duration.ofMillis(val) : null; ++ } ++ } ++ ++ class DurationInSecondsProperty extends DurationProperty { ++ public DurationInSecondsProperty(String name) { ++ super(name); ++ } ++ ++ public DurationInSecondsProperty(String name, Duration def) { ++ super(name, def); ++ } ++ ++ @Override ++ protected Object toStorage(Duration value) { ++ return value != null ? value.toMillis() / 1_000 : null; ++ } ++ ++ @Override ++ protected Duration fromStorage(Object value) { ++ Long val = PropertyResolverUtils.toLong(value); ++ return val != null ? Duration.ofSeconds(val) : null; ++ } ++ } ++ ++ class StringProperty extends BaseProperty { ++ ++ public StringProperty(String name) { ++ super(name); ++ } ++ ++ public StringProperty(String name, String def) { ++ super(name, def); ++ } ++ ++ @Override ++ protected String fromStorage(Object value) { ++ return value != null ? value.toString() : null; ++ } ++ } ++ ++ class BooleanProperty extends BaseProperty { ++ ++ public BooleanProperty(String name) { ++ super(name); ++ } ++ ++ public BooleanProperty(String name, Boolean defaultValue) { ++ super(name, defaultValue); ++ } ++ ++ @Override ++ protected Boolean fromStorage(Object value) { ++ return PropertyResolverUtils.toBoolean(value); ++ } ++ ++ } ++ ++ class LongProperty extends BaseProperty { ++ ++ public LongProperty(String name) { ++ super(name); ++ } ++ ++ public LongProperty(String name, Long defaultValue) { ++ super(name, defaultValue); ++ } ++ ++ @Override ++ protected Long fromStorage(Object value) { ++ return PropertyResolverUtils.toLong(value); ++ } ++ } ++ ++ class IntProperty extends BaseProperty { ++ ++ public IntProperty(String name) { ++ super(name); ++ } ++ ++ public IntProperty(String name, Integer defaultValue) { ++ super(name, defaultValue); ++ } ++ ++ @Override ++ protected Integer fromStorage(Object value) { ++ return PropertyResolverUtils.toInteger(value); ++ } ++ } ++ ++ class CharsetProperty extends BaseProperty { ++ ++ public CharsetProperty(String name) { ++ super(name); ++ } ++ ++ public CharsetProperty(String name, Charset defaultValue) { ++ super(name, defaultValue); ++ } ++ ++ @Override ++ protected Charset fromStorage(Object value) { ++ return PropertyResolverUtils.toCharset(value); ++ } ++ } ++ ++ class ObjectProperty extends BaseProperty { ++ ++ public ObjectProperty(String name) { ++ super(name); ++ } ++ ++ public ObjectProperty(String name, Object defaultValue) { ++ super(name, defaultValue); ++ } ++ ++ @Override ++ protected Object fromStorage(Object value) { ++ return value; ++ } ++ } ++ ++ class EnumProperty> extends BaseProperty { ++ ++ private final Class type; ++ ++ public EnumProperty(String name, Class type) { ++ super(name); ++ this.type = Objects.requireNonNull(type, "type is required"); ++ } ++ ++ public EnumProperty(String name, Class type, T def) { ++ super(name, def); ++ this.type = Objects.requireNonNull(type, "type is required"); ++ } ++ ++ @Override ++ protected T fromStorage(Object value) { ++ return PropertyResolverUtils.toEnum(type, value, false, Arrays.asList(type.getEnumConstants())); ++ } ++ } ++ ++ class Validating implements Property { ++ private final Property delegate; ++ private final Consumer validator; ++ ++ public Validating(Property delegate, Consumer validator) { ++ this.delegate = delegate; ++ this.validator = validator; ++ } ++ ++ @Override ++ public String getName() { ++ return delegate.getName(); ++ } ++ ++ @Override ++ public Optional getDefault() { ++ return delegate.getDefault(); ++ } ++ ++ @Override ++ public T getRequiredDefault() { ++ return delegate.getRequiredDefault(); ++ } ++ ++ @Override ++ public Optional get(PropertyResolver resolver) { ++ Optional t = delegate.get(resolver); ++ t.ifPresent(validator); ++ return t; ++ } ++ ++ @Override ++ public T getRequired(PropertyResolver resolver) { ++ return get(resolver).get(); ++ } ++ ++ @Override ++ public T getOrNull(PropertyResolver resolver) { ++ return get(resolver).orElse(null); ++ } ++ ++ @Override ++ public void set(PropertyResolver resolver, T value) { ++ validator.accept(value); ++ delegate.set(resolver, value); ++ } ++ ++ @Override ++ public void remove(PropertyResolver resolver) { ++ delegate.remove(resolver); ++ } ++ } ++ ++ String getName(); ++ ++ Optional getDefault(); ++ ++ T getRequiredDefault(); ++ ++ Optional get(PropertyResolver resolver); ++ ++ T getRequired(PropertyResolver resolver); ++ ++ T getOrNull(PropertyResolver resolver); ++ ++ void set(PropertyResolver resolver, T value); ++ ++ void remove(PropertyResolver resolver); ++} +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 +new file mode 100644 +index 0000000..9e9b2d2 +--- /dev/null ++++ b/sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java +@@ -0,0 +1,681 @@ ++/* ++ * 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.core; ++ ++import java.io.IOException; ++import java.nio.charset.Charset; ++import java.time.Duration; ++ ++import org.apache.sshd.client.config.keys.ClientIdentityLoader; ++import org.apache.sshd.common.Property; ++import org.apache.sshd.common.channel.Channel; ++import org.apache.sshd.common.session.Session; ++import org.apache.sshd.common.util.OsUtils; ++import org.apache.sshd.common.util.ValidateUtils; ++import org.apache.sshd.common.util.io.IoUtils; ++import org.apache.sshd.common.util.net.SshdSocketAddress; ++import org.apache.sshd.server.auth.WelcomeBannerPhase; ++import org.apache.sshd.server.channel.ChannelDataReceiver; ++ ++/** ++ * Configurable properties for sshd-core. ++ * ++ * @author Apache MINA SSHD Project ++ */ ++public final class CoreModuleProperties { ++ ++ /** ++ * Value that can be set in order to control the type of authentication channel being requested when forwarding a ++ * PTY session. ++ */ ++ public static final Property PROXY_AUTH_CHANNEL_TYPE ++ = Property.string("ssh-agent-factory-proxy-auth-channel-type", "auth-agent-req@openssh.com"); ++ ++ /** ++ * See {@link org.apache.sshd.agent.local.ProxyAgentFactory#getChannelForwardingFactories} ++ */ ++ public static final Property PREFER_UNIX_AGENT ++ = Property.bool("ssh-prefer-unix-agent", OsUtils.isUNIX()); ++ ++ /** ++ * Value that can be set on the {@link org.apache.sshd.common.FactoryManager} or the session to configure the ++ * channel open timeout value (millis). ++ */ ++ public static final Property CHANNEL_OPEN_TIMEOUT ++ = Property.duration("ssh-agent-server-channel-open-timeout", Duration.ofSeconds(30)); ++ ++ /** ++ * Value used to configure the type of proxy forwarding channel to be used. See also ++ * https://tools.ietf.org/html/draft-ietf-secsh-agent-02 ++ */ ++ public static final Property PROXY_CHANNEL_TYPE ++ = Property.string("ssh-agent-server-channel-proxy-type", "auth-agent@openssh.com"); ++ ++ /** ++ * Property that can be set on the {@link Session} in order to control the authentication timeout (millis). ++ */ ++ public static final Property AUTH_SOCKET_TIMEOUT ++ = Property.duration("ssh-agent-server-proxy-auth-socket-timeout", Duration.ofHours(1)); ++ ++ public static final int DEFAULT_FORWARDER_BUF_SIZE = 1024; ++ public static final int MIN_FORWARDER_BUF_SIZE = 127; ++ public static final int MAX_FORWARDER_BUF_SIZE = 32767; ++ ++ /** ++ * Property that can be set on the factory manager in order to control the buffer size used to forward data from the ++ * established channel ++ * ++ * @see #MIN_FORWARDER_BUF_SIZE ++ * @see #MAX_FORWARDER_BUF_SIZE ++ * @see #DEFAULT_FORWARDER_BUF_SIZE ++ */ ++ public static final Property FORWARDER_BUFFER_SIZE ++ = Property.integer("channel-agent-fwd-buf-size", DEFAULT_FORWARDER_BUF_SIZE); ++ /** ++ * Ordered comma separated list of authentications methods. Authentications methods accepted by the server will be ++ * tried in the given order. If not configured or {@code null}/empty, then the session's ++ * {@link org.apache.sshd.client.ClientAuthenticationManager#getUserAuthFactories()} is used as-is ++ */ ++ public static final Property PREFERRED_AUTHS ++ = Property.string("preferred-auths"); ++ ++ /** ++ * Specifies the number of interactive prompts before giving up. The argument to this keyword must be an integer. ++ */ ++ public static final Property PASSWORD_PROMPTS ++ = Property.integer("password-prompts", 3); ++ ++ /** ++ * Key used to retrieve the value of the client 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 CLIENT_IDENTIFICATION ++ = Property.string("client-identification"); ++ ++ /** ++ * Whether to send the identification string immediately upon session connection being established or wait for the ++ * server's identification before sending our own. ++ * ++ * @see RFC 4253 - section 4.2 - Protocol Version ++ * Exchange ++ */ ++ public static final Property SEND_IMMEDIATE_IDENTIFICATION ++ = Property.bool("send-immediate-identification", true); ++ /** ++ * Whether to send {@code SSH_MSG_KEXINIT} immediately after sending the client identification string or wait until ++ * the severer's one has been received. ++ * ++ * @see #SEND_IMMEDIATE_IDENTIFICATION ++ */ ++ public static final Property SEND_IMMEDIATE_KEXINIT ++ = Property.bool("send-immediate-kex-init", true); ++ ++ /** ++ * Key used to set the heartbeat interval in milliseconds (0 to disable = default) ++ */ ++ public static final Property HEARTBEAT_INTERVAL ++ = Property.duration("heartbeat-interval", Duration.ZERO); ++ ++ /** ++ * Key used to check the heartbeat request that should be sent to the server ++ */ ++ public static final Property HEARTBEAT_REQUEST ++ = Property.string("heartbeat-request", "keepalive@sshd.apache.org"); ++ ++ /** ++ * Key used to indicate that the heartbeat request is also expecting a reply - time in milliseconds to wait ++ * for the reply. If non-positive then no reply is expected (nor requested). ++ */ ++ public static final Property HEARTBEAT_REPLY_WAIT ++ = Property.durationSec("heartbeat-reply-wait", Duration.ZERO); ++ ++ /** ++ * Whether to ignore invalid identities files when pre-initializing the client session ++ * ++ * @see ClientIdentityLoader#isValidLocation(org.apache.sshd.common.NamedResource) ++ */ ++ public static final Property IGNORE_INVALID_IDENTITIES ++ = Property.bool("ignore-invalid-identities", true); ++ ++ /** ++ * Defines if we should abort in case we encounter an invalid (e.g. expired) openssh certificate. ++ */ ++ public static final Property ABORT_ON_INVALID_CERTIFICATE ++ = Property.bool("abort-on-invalid-certificate", false); ++ ++ /** ++ * As per RFC-4256: ++ * ++ * The language tag is deprecated and SHOULD be the empty string. It may be removed in a future revision of this ++ * specification. Instead, the server SHOULD select the language to be used based on the tags communicated during ++ * key exchange ++ */ ++ public static final Property INTERACTIVE_LANGUAGE_TAG ++ = Property.string("kb-client-interactive-language-tag", ""); ++ ++ /** ++ * As per RFC-4256: ++ * ++ * The submethods field is included so the user can give a hint of which actual methods he wants to use. It is a ++ * comma-separated list of authentication submethods (software or hardware) that the user prefers. If the client has ++ * knowledge of the submethods preferred by the user, presumably through a configuration setting, it MAY use the ++ * submethods field to pass this information to the server. Otherwise, it MUST send the empty string. ++ * ++ * The actual names of the submethods is something the user and the server need to agree upon. ++ * ++ * Server interpretation of the submethods field is implementation- dependent. ++ */ ++ public static final Property INTERACTIVE_SUBMETHODS ++ = Property.string("kb-client-interactive-sub-methods", ""); ++ ++ /** ++ * Configure whether reply for the "exec" request is required ++ */ ++ public static final Property REQUEST_EXEC_REPLY ++ = Property.bool("channel-exec-want-reply", false); ++ ++ /** ++ * On some platforms, a call to {@ode System.in.read(new byte[65536], 0, 32768)} always throws an ++ * {@link IOException}. So we need to protect against that and chunk the call into smaller calls. This problem was ++ * found on Windows, JDK 1.6.0_03-b05. ++ */ ++ public static final Property INPUT_STREAM_PUMP_CHUNK_SIZE ++ = Property.integer("stdin-pump-chunk-size", 1024); ++ ++ /** ++ * Configure whether reply for the "shell" request is required ++ */ ++ public static final Property REQUEST_SHELL_REPLY ++ = Property.bool("channel-shell-want-reply", false); ++ ++ /** ++ * Configure whether reply for the "subsystem&quoot; request is required ++ * ++ *

++ * Default value for {@link #REQUEST_SUBSYSTEM_REPLY} - according to ++ * RFC4254 section 6.5: ++ *

++ *

++ * It is RECOMMENDED that the reply to these messages be requested and checked. ++ *

++ */ ++ public static final Property REQUEST_SUBSYSTEM_REPLY ++ = Property.bool("channel-subsystem-want-reply", true); ++ ++ public static final Property PROP_DHGEX_CLIENT_MIN_KEY ++ = Property.integer("dhgex-client-min"); ++ ++ public static final Property PROP_DHGEX_CLIENT_MAX_KEY ++ = Property.integer("dhgex-client-max"); ++ ++ public static final Property PROP_DHGEX_CLIENT_PRF_KEY ++ = Property.integer("dhgex-client-prf"); ++ /** ++ * Key used to retrieve the value of the channel window size in the configuration properties map. ++ */ ++ public static final Property WINDOW_SIZE ++ = Property.long_("window-size", 0x200000L); ++ ++ /** ++ * Key used to retrieve timeout (msec.) to wait for data to become available when reading from a channel. If not set ++ * or non-positive then infinite value is assumed ++ */ ++ public static final Property WINDOW_TIMEOUT ++ = Property.duration("window-timeout", Duration.ZERO); ++ ++ /** ++ * Key used to retrieve the value of the maximum packet size in the configuration properties map. ++ */ ++ public static final Property MAX_PACKET_SIZE ++ = Property.long_("packet-size", 0x8000L); ++ ++ /** ++ * A safety value that is designed to avoid an attack that uses large channel packet sizes ++ */ ++ public static final Property LIMIT_PACKET_SIZE ++ = Property.long_("max-packet-size", Integer.MAX_VALUE / 4L); ++ ++ /** ++ * Number of NIO worker threads to use. ++ */ ++ public static final Property NIO_WORKERS ++ = Property.validating(Property.integer("nio-workers", Runtime.getRuntime().availableProcessors() + 1), ++ w -> ValidateUtils.checkTrue(w > 0, "Number of NIO workers must be positive: %d", w)); ++ /** ++ * Key used to retrieve the value of the timeout after which it will close the connection if the other side has not ++ * been authenticated - in milliseconds. ++ */ ++ public static final Property AUTH_TIMEOUT ++ = Property.duration("auth-timeout", Duration.ofMinutes(2)); ++ ++ /** ++ * Key used to retrieve the value of idle timeout after which it will close the connection - in milliseconds. ++ */ ++ public static final Property IDLE_TIMEOUT ++ = Property.duration("idle-timeout", Duration.ofMinutes(10)); ++ ++ /** ++ * Key used to retrieve the value of the socket read timeout for NIO2 session implementation - in milliseconds. ++ */ ++ public static final Property NIO2_READ_TIMEOUT ++ = Property.duration("nio2-read-timeout", IDLE_TIMEOUT.getRequiredDefault().plus(Duration.ofSeconds(15L))); ++ ++ /** ++ * Minimum NIO2 write wait timeout for a single outgoing packet - in milliseconds ++ */ ++ public static final Property NIO2_MIN_WRITE_TIMEOUT ++ = Property.duration("nio2-min-write-timeout", Duration.ofSeconds(30L)); ++ ++ /** ++ * Key used to retrieve the value of the disconnect timeout which is used when a disconnection is attempted. If the ++ * disconnect message has not been sent before the timeout, the underlying socket will be forcibly closed - in ++ * milliseconds. ++ */ ++ public static final Property DISCONNECT_TIMEOUT ++ = Property.duration("disconnect-timeout", Duration.ofSeconds(10)); ++ ++ /** ++ * Key used to configure the timeout used when writing a close request on a channel. If the message can not be ++ * written before the specified timeout elapses, the channel will be immediately closed. In milliseconds. ++ */ ++ public static final Property CHANNEL_CLOSE_TIMEOUT ++ = Property.duration("channel-close-timeout", Duration.ofSeconds(5)); ++ ++ /** ++ * Timeout (milliseconds) to wait for client / server stop request if immediate stop requested. ++ */ ++ public static final Property STOP_WAIT_TIME ++ = Property.duration("stop-wait-time", Duration.ofMinutes(1)); ++ ++ /** ++ * Socket backlog. See {@link java.nio.channels.AsynchronousServerSocketChannel#bind(java.net.SocketAddress, int)} ++ */ ++ public static final Property SOCKET_BACKLOG ++ = Property.integer("socket-backlog", 0); ++ ++ /** ++ * Socket keep-alive. See {@link java.net.StandardSocketOptions#SO_KEEPALIVE} ++ */ ++ public static final Property SOCKET_KEEPALIVE ++ = Property.bool("socket-keepalive", false); ++ ++ /** ++ * Socket send buffer size. See {@link java.net.StandardSocketOptions#SO_SNDBUF} ++ */ ++ public static final Property SOCKET_SNDBUF ++ = Property.integer("socket-sndbuf"); ++ ++ /** ++ * Socket receive buffer size. See {@link java.net.StandardSocketOptions#SO_RCVBUF} ++ */ ++ public static final Property SOCKET_RCVBUF ++ = Property.integer("socket-rcvbuf"); ++ ++ /** ++ * Socket reuse address. See {@link java.net.StandardSocketOptions#SO_REUSEADDR} ++ */ ++ public static final Property SOCKET_REUSEADDR ++ = Property.bool("socket-reuseaddr", true); ++ /** ++ * Socket linger. See {@link java.net.StandardSocketOptions#SO_LINGER} ++ */ ++ public static final Property SOCKET_LINGER ++ = Property.integer("socket-linger", -1); ++ ++ /** ++ * Socket tcp no-delay. See {@link java.net.StandardSocketOptions#TCP_NODELAY} ++ */ ++ public static final Property TCP_NODELAY ++ = Property.bool("tcp-nodelay", false); ++ ++ /** ++ * Read buffer size for NIO2 sessions See {@link org.apache.sshd.common.io.nio2.Nio2Session} ++ */ ++ public static final Property NIO2_READ_BUFFER_SIZE ++ = Property.integer("nio2-read-buf-size", 32 * 1024); ++ ++ /** ++ * Maximum allowed size of the initial identification text sent during the handshake ++ */ ++ public static final Property MAX_IDENTIFICATION_SIZE ++ = Property.integer("max-identification-size", 16 * 1024); ++ ++ /** ++ * Key re-exchange will be automatically performed after the session has sent or received the given amount of bytes. ++ * If non-positive, then disabled. ++ */ ++ public static final Property REKEY_BYTES_LIMIT ++ = Property.long_("rekey-bytes-limit", 1024L * 1024L * 1024L); // 1GB ++ ++ /** ++ * Key re-exchange will be automatically performed after the specified amount of time has elapsed since the last key ++ * exchange - in milliseconds. If non-positive then disabled. ++ * ++ * @see RFC4253 section 9 ++ */ ++ public static final Property REKEY_TIME_LIMIT ++ = Property.duration("rekey-time-limit", Duration.ofHours(1)); ++ ++ /** ++ * Key re-exchange will be automatically performed after the specified number of packets has been exchanged - ++ * positive 64-bit value. If non-positive then disabled. ++ * ++ * @see RFC4344 section 3.1 ++ */ ++ public static final Property REKEY_PACKETS_LIMIT ++ = Property.long_("rekey-packets-limit", 1L << 31); ++ ++ /** ++ * Key re-exchange will be automatically performed after the specified number of cipher blocks has been processed - ++ * positive 64-bit value. If non-positive then disabled. The default is calculated according to ++ * RFC4344 section 3.2 ++ */ ++ public static final Property REKEY_BLOCKS_LIMIT ++ = Property.long_("rekey-blocks-limit", 0L); ++ ++ /** ++ * Average number of packets to be skipped before an {@code SSH_MSG_IGNORE} message is inserted in the stream. If ++ * non-positive, then feature is disabled ++ * ++ * @see #IGNORE_MESSAGE_VARIANCE ++ * @see RFC4251 section 9.3.1 ++ */ ++ public static final Property IGNORE_MESSAGE_FREQUENCY ++ = Property.long_("ignore-message-frequency", 1024L); ++ ++ /** ++ * The variance to be used around the configured {@link #IGNORE_MESSAGE_FREQUENCY} value in order to avoid insertion ++ * at a set frequency. If zero, then exact frequency is used. If negative, then the absolute value is ++ * used. If greater or equal to the frequency, then assumed to be zero - i.e., no variance ++ * ++ * @see RFC4251 section 9.3.1 ++ */ ++ public static final Property IGNORE_MESSAGE_VARIANCE ++ = Property.integer("ignore-message-variance", 32); ++ ++ /** ++ * Minimum size of {@code SSH_MSG_IGNORE} payload to send if feature enabled. If non-positive then no message is ++ * sent. Otherwise, the actual size is between this size and twice its value ++ * ++ * @see RFC4251 section 9.3.1 ++ */ ++ public static final Property IGNORE_MESSAGE_SIZE ++ = Property.integer("ignore-message-size", 16); ++ ++ /** ++ * The request type of agent forwarding. The value may be {@value #AGENT_FORWARDING_TYPE_IETF} or ++ * {@value #AGENT_FORWARDING_TYPE_OPENSSH}. ++ */ ++ public static final String AGENT_FORWARDING_TYPE = "agent-fw-auth-type"; ++ ++ /** ++ * The agent forwarding type defined by IETF (https://tools.ietf.org/html/draft-ietf-secsh-agent-02). ++ */ ++ public static final String AGENT_FORWARDING_TYPE_IETF = "auth-agent-req"; ++ ++ /** ++ * The agent forwarding type defined by OpenSSH. ++ */ ++ public static final String AGENT_FORWARDING_TYPE_OPENSSH = "auth-agent-req@openssh.com"; ++ ++ /** ++ * Configure max. wait time (millis) to wait for space to become available ++ */ ++ public static final Property WAIT_FOR_SPACE_TIMEOUT ++ = Property.duration("channel-output-wait-for-space-timeout", Duration.ofSeconds(30L)); ++ ++ /** ++ * Used to configure the timeout (milliseconds) for receiving a response for the forwarding request ++ */ ++ public static final Property FORWARD_REQUEST_TIMEOUT ++ = Property.duration("tcpip-forward-request-timeout", Duration.ofSeconds(15L)); ++ ++ /** ++ * Property that can be used to configure max. allowed concurrent active channels ++ * ++ * @see org.apache.sshd.common.session.ConnectionService#registerChannel(Channel) ++ */ ++ public static final Property MAX_CONCURRENT_CHANNELS ++ = Property.integer("max-sshd-channels", Integer.MAX_VALUE); ++ ++ /** ++ * RFC4254 does not clearly specify how to handle {@code SSH_MSG_CHANNEL_DATA} and ++ * {@code SSH_MSG_CHANNEL_EXTENDED_DATA} received through an unknown channel. Therefore, we provide a configurable ++ * approach to it with the default set to ignore it. ++ */ ++ public static final Property SEND_REPLY_FOR_CHANNEL_DATA ++ = Property.bool("send-unknown-channel-data-reply", false); ++ ++ /** ++ * Key used to retrieve the value in the configuration properties map of the maximum number of failed authentication ++ * requests before the server closes the connection. ++ */ ++ public static final Property MAX_AUTH_REQUESTS ++ = Property.integer("max-auth-requests", 20); ++ ++ /** ++ * Key used to retrieve the value of welcome banner that will be displayed when a user connects to the server. If ++ * {@code null}/empty then no banner will be sent. The value can be one of the following: ++ *
    ++ *

    ++ *

  • A {@link java.io.File} or {@link java.nio.file.Path}, in which case its contents will be transmitted. ++ * Note: if the file is empty or does not exits, no banner will be transmitted.
  • ++ *

    ++ * ++ *

    ++ *

  • 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