From 2e633a4e86fb167b7a418e15d5cdc80b0dc0528c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Oct 2019 19:41:52 +0200 Subject: [PATCH 1/3] Fixes #4217 - SslConnection.DecryptedEnpoint.flush eternal busy loop. Releasing the encrypted output buffer so that it can be re-acquired with an expanded capacity. Signed-off-by: Simone Bordet --- .../jetty/client/HttpClientTLSTest.java | 79 +++++++++++++++++++ .../eclipse/jetty/io/ssl/SslConnection.java | 10 ++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index d75718c4c1f6..41333e33edcc 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -32,6 +32,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; @@ -48,6 +49,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslHandshakeListener; @@ -73,6 +75,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -807,4 +810,80 @@ protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuff Throwable cause = failure.getCause(); assertThat(cause, Matchers.instanceOf(SSLHandshakeException.class)); } + + @Test + public void testTLSLargeFragments() throws Exception + { + // The SSLEngine implementation may read a TLS packet that is larger than the default size, + // because the other peer uses large TLS fragments. + // When this happens, the SSLEngine implementation expands the SSLSession.packetBufferSize + // _during_ the TLS handshake, which causes a BUFFER_OVERFLOW result when trying to wrap(). + + SslContextFactory serverTLSFactory = createServerSslContextFactory(); + startServer(serverTLSFactory, new EmptyServerHandler()); + + SslContextFactory clientTLSFactory = createClientSslContextFactory(); + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + client = new HttpClient(clientTLSFactory) + { + @Override + protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) + { + if (sslContextFactory == null) + sslContextFactory = getSslContextFactory(); + return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory) + { + @Override + protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) + { + AtomicInteger outputHash = new AtomicInteger(); + byteBufferPool = new MappedByteBufferPool() + { + @Override + public void release(ByteBuffer buffer) + { + // Discard the TLS buffer so that it cannot be re-acquired. + if (outputHash.get() != System.identityHashCode(buffer)) + super.release(buffer); + } + }; + return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) + { + private int wrapCount; + + @Override + protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuffer output) throws SSLException + { + if (++wrapCount == 1) + { + outputHash.set(System.identityHashCode(output)); + // Replace the output buffer with one that will cause BUFFER_OVERFLOW. + output = ByteBuffer.allocate(1); + SSLEngineResult result = super.wrap(sslEngine, input, output); + assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); + return result; + } + else + { + // Make sure that if there was a BUFFER_OVERFLOW, we re-acquire + // the output buffer with potentially a different capacity due + // to the buffer expansion performed by the TLS implementation. + assertNotEquals(outputHash, System.identityHashCode(output)); + return super.wrap(sslEngine, input, output); + } + } + }; + } + }; + } + }; + client.setExecutor(clientThreads); + client.start(); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .send(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 9763ba7b98af..851c14bbf044 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -983,7 +983,8 @@ public boolean flush(ByteBuffer... appOuts) throws IOException LOG.debug("flush starting handshake {}", SslConnection.this); } - // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer + // We call sslEngine.wrap to try to take bytes from appOuts + // buffers and encrypt them into the _encryptedOutput buffer. BufferUtil.compact(_encryptedOutput); int pos = BufferUtil.flipToFill(_encryptedOutput); SSLEngineResult wrapResult; @@ -1032,6 +1033,13 @@ public boolean flush(ByteBuffer... appOuts) throws IOException case BUFFER_OVERFLOW: if (!flushed) return result = false; + // It's possible that SSLSession.packetBufferSize has been expanded + // during the TLS handshake. Wrapping a large application buffer + // causes BUFFER_OVERFLOW because the (old) packetBufferSize is + // too small. Release the encrypted output buffer so that it will + // be re-acquired with the larger capacity. + // See also system property "jsse.SSLEngine.acceptLargeFragments". + releaseEncryptedOutputBuffer(); continue; case OK: From 991cf20cce21171f6c4315bfea5379d5f6a2f486 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 18 Oct 2019 19:48:14 +0200 Subject: [PATCH 2/3] Issue #4217 - SslConnection.DecryptedEndpoint.flush eternal busy loop. Releasing the decrypted input buffer so that it can be re-acquired with an expanded capacity. Looping around only if the buffer size has changed. Signed-off-by: Simone Bordet --- .../jetty/client/HttpClientTLSTest.java | 79 ------------- .../eclipse/jetty/io/ssl/SslConnection.java | 106 ++++++++++++++---- 2 files changed, 82 insertions(+), 103 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index 41333e33edcc..d75718c4c1f6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -32,7 +32,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; @@ -49,7 +48,6 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslHandshakeListener; @@ -75,7 +73,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -810,80 +807,4 @@ protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuff Throwable cause = failure.getCause(); assertThat(cause, Matchers.instanceOf(SSLHandshakeException.class)); } - - @Test - public void testTLSLargeFragments() throws Exception - { - // The SSLEngine implementation may read a TLS packet that is larger than the default size, - // because the other peer uses large TLS fragments. - // When this happens, the SSLEngine implementation expands the SSLSession.packetBufferSize - // _during_ the TLS handshake, which causes a BUFFER_OVERFLOW result when trying to wrap(). - - SslContextFactory serverTLSFactory = createServerSslContextFactory(); - startServer(serverTLSFactory, new EmptyServerHandler()); - - SslContextFactory clientTLSFactory = createClientSslContextFactory(); - QueuedThreadPool clientThreads = new QueuedThreadPool(); - clientThreads.setName("client"); - client = new HttpClient(clientTLSFactory) - { - @Override - protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) - { - if (sslContextFactory == null) - sslContextFactory = getSslContextFactory(); - return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory) - { - @Override - protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) - { - AtomicInteger outputHash = new AtomicInteger(); - byteBufferPool = new MappedByteBufferPool() - { - @Override - public void release(ByteBuffer buffer) - { - // Discard the TLS buffer so that it cannot be re-acquired. - if (outputHash.get() != System.identityHashCode(buffer)) - super.release(buffer); - } - }; - return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) - { - private int wrapCount; - - @Override - protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuffer output) throws SSLException - { - if (++wrapCount == 1) - { - outputHash.set(System.identityHashCode(output)); - // Replace the output buffer with one that will cause BUFFER_OVERFLOW. - output = ByteBuffer.allocate(1); - SSLEngineResult result = super.wrap(sslEngine, input, output); - assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); - return result; - } - else - { - // Make sure that if there was a BUFFER_OVERFLOW, we re-acquire - // the output buffer with potentially a different capacity due - // to the buffer expansion performed by the TLS implementation. - assertNotEquals(outputHash, System.identityHashCode(output)); - return super.wrap(sslEngine, input, output); - } - } - }; - } - }; - } - }; - client.setExecutor(clientThreads); - client.start(); - - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) - .scheme(HttpScheme.HTTPS.asString()) - .send(); - assertEquals(HttpStatus.OK_200, response.getStatus()); - } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 851c14bbf044..a98475ba4eb0 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -31,6 +31,7 @@ import javax.net.ssl.SSLEngineResult.Status; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractEndPoint; @@ -308,10 +309,38 @@ private boolean isHandshakeComplete() return state == HandshakeState.SUCCEEDED || state == HandshakeState.FAILED; } + private int getApplicationBufferSize() + { + SSLSession hsSession = _sslEngine.getHandshakeSession(); + SSLSession session = _sslEngine.getSession(); + int size = session.getApplicationBufferSize(); + if (hsSession == null) + return size; + int hsSize = hsSession.getApplicationBufferSize(); + return Math.max(hsSize, size); + } + + private int getPacketBufferSize() + { + SSLSession hsSession = _sslEngine.getHandshakeSession(); + SSLSession session = _sslEngine.getSession(); + int size = session.getPacketBufferSize(); + if (hsSession == null) + return size; + int hsSize = hsSession.getPacketBufferSize(); + return Math.max(hsSize, size); + } + private void acquireEncryptedInput() { if (_encryptedInput == null) - _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers); + _encryptedInput = _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers); + } + + private void acquireEncryptedOutput() + { + if (_encryptedOutput == null) + _encryptedOutput = _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers); } @Override @@ -409,6 +438,24 @@ public String toConnectionString() connection instanceof AbstractConnection ? ((AbstractConnection)connection).toConnectionString() : connection); } + private void releaseEncryptedInputBuffer() + { + if (_encryptedInput != null && !_encryptedInput.hasRemaining()) + { + _bufferPool.release(_encryptedInput); + _encryptedInput = null; + } + } + + protected void releaseDecryptedInputBuffer() + { + if (_decryptedInput != null && !_decryptedInput.hasRemaining()) + { + _bufferPool.release(_decryptedInput); + _decryptedInput = null; + } + } + private void releaseEncryptedOutputBuffer() { if (!Thread.holdsLock(_decryptedEndPoint)) @@ -544,9 +591,12 @@ public void setConnection(Connection connection) { if (connection instanceof AbstractConnection) { - AbstractConnection a = (AbstractConnection)connection; - if (a.getInputBufferSize() < _sslEngine.getSession().getApplicationBufferSize()) - a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize()); + // This is an optimization to avoid that upper layer connections use small + // buffers and we need to copy decrypted data rather than decrypting in place. + AbstractConnection c = (AbstractConnection)connection; + int appBufferSize = getApplicationBufferSize(); + if (c.getInputBufferSize() < appBufferSize) + c.setInputBufferSize(appBufferSize); } super.setConnection(connection); } @@ -613,12 +663,13 @@ public int fill(ByteBuffer buffer) throws IOException // can we use the passed buffer if it is big enough ByteBuffer appIn; + int appBufferSize = getApplicationBufferSize(); if (_decryptedInput == null) { - if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize()) + if (BufferUtil.space(buffer) > appBufferSize) appIn = buffer; else - appIn = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers); + appIn = _decryptedInput = _bufferPool.acquire(appBufferSize, _decryptedDirectBuffers); } else { @@ -698,8 +749,21 @@ public int fill(ByteBuffer buffer) throws IOException } return filled = netFilled; + case BUFFER_OVERFLOW: + // It's possible that SSLSession.applicationBufferSize has been expanded + // by the SSLEngine implementation. Unwrapping a large encrypted buffer + // causes BUFFER_OVERFLOW because the (old) applicationBufferSize is + // too small. Release the decrypted input buffer so it will be re-acquired + // with the larger capacity. + // See also system property "jsse.SSLEngine.acceptLargeFragments". + if (BufferUtil.isEmpty(_decryptedInput) && appBufferSize < getApplicationBufferSize()) + { + releaseDecryptedInputBuffer(); + continue; + } + throw new IllegalStateException("Unexpected unwrap result " + unwrap); + case OK: - { if (unwrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED) handshakeSucceeded(); @@ -717,7 +781,6 @@ public int fill(ByteBuffer buffer) throws IOException } break; - } default: throw new IllegalStateException("Unexpected unwrap result " + unwrap); @@ -737,17 +800,8 @@ public int fill(ByteBuffer buffer) throws IOException } finally { - if (_encryptedInput != null && !_encryptedInput.hasRemaining()) - { - _bufferPool.release(_encryptedInput); - _encryptedInput = null; - } - - if (_decryptedInput != null && !_decryptedInput.hasRemaining()) - { - _bufferPool.release(_decryptedInput); - _decryptedInput = null; - } + releaseEncryptedInputBuffer(); + releaseDecryptedInputBuffer(); if (_flushState == FlushState.WAIT_FOR_FILL) { @@ -974,8 +1028,8 @@ public boolean flush(ByteBuffer... appOuts) throws IOException throw new IllegalStateException("Unexpected HandshakeStatus " + status); } - if (_encryptedOutput == null) - _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers); + int packetBufferSize = getPacketBufferSize(); + acquireEncryptedOutput(); if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE)) { @@ -1034,13 +1088,17 @@ public boolean flush(ByteBuffer... appOuts) throws IOException if (!flushed) return result = false; // It's possible that SSLSession.packetBufferSize has been expanded - // during the TLS handshake. Wrapping a large application buffer + // by the SSLEngine implementation. Wrapping a large application buffer // causes BUFFER_OVERFLOW because the (old) packetBufferSize is // too small. Release the encrypted output buffer so that it will // be re-acquired with the larger capacity. // See also system property "jsse.SSLEngine.acceptLargeFragments". - releaseEncryptedOutputBuffer(); - continue; + if (packetBufferSize < getPacketBufferSize()) + { + releaseEncryptedOutputBuffer(); + continue; + } + throw new IllegalStateException("Unexpected wrap result " + wrap); case OK: if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED) From 73eb82c20f0622b9a4c8b92722a25faef9069ea8 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 19 Oct 2019 20:06:10 +0200 Subject: [PATCH 3/3] Issue #4217 - SslConnection.DecryptedEndpoint.flush eternal busy loop. Updates after review. Added test case. Signed-off-by: Simone Bordet --- .../jetty/client/HttpClientTLSTest.java | 116 ++++++++++++++++++ .../eclipse/jetty/io/ssl/SslConnection.java | 20 +-- 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index d75718c4c1f6..4d8d6f685d9b 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -807,4 +807,120 @@ protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuff Throwable cause = failure.getCause(); assertThat(cause, Matchers.instanceOf(SSLHandshakeException.class)); } + + @Test + public void testTLSLargeFragments() throws Exception + { + CountDownLatch serverLatch = new CountDownLatch(1); + SslContextFactory serverTLSFactory = createServerSslContextFactory(); + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.addCustomizer(new SecureRequestCustomizer()); + HttpConnectionFactory http = new HttpConnectionFactory(httpConfig); + SslConnectionFactory ssl = new SslConnectionFactory(serverTLSFactory, http.getProtocol()) + { + @Override + protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine) + { + return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) + { + @Override + protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException + { + int inputBytes = input.remaining(); + SSLEngineResult result = super.unwrap(sslEngine, input, output); + if (inputBytes == 5) + serverLatch.countDown(); + return result; + } + }; + } + }; + connector = new ServerConnector(server, 1, 1, ssl, http); + server.addConnector(connector); + server.setHandler(new EmptyServerHandler()); + server.start(); + + long idleTimeout = 2000; + + CountDownLatch clientLatch = new CountDownLatch(1); + SslContextFactory clientTLSFactory = createClientSslContextFactory(); + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + client = new HttpClient(clientTLSFactory) + { + @Override + protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) + { + if (sslContextFactory == null) + sslContextFactory = getSslContextFactory(); + return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory) + { + @Override + protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) + { + return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) + { + @Override + protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuffer output) throws SSLException + { + try + { + clientLatch.countDown(); + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + return super.wrap(sslEngine, input, output); + } + catch (InterruptedException x) + { + throw new SSLException(x); + } + } + }; + } + }; + } + }; + client.setIdleTimeout(idleTimeout); + client.setExecutor(clientThreads); + client.start(); + + String host = "localhost"; + int port = connector.getLocalPort(); + + CountDownLatch responseLatch = new CountDownLatch(1); + client.newRequest(host, port) + .scheme(HttpScheme.HTTPS.asString()) + .send(result -> + { + assertTrue(result.isSucceeded()); + assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + responseLatch.countDown(); + }); + // Wait for the TLS buffers to be acquired by the client, then the + // HTTP request will be paused waiting for the TLS buffer to be expanded. + assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + + // Send the large frame bytes that will enlarge the TLS buffers. + try (Socket socket = new Socket(host, port)) + { + OutputStream output = socket.getOutputStream(); + byte[] largeFrameBytes = new byte[5]; + largeFrameBytes[0] = 22; // Type = handshake + largeFrameBytes[1] = 3; // Major TLS version + largeFrameBytes[2] = 3; // Minor TLS version + // Frame length is 0x7FFF == 32767, i.e. a "large fragment". + // Maximum allowed by RFC 8446 is 16384, but SSLEngine supports up to 33093. + largeFrameBytes[3] = 0x7F; // Length hi byte + largeFrameBytes[4] = (byte)0xFF; // Length lo byte + output.write(largeFrameBytes); + output.flush(); + // Just close the connection now, the large frame + // length was enough to trigger the buffer expansion. + } + + // The HTTP request will resume and be forced to handle the TLS buffer expansion. + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index a98475ba4eb0..bb202a8f72b5 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.ToIntFunction; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -311,23 +312,22 @@ private boolean isHandshakeComplete() private int getApplicationBufferSize() { - SSLSession hsSession = _sslEngine.getHandshakeSession(); - SSLSession session = _sslEngine.getSession(); - int size = session.getApplicationBufferSize(); - if (hsSession == null) - return size; - int hsSize = hsSession.getApplicationBufferSize(); - return Math.max(hsSize, size); + return getBufferSize(SSLSession::getApplicationBufferSize); } private int getPacketBufferSize() + { + return getBufferSize(SSLSession::getPacketBufferSize); + } + + private int getBufferSize(ToIntFunction bufferSizeFn) { SSLSession hsSession = _sslEngine.getHandshakeSession(); SSLSession session = _sslEngine.getSession(); - int size = session.getPacketBufferSize(); - if (hsSession == null) + int size = bufferSizeFn.applyAsInt(session); + if (hsSession == null || hsSession == session) return size; - int hsSize = hsSession.getPacketBufferSize(); + int hsSize = bufferSizeFn.applyAsInt(hsSession); return Math.max(hsSize, size); }