Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8216326

SSLSocket.getOutputStream().close() does not close the associated socket.

    XMLWordPrintable

    Details

    • Subcomponent:
    • Introduced In Version:
      11
    • CPU:
      x86_64
    • OS:
      generic

      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


        Attachments

          Activity

            People

            • Assignee:
              xuelei Xue-Lei Fan
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated: