-
Bug
-
Resolution: Fixed
-
P2
-
11, 12
-
b24
-
x86_64
-
linux
-
Verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8215462 | 11.0.3-oracle | Sean Coffey | P2 | Closed | Fixed | b01 |
JDK-8219114 | 11.0.3 | Jamil Nimeh | P2 | Closed | Fixed | master |
JDK-8256869 | openjdk8u272 | Martin Balao Alonso | P2 | Closed | Fixed | b06 |
JDK-8243710 | 8u261 | Prasadarao Koppula | P2 | Closed | Fixed | b05 |
JDK-8247043 | emb-8u261 | Prasadarao Koppula | P2 | Resolved | Fixed | team |
Tested on Fedora 29 and Debian Jessie
With Java 11.0.1 (OpenJDK + Oracle)
A DESCRIPTION OF THE PROBLEM :
We came across a major Java 11 bug within the SSL session resumption/Server name indication code
causing StackOverflowErrors when using the built-in HTTPClient (or HttpURLConnection)
together with HTTPS and TLS version 1.2.
We could observe the problem on production systems which are making lots of
HTTPS service calls to clustered endpoints. Depending on the amount of requests
and the thread stack size, the StackOverflowErrors are showing up after a few days
uptime and we have to restart the JVMs.
I could track down the problem to the class sun.security.ssl.SSLSessionImpl,
where a list of requestedServerNames from the HandshakeContext is put into an unmodifiable list
again and again, when the same session is resumed, thus ending up with a nested
list exceeding the thread stack size, when accessed in
sun.security.ssl.ServerNameExtension$CHServerNameProducer.produce().
I attached some test code, which can reproduce the problem using raw sockets.
The bug seems to be new in Java 11 and only applying to TLS 1.2.
REGRESSION : Last worked in version 10
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Make thousands of HTTPS requests using HTTPClient/HttpURLConnections to a clustered server using TLSv1.2 (probably it's important that no session tickets are used and the endpoint is clustered, so that you get session id cache misses on the server and the client creates new ones).
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should just work.
ACTUAL -
Exception in thread "Thread-1" java.lang.StackOverflowError
at java.base/java.util.Collections$UnmodifiableCollection$1.<init>(Collections.java:1042)
at java.base/java.util.Collections$UnmodifiableCollection.iterator(Collections.java:1041)
...
at java.base/java.util.Collections$UnmodifiableCollection$1.<init>(Collections.java:1042)
at java.base/java.util.Collections$UnmodifiableCollection.iterator(Collections.java:1041)
at java.base/java.util.Collections$UnmodifiableCollection$1.<init>(Collections.java:1042)
at java.base/java.util.Collections$UnmodifiableCollection.iterator(Collections.java:1041)
at java.base/sun.security.ssl.ServerNameExtension$CHServerNameProducer.produce(ServerNameExtension.java:228)
at java.base/sun.security.ssl.SSLExtension.produce(SSLExtension.java:532)
at java.base/sun.security.ssl.SSLExtensions.produce(SSLExtensions.java:228)
at java.base/sun.security.ssl.ClientHello$ClientHelloKickstartProducer.produce(ClientHello.java:648)
at java.base/sun.security.ssl.SSLHandshake.kickstart(SSLHandshake.java:515)
at java.base/sun.security.ssl.ClientHandshakeContext.kickstart(ClientHandshakeContext.java:104)
at java.base/sun.security.ssl.TransportContext.kickstart(TransportContext.java:228)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:395)
at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:716)
at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:970)
at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:942)
---------- BEGIN SOURCE ----------
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
* Test showing StackOverflowError within
* sun.security.ssl.ServerNameExtension$CHServerNameProducer.produce().
*
* The test should be started with minimal thread stack size and increased
* stacktrace depth:
*
* <pre>
* java -Xss140k -XX:MaxJavaStackTraceDepth=2000 SNIBugTest
* </pre>
*
* Using these settings the test should fail after about 778 iterations.
*
* By default the test uses the hostname localhost and localhost.localdomain as
* SNI name. By default it creates a keystore with a self-signed certificate.
* You can change the host names and provide custom javax.net.ssl.XXX settings
* if you want to test with an alternative domain or another
* keystore/truststore.
*/
public class SNIBugTest {
static String hostName = "localhost";
static String sniHostName = "localhost.localdomain";
static int maxRequests = 1000;
static volatile int serverPort;
static AtomicInteger requestCount = new AtomicInteger();
public static void main(String[] args) throws Exception {
if (System.getProperty("javax.net.ssl.keystore") == null) {
createKeystore();
System.setProperty("javax.net.ssl.keyStore", "testkeystore");
System.setProperty("javax.net.ssl.keyStorePassword", "passphrase");
System.setProperty("javax.net.ssl.trustStore", "testkeystore");
System.setProperty("javax.net.ssl.trustStorePassword",
"passphrase");
}
ServerThread server = new ServerThread();
server.start();
while (serverPort == 0) {
Thread.sleep(100);
}
ClientThread client = new ClientThread();
client.start();
client.join();
server.interrupt();
}
static void createKeystore() throws Exception {
ProcessBuilder builder = new ProcessBuilder("keytool", "-genkey",
"-alias", "dummy", "-keyalg", "RSA", "-keysize", "2048",
"-sigalg", "SHA256withRSA", "-validity", "365", "-keypass",
"passphrase", "-keystore", "testkeystore", "-storepass",
"passphrase", "-dname", "CN=localhost.localdomain, OU=Dummy,"
+ " O=Dummy, L=Cupertino, ST=CA, C=US");
builder.redirectErrorStream(true);
Process process = builder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
static class ClientThread extends Thread {
@Override
public void run() {
SSLSocketFactory factory = (SSLSocketFactory)
SSLSocketFactory.getDefault();
for (int i = 0; i < maxRequests; i++) {
try (SSLSocket sslSocket = (SSLSocket) factory
.createSocket(hostName, serverPort)) {
SNIHostName serverName = new SNIHostName(sniHostName);
List<SNIServerName> serverNames = new ArrayList<>(1);
serverNames.add(serverName);
SSLParameters params = sslSocket.getSSLParameters();
params.setServerNames(serverNames);
sslSocket.setSSLParameters(params);
OutputStream out = sslSocket.getOutputStream();
InputStream in = sslSocket.getInputStream();
out.write(0);
in.read();
} catch (IOException x) {
x.printStackTrace();
}
}
}
}
static class ServerThread extends Thread {
@Override
public void run() {
SSLServerSocketFactory factory = (SSLServerSocketFactory)
SSLServerSocketFactory.getDefault();
try (SSLServerSocket serverSocket = (SSLServerSocket) factory
.createServerSocket(0)) {
serverPort = serverSocket.getLocalPort();
serverSocket.setSoTimeout(1000);
// force TLS version 1.2
serverSocket.setEnabledProtocols(new String[] { "TLSv1.2" });
while (!isInterrupted()) {
try (SSLSocket socket =
(SSLSocket) serverSocket.accept()) {
SNIMatcher matcher = SNIHostName
.createSNIMatcher(sniHostName);
Collection<SNIMatcher> matchers = new ArrayList<>(1);
matchers.add(matcher);
SSLParameters params = serverSocket.getSSLParameters();
params.setSNIMatchers(matchers);
System.out.println(requestCount.incrementAndGet());
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
int data = in.read();
out.write(data);
// simulate failed session lookup on clustered system
socket.getSession().invalidate();
} catch (SocketTimeoutException x) {
continue;
} catch (IOException x) {
x.printStackTrace();
}
}
} catch (IOException x) {
x.printStackTrace();
}
}
}
}
---------- END SOURCE ----------
FREQUENCY : always
- backported by
-
JDK-8247043 SSL session resumption/SNI with TLS1.2 causes StackOverflowError
-
- Resolved
-
-
JDK-8215462 SSL session resumption/SNI with TLS1.2 causes StackOverflowError
-
- Closed
-
-
JDK-8219114 SSL session resumption/SNI with TLS1.2 causes StackOverflowError
-
- Closed
-
-
JDK-8243710 SSL session resumption/SNI with TLS1.2 causes StackOverflowError
-
- Closed
-
-
JDK-8256869 SSL session resumption/SNI with TLS1.2 causes StackOverflowError
-
- Closed
-
- duplicates
-
JDK-8214102 TLS causes StackOverflowError when SSL session cache is invalidated by server
-
- Closed
-
- relates to
-
JDK-6323374 (coll) Optimize Collections.unmodifiable* and synchronized*
-
- Resolved
-
-
JDK-8312630 java/security should not create unmodifiable collections with redundant wrapping
-
- Closed
-