Details
-
Bug
-
Resolution: Fixed
-
P2
-
11, 12
-
b17
-
x86_64
-
generic
-
Verified
Backports
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8223756 | 11.0.5-oracle | Sean Coffey | P2 | Closed | Fixed | b01 |
JDK-8227096 | 11.0.5 | Xuelei Fan | P2 | Resolved | Fixed | b01 |
JDK-8256875 | openjdk8u272 | Martin Balao Alonso | P2 | Closed | Fixed | |
JDK-8243745 | 8u261 | Prasadarao Koppula | P2 | Closed | Fixed | b05 |
JDK-8247083 | emb-8u261 | Prasadarao Koppula | P2 | Resolved | Fixed | team |
Description
ADDITIONAL SYSTEM INFORMATION :
OS: macOS 10.13.6 (17G4015).
JDKs with this bug (11):
- openjdk 11.0.1 2018-10-16, OpenJDK Runtime Environment 18.9 (build 11.0.1+13), OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode);
- openjdk 11 2018-09-25, OpenJDK Runtime Environment 18.9 (build 11+28), OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode).
A DESCRIPTION OF THE PROBLEM :
The method SSLSocket.getOutputStream() has the same specification as Socket.getOutputStream(), and the specification says: "Closing the returned OutputStream will close the associated socket" (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Socket.html#getOutputStream()). However, starting with JDK 11 this behaviour is broken for SSLSocket, and still works for Socket.
REGRESSION : Last worked in version 10.0.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Step 0.
It may be more convenient to get the description and the source code of the minimal working example here:
https://github.com/stIncMale/JDK11-SSLSocket.getOutputStream.close-bug
Otherwise, proceed with the following steps.
Step 1.
Create file "./keystore.jks" with the following command:
keytool -keystore ./keystore.jks -storepass "password" -storetype JKS -genkeypair -dname "CN=Unknown" -alias "myKey" -keypass "password" -keyalg RSA -validity 999999
Create file "./SslSocketOutputStreamCloseBug.java" by using the source code from "Source code for an executable test case".
Step 2.
Run the test with the following command:
java -Djavax.net.ssl.trustStore=./keystore.jks -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=./keystore.jks -Djavax.net.ssl.keyStorePassword=password ./SslSocketOutputStreamCloseBug.java
If you want to run it with any JDK before JDK 11 (e.g. JDK 10), use the following command:
javac ./SslSocketOutputStreamCloseBug.java && java -Djavax.net.ssl.trustStore=./keystore.jks -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=./keystore.jks -Djavax.net.ssl.keyStorePassword=password SslSocketOutputStreamCloseBug ; rm ./SslSocketOutputStreamCloseBug*.class
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program outputs among other messages "Client ð Closed the socket" and exits.
ACTUAL -
The program outputs among other messages "Client ð Failed to close the socket" and hangs infinitely printing "Client ð Still waiting for data from the server...".
I observed the expected result with JDK 10, 9, 8:
- openjdk version "10.0.2" 2018-07-17, OpenJDK Runtime Environment 18.3 (build 10.0.2+13), OpenJDK 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode);
- openjdk version "10.0.1" 2018-04-17, OpenJDK Runtime Environment (build 10.0.1+10), OpenJDK 64-Bit Server VM (build 10.0.1+10, mixed mode);
- openjdk version "10" 2018-03-20, OpenJDK Runtime Environment 18.3 (build 10+46), OpenJDK 64-Bit Server VM 18.3 (build 10+46, mixed mode);
- openjdk 9.0.4, OpenJDK Runtime Environment (build 9.0.4+11), OpenJDK 64-Bit Server VM (build 9.0.4+11, mixed mode);
- java version "1.8.0_162", Java(TM) SE Runtime Environment (build 1.8.0_162-b12), Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode).
---------- BEGIN SOURCE ----------
//file "./SslSocketOutputStreamCloseBug.java"
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
class SslSocketOutputStreamCloseBug {
public static void main(String... args) throws IOException, InterruptedException, ExecutionException {
boolean useTls = args.length == 0 || !args[0].equalsIgnoreCase("noTls");
SocketAddress serverSocketAddress = startServer(useTls);
try (Socket clientSideSocket = useTls ? SSLSocketFactory.getDefault().createSocket() : new Socket()) {
clientSideSocket.setSoTimeout(0);
logClient("Connecting to " + serverSocketAddress);
clientSideSocket.connect(serverSocketAddress, 0);
if (clientSideSocket instanceof SSLSocket) {
((SSLSocket)clientSideSocket).startHandshake();
}
logClient("Connected via the socket " + clientSideSocket);
InputStream clientSideInputStream = clientSideSocket.getInputStream();
Thread clientSideReadingThread = new Thread(() -> {
try {
logClient("Waiting for data from the server");
logClient("Received " + toHexOrEof(clientSideInputStream.read()));//wait for data from the server that never sends any
} catch (IOException e) {
//clientSideSocket was closed
} catch (RuntimeException e) {
e.printStackTrace(System.out);
} finally {
logClient("Stopped waiting for data from the server");
}
});
clientSideReadingThread.start();
clientSideReadingThread.join(500);//wait until clientSideReadingThread starts reading
logClient("Closing the socket");
clientSideSocket.getOutputStream().close();//must close clientSideSocket
if (clientSideSocket.isClosed()) {
logClient("Closed the socket");
} else {
logClient("Failed to close the socket");
}
do {//wait until clientSideReadingThread dies
clientSideReadingThread.join(1000);
if (clientSideReadingThread.isAlive()) {
logClient("Still waiting for data from the server...");
} else {
break;
}
} while (true);
} finally {
logClient("Disconnected");
}
}
private static SocketAddress startServer(boolean useTls) throws ExecutionException, InterruptedException {
CompletableFuture<SocketAddress> resultFuture = new CompletableFuture<>();
Thread serverSideThread = new Thread(() -> {
try (ServerSocket serverSocket = useTls ? SSLServerSocketFactory.getDefault().createServerSocket() : new ServerSocket()) {
SocketAddress serverSocketAddress = new InetSocketAddress("localhost", 0);
logServer("Starting on " + serverSocketAddress);
serverSocket.setSoTimeout(0);
serverSocket.bind(serverSocketAddress);
resultFuture.complete(new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort()));
logServer("Accepting connections on " + serverSocket);
Socket serverSideSocket = serverSocket.accept();
logServer("Accepted a connection from " + serverSideSocket.getRemoteSocketAddress());
logServer("Waiting for data from the client " + serverSideSocket.getRemoteSocketAddress());
logServer("Received " + toHexOrEof(serverSideSocket.getInputStream().read()));//wait for data from the client that never sends any
} catch (IOException | RuntimeException e) {
resultFuture.completeExceptionally(e);
} finally {
resultFuture.completeExceptionally(new RuntimeException());
logServer("Shut down");
}
});
serverSideThread.start();
return resultFuture.get();
}
private static void logServer(Object msg) {
System.out.println("Server (:) " + msg);
}
private static void logClient(Object msg) {
System.out.println("Client \uD83D\uDD0C " + msg);
}
private static String toHexOrEof(int baseTenByte) {
return baseTenByte == -1 ? "EOF" : String.format(Locale.ROOT, "0x%02X", baseTenByte);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
As a workaround in projects we can:
- Close an SSLSocket directly via SSLSocket.close().
- Use Socket :) At least Socket.getOutputStream().close() does not have this bug.
FREQUENCY : always
OS: macOS 10.13.6 (17G4015).
JDKs with this bug (11):
- openjdk 11.0.1 2018-10-16, OpenJDK Runtime Environment 18.9 (build 11.0.1+13), OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode);
- openjdk 11 2018-09-25, OpenJDK Runtime Environment 18.9 (build 11+28), OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode).
A DESCRIPTION OF THE PROBLEM :
The method SSLSocket.getOutputStream() has the same specification as Socket.getOutputStream(), and the specification says: "Closing the returned OutputStream will close the associated socket" (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/Socket.html#getOutputStream()). However, starting with JDK 11 this behaviour is broken for SSLSocket, and still works for Socket.
REGRESSION : Last worked in version 10.0.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Step 0.
It may be more convenient to get the description and the source code of the minimal working example here:
https://github.com/stIncMale/JDK11-SSLSocket.getOutputStream.close-bug
Otherwise, proceed with the following steps.
Step 1.
Create file "./keystore.jks" with the following command:
keytool -keystore ./keystore.jks -storepass "password" -storetype JKS -genkeypair -dname "CN=Unknown" -alias "myKey" -keypass "password" -keyalg RSA -validity 999999
Create file "./SslSocketOutputStreamCloseBug.java" by using the source code from "Source code for an executable test case".
Step 2.
Run the test with the following command:
java -Djavax.net.ssl.trustStore=./keystore.jks -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=./keystore.jks -Djavax.net.ssl.keyStorePassword=password ./SslSocketOutputStreamCloseBug.java
If you want to run it with any JDK before JDK 11 (e.g. JDK 10), use the following command:
javac ./SslSocketOutputStreamCloseBug.java && java -Djavax.net.ssl.trustStore=./keystore.jks -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=./keystore.jks -Djavax.net.ssl.keyStorePassword=password SslSocketOutputStreamCloseBug ; rm ./SslSocketOutputStreamCloseBug*.class
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program outputs among other messages "Client ð Closed the socket" and exits.
ACTUAL -
The program outputs among other messages "Client ð Failed to close the socket" and hangs infinitely printing "Client ð Still waiting for data from the server...".
I observed the expected result with JDK 10, 9, 8:
- openjdk version "10.0.2" 2018-07-17, OpenJDK Runtime Environment 18.3 (build 10.0.2+13), OpenJDK 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode);
- openjdk version "10.0.1" 2018-04-17, OpenJDK Runtime Environment (build 10.0.1+10), OpenJDK 64-Bit Server VM (build 10.0.1+10, mixed mode);
- openjdk version "10" 2018-03-20, OpenJDK Runtime Environment 18.3 (build 10+46), OpenJDK 64-Bit Server VM 18.3 (build 10+46, mixed mode);
- openjdk 9.0.4, OpenJDK Runtime Environment (build 9.0.4+11), OpenJDK 64-Bit Server VM (build 9.0.4+11, mixed mode);
- java version "1.8.0_162", Java(TM) SE Runtime Environment (build 1.8.0_162-b12), Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode).
---------- BEGIN SOURCE ----------
//file "./SslSocketOutputStreamCloseBug.java"
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
class SslSocketOutputStreamCloseBug {
public static void main(String... args) throws IOException, InterruptedException, ExecutionException {
boolean useTls = args.length == 0 || !args[0].equalsIgnoreCase("noTls");
SocketAddress serverSocketAddress = startServer(useTls);
try (Socket clientSideSocket = useTls ? SSLSocketFactory.getDefault().createSocket() : new Socket()) {
clientSideSocket.setSoTimeout(0);
logClient("Connecting to " + serverSocketAddress);
clientSideSocket.connect(serverSocketAddress, 0);
if (clientSideSocket instanceof SSLSocket) {
((SSLSocket)clientSideSocket).startHandshake();
}
logClient("Connected via the socket " + clientSideSocket);
InputStream clientSideInputStream = clientSideSocket.getInputStream();
Thread clientSideReadingThread = new Thread(() -> {
try {
logClient("Waiting for data from the server");
logClient("Received " + toHexOrEof(clientSideInputStream.read()));//wait for data from the server that never sends any
} catch (IOException e) {
//clientSideSocket was closed
} catch (RuntimeException e) {
e.printStackTrace(System.out);
} finally {
logClient("Stopped waiting for data from the server");
}
});
clientSideReadingThread.start();
clientSideReadingThread.join(500);//wait until clientSideReadingThread starts reading
logClient("Closing the socket");
clientSideSocket.getOutputStream().close();//must close clientSideSocket
if (clientSideSocket.isClosed()) {
logClient("Closed the socket");
} else {
logClient("Failed to close the socket");
}
do {//wait until clientSideReadingThread dies
clientSideReadingThread.join(1000);
if (clientSideReadingThread.isAlive()) {
logClient("Still waiting for data from the server...");
} else {
break;
}
} while (true);
} finally {
logClient("Disconnected");
}
}
private static SocketAddress startServer(boolean useTls) throws ExecutionException, InterruptedException {
CompletableFuture<SocketAddress> resultFuture = new CompletableFuture<>();
Thread serverSideThread = new Thread(() -> {
try (ServerSocket serverSocket = useTls ? SSLServerSocketFactory.getDefault().createServerSocket() : new ServerSocket()) {
SocketAddress serverSocketAddress = new InetSocketAddress("localhost", 0);
logServer("Starting on " + serverSocketAddress);
serverSocket.setSoTimeout(0);
serverSocket.bind(serverSocketAddress);
resultFuture.complete(new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort()));
logServer("Accepting connections on " + serverSocket);
Socket serverSideSocket = serverSocket.accept();
logServer("Accepted a connection from " + serverSideSocket.getRemoteSocketAddress());
logServer("Waiting for data from the client " + serverSideSocket.getRemoteSocketAddress());
logServer("Received " + toHexOrEof(serverSideSocket.getInputStream().read()));//wait for data from the client that never sends any
} catch (IOException | RuntimeException e) {
resultFuture.completeExceptionally(e);
} finally {
resultFuture.completeExceptionally(new RuntimeException());
logServer("Shut down");
}
});
serverSideThread.start();
return resultFuture.get();
}
private static void logServer(Object msg) {
System.out.println("Server (:) " + msg);
}
private static void logClient(Object msg) {
System.out.println("Client \uD83D\uDD0C " + msg);
}
private static String toHexOrEof(int baseTenByte) {
return baseTenByte == -1 ? "EOF" : String.format(Locale.ROOT, "0x%02X", baseTenByte);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
As a workaround in projects we can:
- Close an SSLSocket directly via SSLSocket.close().
- Use Socket :) At least Socket.getOutputStream().close() does not have this bug.
FREQUENCY : always
Attachments
Issue Links
- backported by
-
JDK-8227096 SSLSocket stream close() does not close the associated socket
- Resolved
-
JDK-8247083 SSLSocket stream close() does not close the associated socket
- Resolved
-
JDK-8223756 SSLSocket stream close() does not close the associated socket
- Closed
-
JDK-8243745 SSLSocket stream close() does not close the associated socket
- Closed
-
JDK-8256875 SSLSocket stream close() does not close the associated socket
- Closed