Skip to content

Commit

Permalink
Merge pull request #4245 from eclipse/jetty-9.3.x-4217-sslconnection-…
Browse files Browse the repository at this point in the history
…flush-loop

Issue #4217 - (9.3.x) SslConnection DecryptedEndpoint flush eternal busy loop
  • Loading branch information
joakime authored Oct 31, 2019
2 parents abc92e5 + a3f3612 commit 6c69c39
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,47 @@
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;

import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class HttpClientTLSTest
{
private Server server;
Expand Down Expand Up @@ -516,4 +530,119 @@ protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnection
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}

@Test
public void testTLSLargeFragments() throws Exception
{
CountDownLatch serverLatch = new CountDownLatch(1);
SslContextFactory serverTLSFactory = createSslContextFactory();
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 = createSslContextFactory();
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
client = new HttpClient(clientTLSFactory)
{
@Override
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
SslContextFactory 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
Expand All @@ -41,10 +40,12 @@
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.toolchain.test.JDK;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -59,6 +60,8 @@ public class SslBytesClientTest extends SslBytesTest
@Before
public void init() throws Exception
{
// Disable for JDK 9 and above.
Assume.assumeFalse(JDK.IS_9);
threadPool = Executors.newCachedThreadPool();

client = new HttpClient(new SslContextFactory(true));
Expand Down
1 change: 1 addition & 0 deletions jetty-client/src/test/resources/jetty-logging.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
Loading

0 comments on commit 6c69c39

Please sign in to comment.