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

javax.net.ssl.SSLSocket.isClosed() race condition after calling Socket.close()

XMLWordPrintable

    • x86_64
    • linux

      FULL PRODUCT VERSION :
      $ java -version
      java version "1.7.0_55"
      OpenJDK Runtime Environment (IcedTea 2.4.7) (7u55-2.4.7-1ubuntu1~0.13.10.1)
      OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Linux Mint 16 64-bit

      $ uname -a
      Linux scc 3.11.0-15-generic #25-Ubuntu SMP Thu Jan 30 17:22:01 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux


      A DESCRIPTION OF THE PROBLEM :
      The documentation for java.net.Socket.close() states that any thread currently blocked on an I/O operation will throw a SocketException. In the handling of the exception, one can call Socket.isClosed() and this should indicate whether the socket was closed because close() was called in another thread of the program (isClosed() == true) or because the socket was closed by some external reason, e.g. a TCP reset (isClosed() == false).

      In the case of plain (non-SSL) sockets, this works as expected. However, in the case of SSL sockets, it is possible that a socket closed via the close() method will erroneously result in isClosed() == false. In this situation, it is possible to sleep the thread for a few milliseconds and then when calling isClosed() again, the value will have changed to true (the correct value).

      This demonstrates some race condition in the underlying code that allows the value of isClosed() to change depending on how long one waits to call it after the exception is caught.

      REGRESSION. Last worked in version 6u43

      ADDITIONAL REGRESSION INFORMATION:
      $ java -version
      java version "1.6.0_24"
      OpenJDK Runtime Environment (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu2)
      OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      The demo code works by opening a socket and then starting a thread to read from it. A brief (5 second delay) is used to let the reading thread to start and reach the call to read(). Then another thread is started, which calls Socket.close(). This causes the reading thread to unblock and catch an IOException (specifically the subclass SocketException). In the handling of the exception, check Socket.isClosed(). Then sleep some given number of milliseconds and check Socket.isClosed() again. If the value has changed between the two calls, the bug is demonstrated.

      Program arguments:

          1. Host name
          2. Port
          3. SSL mode: true=SSL, false=non SSL (the bug only seems to occur in SSL connections)
          4. Milliseconds delay between checking value of isClosed()

      Compile the demo and execute like this:
      $ java com.sibilantsolutions.iscloseddemo.IsClosedDemo google.com 443 true 250



      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The value of Socket.isClosed() should always be true in the exception handling caught when read() unblocks after calling Socket.close() in another thread.

      Output when bug does not occur:

      $ java com.sibilantsolutions.iscloseddemo.IsClosedDemo google.com 443 true 250
      Testing against host=google.com, port=443, ssl=true, sleepMs=250.
      Connected socket=7c1ca8e[SSL_NULL_WITH_NULL_NULL: Socket[addr=google.com/74.125.228.225,port=443,localport=50739]]
      Calling read...
      SSL handshake completed: 7c1ca8e[TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: Socket[addr=google.com/74.125.228.225,port=443,localport=50739]].
      Calling close.
      Bug not encountered. Value of isClosed stayed=true.

      ACTUAL -
      The value of Socket.isClosed(), in SSLSockets after having called Socket.close(), is sometimes false and then, after sleeping a few milliseconds, isClosed() correctly returns true.

      Output when bug occurs:

      $ java com.sibilantsolutions.iscloseddemo.IsClosedDemo google.com 443 true 250
      Testing against host=google.com, port=443, ssl=true, sleepMs=250.
      Connected socket=7c1ca8e[SSL_NULL_WITH_NULL_NULL: Socket[addr=google.com/173.194.121.9,port=443,localport=59402]]
      Calling read...
      SSL handshake completed: 7c1ca8e[TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: Socket[addr=google.com/173.194.121.9,port=443,localport=59402]].
      Calling close.

      ***********************
      *** BUG BUG BUG BUG BUG
      ***
      *** Value of isClosed changed from=false to=true!!!!!
      ***
      *** BUG BUG BUG BUG BUG
      ***********************


      REPRODUCIBILITY :
      This bug can be reproduced often.

      ---------- BEGIN SOURCE ----------
      package com.sibilantsolutions.iscloseddemo;

      import java.io.IOException;
      import java.io.InputStream;
      import java.net.Socket;

      import javax.net.SocketFactory;
      import javax.net.ssl.HandshakeCompletedEvent;
      import javax.net.ssl.HandshakeCompletedListener;
      import javax.net.ssl.SSLSocket;
      import javax.net.ssl.SSLSocketFactory;

      /**
       * Demo race condition in Socket.isClosed() after having called Socket.close().
       *
       * - Connect a socket to a host.
       * - Start a thread and call the socket's InputStream.read().
       * - From a different thread, call socket.close().
       * - This will cause read() to throw an exception.
       * - In the exception handling, check the value of socket.isClosed(). It should be true. However,
       * in SSL sockets, it is often false. Sleeping for a short time and checking again will
       * then correctly say isClosed() is true. It SHOULD have been true all along.
       *
       * Note that there are two scenarios which can cause read() to throw an exception:
       * 1) Calling Socket.close() from a different thread, which is what we demo here.
       * 2) Experiencing an external network error like a TCP connection reset. We do not address
       * this scenario in this demo.
       */
      public class IsClosedDemo
      {

          static public void main( String[] args )
          {
              int i = 0;
              String host = args[i++];
              int port = Integer.parseInt( args[i++] );
              boolean isSSL = Boolean.parseBoolean( args[i++] );
              long sleepMs = Long.parseLong( args[i++] );

              System.out.println( "Testing against host=" + host + ", port=" + port + ", ssl=" + isSSL +
                      ", sleepMs=" + sleepMs + '.' );

              Socket socket = connect( host, port, isSSL );

              readInThread( socket, sleepMs );

              try
              {
                      //Let the read thread get started.
                  Thread.sleep( 5 * 1000 );
              }
              catch ( InterruptedException e )
              {
                  // TODO Auto-generated catch block
                  throw new UnsupportedOperationException( "OGTE TODO!", e );
              }

              testInThread( socket );
          }

          private static Socket connect( String host, int port, boolean isSSL )
          {
              SocketFactory socketFactory;

              if ( isSSL )
                  socketFactory = SSLSocketFactory.getDefault();
              else
                  socketFactory = SocketFactory.getDefault();

              Socket socket;

              try
              {
                  socket = socketFactory.createSocket( host, port );
              }
              catch ( IOException e )
              {
                  // TODO Auto-generated catch block
                  throw new UnsupportedOperationException( "OGTE TODO!", e );
              }

              System.out.println( "Connected socket=" + socket );

              if ( isSSL )
              {
                  SSLSocket sslSocket = (SSLSocket)socket;

                  sslSocket.addHandshakeCompletedListener( new HandshakeCompletedListener() {

                      public void handshakeCompleted( HandshakeCompletedEvent event )
                      {
                          System.out.println( "SSL handshake completed: " + event.getSocket() + "." );
                      }
                  } );

                  //SSL handshake will occur automatically when we attempt to read from the socket later.
              }

              return socket;
          }

          private static void readInThread( final Socket socket, final long sleepMs )
          {
              Runnable r = new Runnable() {

                  public void run()
                  {
                      InputStream ins;

                      try
                      {
                          ins = socket.getInputStream();
                      }
                      catch ( IOException e )
                      {
                          // TODO Auto-generated catch block
                          throw new UnsupportedOperationException( "OGTE TODO!", e );
                      }

                      byte[] buf = new byte[1024];

                      System.out.println( "Calling read..." );

                      try
                      {
                          int numRead = ins.read( buf );

                              //Should not get here in this demo; we call Socket.close() locally to
                              //cause read to unblock and throw the exception which is handled below.s
                          System.out.println( "numRead=" + numRead );
                      }
                      catch ( IOException e )
                      {
                              //Check if the socket is closed, which -should- be true after Socket.close()
                              //has been called.
                          boolean originalIsClosed = socket.isClosed();

                          if ( sleepMs > 0 )
                          {
                              try
                              {
                                  Thread.sleep( sleepMs );
                              }
                              catch ( InterruptedException e1 )
                              {
                                  // TODO Auto-generated catch block
                                  throw new UnsupportedOperationException( "OGTE TODO!", e1 );
                              }
                          }

                              //Now that we have slept for a moment, check again if the socket is closed.
                              //This -should not- change from the value prior to sleeping.
                          boolean nowIsClosed = socket.isClosed();

                          if ( originalIsClosed != nowIsClosed )
                          {
                              System.out.println(
                                      "\n" +
                                      "***********************\n" +
                                      "*** BUG BUG BUG BUG BUG\n" +
                                      "***\n" +
                                      "*** Value of isClosed changed from=" + originalIsClosed +
                                      " to=" + nowIsClosed + "!!!!!\n" +
                                      "***\n" +
                                      "*** BUG BUG BUG BUG BUG\n" +
                                      "***********************" );
                          }
                          else
                          {
                              System.out.println( "Bug not encountered. Value of isClosed stayed=" +
                                      originalIsClosed + '.' );
                          }
                      }
                  }
              };

              Thread thread = new Thread( r );
              thread.start();
          }

          private static void testInThread( final Socket socket )
          {
              Runnable r = new Runnable() {

                  public void run()
                  {
                      System.out.println( "Calling close." );

                      try
                      {
                          socket.close();
                      }
                      catch ( IOException e )
                      {
                          // TODO Auto-generated catch block
                          throw new UnsupportedOperationException( "OGTE TODO!", e );
                      }
                  }
              };

              Thread thread = new Thread( r );
              thread.start();
          }

      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      If Socket.isClosed() returns false when using an SSLSocket, then sleep for a few milliseconds (say, 250) and call isClosed() again. If the value changes from false to true then the bug is demonstrated.

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

              Created:
              Updated:
              Resolved: