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

TLS Socket reads are truncated and fail with Connection Reset

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "1.8.0_111"
      Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
      Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

      java version "1.7.0_80"
      Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
      Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Linux dave-centos7 3.10.0-327.36.1.el7.x86_64 #1 SMP Sun Sep 18 13:04:29 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
      Linux dave-VirtualBox 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:51:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

      A DESCRIPTION OF THE PROBLEM :
      I have created a Server and Client using java.net Sockets. In addition, the sockets are using TLS/SSL to communicate. I have found that there is a scenario where the Server sends some data to the Client, but the Client is unable to read the data. Specifically, if the Server sends data and closes the connection, and it just so happens the Client sent some data that was not read by the Server, a TCP RST is sent. While the Client is reading the data, it will receive a Connection Reset without the opportunity of emptying the receive buffer. Without TLS/SSL, this works as expected. But once TLS/SSL are introduced, the problem appears.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Generate a self signed certificate:
      keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048

      2. Run the included Java program.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      I would expect the Client to completely drain the receive buffer, then receive the Connection Reset exception.
      ACTUAL -
      The Client receives a Connection Reset prematurely without seeing all the data that was sent by the Server.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.net.SocketException: Connection reset
      at java.net.SocketInputStream.read(SocketInputStream.java:209)
      at java.net.SocketInputStream.read(SocketInputStream.java:141)
      at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
      at sun.security.ssl.InputRecord.read(InputRecord.java:503)
      at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973)
      at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:930)
      at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
      at java.io.InputStream.read(InputStream.java:101)
      at Test.main(Test.java:65)

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import javax.net.ssl.KeyManagerFactory;
      import javax.net.ssl.SSLContext;
      import javax.net.ssl.SSLSocket;
      import javax.net.ssl.TrustManagerFactory;
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.net.ServerSocket;
      import java.net.Socket;
      import java.security.KeyStore;

      public class Test {
          public static void main(String[] args) throws Exception {
              KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                      KeyManagerFactory.getDefaultAlgorithm());
              KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
              try (InputStream is = new FileInputStream("keystore.jks")) {
                  ks.load(is, "password".toCharArray());
              }
              keyManagerFactory.init(ks, "password".toCharArray());

              TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                      TrustManagerFactory.getDefaultAlgorithm());
              trustManagerFactory.init(ks);
              final SSLContext context = SSLContext.getInstance("TLSv1");
              context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

              final ServerSocket server = new ServerSocket(9000);
              new Thread() {
                  @Override
                  public void run() {
                      while (true) {
                          try {
                              Socket s = server.accept();
                              SSLSocket sslSocket = (SSLSocket) context.getSocketFactory().createSocket(s, s.getInetAddress().getHostName(), s.getPort(), true);
                              sslSocket.setUseClientMode(false);
                              sslSocket.startHandshake();
                              System.out.println("Server sleeping");
                              Thread.sleep(3000);
                              sslSocket.getOutputStream().write(new byte[18]);
                              sslSocket.getOutputStream().write(new byte[18]);
                              sslSocket.getOutputStream().write(new byte[18]);
                              sslSocket.getOutputStream().write(new byte[18]);
                              sslSocket.getOutputStream().flush();
                              sslSocket.getOutputStream().close();
                              sslSocket.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }.start();
              Socket s = new Socket("localhost", 9000);
              SSLSocket sslSocket = (SSLSocket) context.getSocketFactory().createSocket(s, "localhost", 9000, true);
              sslSocket.setUseClientMode(true);
              sslSocket.startHandshake();

              sslSocket.getOutputStream().write(new byte[1024]);
              Thread.sleep(5000);
              byte[] b = new byte[18];
      int totalRead = 0;
      while (totalRead < 72) {
              int read = sslSocket.getInputStream().read(b);
                System.out.println("read " + read + " " + new String(b, 0, read));
      totalRead += read;
      }
              System.out.println(sslSocket.getInputStream().read() == -1);
          }
      }

      ---------- END SOURCE ----------

            michaelm Michael McMahon
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated:
              Resolved: