SslHandler should fail handshake / close promise and notify pipeline on removal (#10161)

Motivation:

If the SslHandler is removed from the pipeline we also need to ensure we fail the handshake / close promise if it was not notified before as otherwise we may never do so.

Modifications:

- Correctly fail promise and notify pipeline if handshake was not done yet when the SslHandler is removed
- Add unit test

Result:

Fix https://github.com/netty/netty/issues/10158
This commit is contained in:
Norman Maurer 2020-04-03 08:45:48 +02:00 committed by GitHub
parent db39b10249
commit 21f375fa90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 3 deletions

View File

@ -68,6 +68,7 @@ import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status; import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import static io.netty.buffer.ByteBufUtil.ensureWritableSuccess; import static io.netty.buffer.ByteBufUtil.ensureWritableSuccess;
@ -691,6 +692,24 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
new ChannelException("Pending write on removal of SslHandler")); new ChannelException("Pending write on removal of SslHandler"));
} }
pendingUnencryptedWrites = null; pendingUnencryptedWrites = null;
SSLHandshakeException cause = null;
// If the handshake is not done yet we should fail the handshake promise and notify the rest of the
// pipeline.
if (!handshakePromise.isDone()) {
cause = new SSLHandshakeException("SslHandler removed before handshake completed");
if (handshakePromise.tryFailure(cause)) {
ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(cause));
}
}
if (!sslClosePromise.isDone()) {
if (cause == null) {
cause = new SSLHandshakeException("SslHandler removed before handshake completed");
}
notifyClosePromise(cause);
}
if (engine instanceof ReferenceCounted) { if (engine instanceof ReferenceCounted) {
((ReferenceCounted) engine).release(); ((ReferenceCounted) engine).release();
} }

View File

@ -67,7 +67,6 @@ import org.junit.Test;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -85,12 +84,10 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509ExtendedTrustManager;
import static io.netty.buffer.Unpooled.wrappedBuffer; import static io.netty.buffer.Unpooled.wrappedBuffer;
@ -255,6 +252,43 @@ public class SslHandlerTest {
} }
} }
@Test(timeout = 5000L)
public void testHandshakeAndClosePromiseFailedOnRemoval() throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
engine.setUseClientMode(true);
SslHandler handler = new SslHandler(engine);
final AtomicReference<Throwable> handshakeRef = new AtomicReference<Throwable>();
final AtomicReference<Throwable> closeRef = new AtomicReference<Throwable>();
EmbeddedChannel ch = new EmbeddedChannel(handler, new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof SslHandshakeCompletionEvent) {
handshakeRef.set(((SslHandshakeCompletionEvent) evt).cause());
} else if (evt instanceof SslCloseCompletionEvent) {
closeRef.set(((SslCloseCompletionEvent) evt).cause());
}
}
});
assertFalse(handler.handshakeFuture().isDone());
assertFalse(handler.sslCloseFuture().isDone());
ch.pipeline().remove(handler);
try {
while (!handler.handshakeFuture().isDone() || handshakeRef.get() == null
|| !handler.sslCloseFuture().isDone() || closeRef.get() == null) {
Thread.sleep(10);
// Continue running all pending tasks until we notified for everything.
ch.runPendingTasks();
}
assertSame(handler.handshakeFuture().cause(), handshakeRef.get());
assertSame(handler.sslCloseFuture().cause(), closeRef.get());
} finally {
ch.finishAndReleaseAll();
}
}
@Test @Test
public void testTruncatedPacket() throws Exception { public void testTruncatedPacket() throws Exception {
SSLEngine engine = newServerModeSSLEngine(); SSLEngine engine = newServerModeSSLEngine();