8149169: SSLSocketInputRecord.decodeInputRecord buffer overflow

Reviewed-by: xuelei
diff --git a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
index 86b772c..412c3ec 100644
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -366,17 +366,6 @@
     private static final Debug debug = Debug.getInstance("ssl");
 
     /*
-     * Is it the first application record to write?
-     */
-    private boolean isFirstAppOutputRecord = true;
-
-    /*
-     * If AppOutputStream needs to delay writes of small packets, we
-     * will use this to store the data until we actually do the write.
-     */
-    private ByteArrayOutputStream heldRecordBuffer = null;
-
-    /*
      * Whether local cipher suites preference in server side should be
      * honored during handshaking?
      */
@@ -998,9 +987,21 @@
             Plaintext plainText = null;
             while (((state = getConnectionState()) != cs_CLOSED) &&
                     (state != cs_ERROR) && (state != cs_APP_CLOSED)) {
-                // clean the buffer
+
+                /*
+                 * clean the buffer and check if it is too small, e.g. because
+                 * the AppInputStream did not have the chance to see the
+                 * current packet length but rather something like that of the
+                 * handshake before. In that case we return 0 at this point to
+                 * give the caller the chance to adjust the buffer.
+                 */
                 if (buffer != null) {
                     buffer.clear();
+
+                    if (buffer.remaining() <
+                            inputRecord.bytesInCompletePacket(sockInput)) {
+                        return 0;
+                    }
                 }
 
                 /*
diff --git a/jdk/test/sun/security/ssl/SSLSocketImpl/LargePacketAfterHandshakeTest.java b/jdk/test/sun/security/ssl/SSLSocketImpl/LargePacketAfterHandshakeTest.java
new file mode 100644
index 0000000..fd7ae8f
--- /dev/null
+++ b/jdk/test/sun/security/ssl/SSLSocketImpl/LargePacketAfterHandshakeTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/*
+ * @test
+ * @bug 8149169
+ * @summary Test for BufferOverflowException during read from SSLSocket when
+ *          large packet is coming from server after server initiated handshake
+ * @run main/othervm LargePacketAfterHandshakeTest
+ */
+public class LargePacketAfterHandshakeTest {
+    static String pathToStores = "../../../../javax/net/ssl/etc";
+    static String keyStoreFile = "keystore";
+    static String trustStoreFile = "truststore";
+    static String passwd = "passphrase";
+
+    volatile static int serverport = -1;
+    volatile static boolean serverReady = false;
+    volatile static boolean clientDone = false;
+    volatile static Exception serverException = null;
+
+    public static void runServer() {
+        try {
+            System.out.println("Server: Started server thread.");
+            SSLServerSocketFactory ssf =
+                (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
+            SSLServerSocket s = (SSLServerSocket)ssf.createServerSocket(0);
+            serverport = s.getLocalPort();
+            System.out.println("Server: Started, listening on port " +
+                    serverport + ".");
+            serverReady = true;
+            SSLSocket c = (SSLSocket)s.accept();
+            s.close();
+            System.out.println(
+                "Server: Accepted client connection and closed server socket.");
+            BufferedReader r = new BufferedReader(
+                    new InputStreamReader(c.getInputStream()));
+            BufferedWriter w = new BufferedWriter(
+                    new OutputStreamWriter(c.getOutputStream()));
+            String echostring = r.readLine();
+            System.out.println("Server: Read " + echostring.length() +
+                    " chars of input data.");
+            c.startHandshake();
+            System.out.println("Server: Kicked new handshake.");
+            w.write(echostring);
+            w.newLine();
+            w.flush();
+            System.out.println("Server: Echoed " + echostring.length() +
+                    " chars of input data.");
+            while (!clientDone) {
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    System.out.println("Server: Caught InterruptedException.");
+                }
+            }
+            r.close();
+            w.close();
+            c.close();
+            System.out.println(
+                    "Server: Closed streams and client socket, exiting.");
+        } catch (Exception e) {
+            System.out.println("Server: Caught Exception.");
+            e.printStackTrace();
+            serverReady = true;
+            serverException = e;
+        }
+    }
+
+    public static void runClient() throws IOException {
+        try {
+            SSLSocketFactory f =
+                    (SSLSocketFactory)SSLSocketFactory.getDefault();
+            System.out.println("Client: Initialized.");
+            while (!serverReady) {
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    System.out.println("Client: Caught InterruptedException.");
+                }
+            }
+            SSLSocket c = (SSLSocket)f.createSocket("localhost", serverport);
+            BufferedWriter w = new BufferedWriter(
+                    new OutputStreamWriter(c.getOutputStream()));
+            BufferedReader r = new BufferedReader(
+                    new InputStreamReader(c.getInputStream()));
+            System.out.println("Client: Connected.");
+            String echoPattern = "Otto";
+            StringBuilder echoBuilder =
+                    new StringBuilder(4500 + echoPattern.length());
+            while (echoBuilder.length() < 4500) {
+                echoBuilder.append(echoPattern);
+            }
+            String echostring = echoBuilder.toString();
+            w.write(echostring);
+            w.newLine();
+            w.flush();
+            System.out.println("Client: Sent " + echostring.length() +
+                    " chars of data.");
+            String echoresponse = r.readLine();
+            clientDone = true;
+            System.out.println("Client: Read " + echoresponse.length() +
+                    " chars of data.");
+            w.close();
+            r.close();
+            c.close();
+            System.out.println("Client: Closed streams and socket, exiting.");
+        } catch (IOException e) {
+            System.out.println("Client: Caught Exception.");
+            e.printStackTrace();
+            clientDone = true;
+            throw e;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        String keyFilename = System.getProperty("test.src", "./") + "/" +
+                pathToStores + "/" + keyStoreFile;
+        String trustFilename = System.getProperty("test.src", "./") + "/" +
+                pathToStores + "/" + trustStoreFile;
+
+        System.setProperty("javax.net.ssl.keyStore", keyFilename);
+        System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+        System.setProperty("javax.net.ssl.trustStore", trustFilename);
+        System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+        Thread serverThread = new Thread() {
+            @Override
+            public void run() {
+                runServer();
+            }
+        };
+        serverThread.start();
+        runClient();
+        while (serverThread.isAlive()) {
+            try {
+                serverThread.join();
+            } catch (InterruptedException e) {
+                System.out.println("Main: Caught InterruptedException " +
+                        " waiting for server Thread.");
+            }
+        }
+        if (serverException != null) {
+            throw serverException;
+        }
+    }
+}