From: Markus Koschany <apo@debian.org>
Date: Sat, 31 Jul 2021 17:24:07 +0200
Subject: CVE-2021-28165

---
 .../org/eclipse/jetty/io/ssl/SslConnection.java    |  12 +
 .../eclipse/jetty/server/ssl/SSLEngineTest.java    | 267 +++++++++++++--------
 2 files changed, 183 insertions(+), 96 deletions(-)

diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index a2c1fdc..c385f27 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -330,6 +330,11 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
         _decryptedEndPoint.onFillableFail(cause == null ? new IOException() : cause);
     }
 
+    protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException
+    {
+        return sslEngine.unwrap(input, output);
+    }
+
     @Override
     public String toConnectionString()
     {
@@ -602,8 +607,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
                                     return filled = -1;
 
                                 case BUFFER_UNDERFLOW:
+                                    if (BufferUtil.space(_encryptedInput) == 0)
+                                    {
+                                        BufferUtil.clear(_encryptedInput);
+                                        throw new SSLHandshakeException("Encrypted buffer max length exceeded");
+                                    }
+
                                     if (net_filled > 0)
                                         continue; // try filling some more
+
                                     _underflown = true;
                                     if (net_filled < 0 && _sslEngine.getUseClientMode())
                                     {
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
index ae6a5b6..aa1b9c9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
@@ -1,6 +1,6 @@
 //
 //  ========================================================================
-//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+//  Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
 //  ------------------------------------------------------------------------
 //  All rights reserved. This program and the accompanying materials
 //  are made available under the terms of the Eclipse Public License v1.0
@@ -34,20 +34,31 @@ import java.net.Socket;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.URL;
-
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.net.SocketFactory;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSession;
 import javax.servlet.ServletException;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.util.IO;
@@ -60,6 +71,7 @@ import org.junit.jupiter.api.Test;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
@@ -69,41 +81,45 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 public class SSLEngineTest
 {
     // Useful constants
-    private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n";
-    private static final String JETTY_VERSION= Server.getVersion();
-    private static final String PROTOCOL_VERSION="2.0";
-
-    /** The request. */
-    private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: ";
-    private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: ";
-    private static final String REQUEST_CONTENT=
-        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
-        "<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+
-        "        xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\""+PROTOCOL_VERSION+"\">\n"+
-        "</requests>";
-
-    private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
-    private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
-
-    /** The expected response. */
-    private static final String RESPONSE0="HTTP/1.1 200 OK\n"+
-        "Content-Length: "+HELLO_WORLD.length()+"\n"+
-        "Server: Jetty("+JETTY_VERSION+")\n"+
-        '\n'+
+    private static final String HELLO_WORLD = "Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n";
+    private static final String JETTY_VERSION = Server.getVersion();
+    private static final String PROTOCOL_VERSION = "2.0";
+
+    /**
+     * The request.
+     */
+    private static final String REQUEST0_HEADER = "POST /r0 HTTP/1.1\n" + "Host: localhost\n" + "Content-Type: text/xml\n" + "Content-Length: ";
+    private static final String REQUEST1_HEADER = "POST /r1 HTTP/1.1\n" + "Host: localhost\n" + "Content-Type: text/xml\n" + "Connection: close\n" + "Content-Length: ";
+    private static final String REQUEST_CONTENT =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+            "        xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\"" + PROTOCOL_VERSION + "\">\n" +
+            "</requests>";
+
+    private static final String REQUEST0 = REQUEST0_HEADER + REQUEST_CONTENT.getBytes().length + "\n\n" + REQUEST_CONTENT;
+    private static final String REQUEST1 = REQUEST1_HEADER + REQUEST_CONTENT.getBytes().length + "\n\n" + REQUEST_CONTENT;
+
+    /**
+     * The expected response.
+     */
+    private static final String RESPONSE0 = "HTTP/1.1 200 OK\n" +
+        "Content-Length: " + HELLO_WORLD.length() + "\n" +
+        "Server: Jetty(" + JETTY_VERSION + ")\n" +
+        '\n' +
         HELLO_WORLD;
-    
-    private static final String RESPONSE1="HTTP/1.1 200 OK\n"+
-        "Connection: close\n"+
-        "Content-Length: "+HELLO_WORLD.length()+"\n"+
-        "Server: Jetty("+JETTY_VERSION+")\n"+
-        '\n'+
+
+    private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" +
+        "Connection: close\n" +
+        "Content-Length: " + HELLO_WORLD.length() + "\n" +
+        "Server: Jetty(" + JETTY_VERSION + ")\n" +
+        '\n' +
         HELLO_WORLD;
 
-    private static final int BODY_SIZE=300;
+    private static final int BODY_SIZE = 300;
 
     private Server server;
     private ServerConnector connector;
-
+    private SslContextFactory sslContextFactory;
 
     @BeforeEach
     public void startServer() throws Exception
@@ -114,11 +130,11 @@ public class SSLEngineTest
         sslContextFactory.setKeyStorePassword("storepwd");
         sslContextFactory.setKeyManagerPassword("keypwd");
 
-        server=new Server();
+        server = new Server();
         HttpConnectionFactory http = new HttpConnectionFactory();
         http.setInputBufferSize(512);
         http.getHttpConfiguration().setRequestHeaderSize(512);
-        connector=new ServerConnector(server, sslContextFactory, http);
+        connector = new ServerConnector(server, sslContextFactory, http);
         connector.setPort(0);
         connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
 
@@ -138,19 +154,19 @@ public class SSLEngineTest
         server.setHandler(new HelloWorldHandler());
         server.start();
 
-        SSLContext ctx=SSLContext.getInstance("TLS");
-        ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
 
-        int port=connector.getLocalPort();
+        int port = connector.getLocalPort();
 
-        Socket client=ctx.getSocketFactory().createSocket("localhost",port);
-        OutputStream os=client.getOutputStream();
+        Socket client = ctx.getSocketFactory().createSocket("localhost", port);
+        OutputStream os = client.getOutputStream();
 
         String request =
-            "GET / HTTP/1.1\r\n"+
-            "Host: localhost:"+port+"\r\n"+
-            "Connection: close\r\n"+
-            "\r\n";
+            "GET / HTTP/1.1\r\n" +
+                "Host: localhost:" + port + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
 
         os.write(request.getBytes());
         os.flush();
@@ -158,7 +174,7 @@ public class SSLEngineTest
         String response = IO.toString(client.getInputStream());
 
         assertThat(response, Matchers.containsString("200 OK"));
-        assertThat(response,Matchers.containsString(HELLO_WORLD));
+        assertThat(response, Matchers.containsString(HELLO_WORLD));
     }
 
     @Test
@@ -167,26 +183,81 @@ public class SSLEngineTest
         server.setHandler(new HelloWorldHandler());
         server.start();
 
-        SSLContext ctx=SSLContext.getInstance("TLS");
-        ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
 
-        int port=connector.getLocalPort();
+        int port = connector.getLocalPort();
 
-        Socket client=ctx.getSocketFactory().createSocket("localhost",port);
-        OutputStream os=client.getOutputStream();
+        Socket client = ctx.getSocketFactory().createSocket("localhost", port);
+        OutputStream os = client.getOutputStream();
 
         String request =
-            "GET /?dump=102400 HTTP/1.1\r\n"+
-            "Host: localhost:"+port+"\r\n"+
-            "Connection: close\r\n"+
-            "\r\n";
+            "GET /?dump=102400 HTTP/1.1\r\n" +
+                "Host: localhost:" + port + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
 
         os.write(request.getBytes());
         os.flush();
 
         String response = IO.toString(client.getInputStream());
 
-        assertThat(response.length(),greaterThan(102400));
+        assertThat(response.length(), greaterThan(102400));
+    }
+
+    @Test
+    public void testInvalidLargeTLSFrame() throws Exception
+    {
+        AtomicLong unwraps = new AtomicLong();
+        ConnectionFactory http = connector.getConnectionFactory(HttpConnectionFactory.class);
+        ConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol())
+        {
+            @Override
+            protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+            {
+                return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
+                {
+                    @Override
+                    protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException
+                    {
+                        unwraps.incrementAndGet();
+                        return super.unwrap(sslEngine, input, output);
+                    }
+                };
+            }
+        };
+        ServerConnector tlsConnector = new ServerConnector(server, 1, 1, ssl, http);
+        server.addConnector(tlsConnector);
+        server.setHandler(new HelloWorldHandler());
+        server.start();
+
+        // Create raw TLS record.
+        byte[] bytes = new byte[20005];
+        Arrays.fill(bytes, (byte)1);
+
+        bytes[0] = 22; // record type
+        bytes[1] = 3;  // major version
+        bytes[2] = 3;  // minor version
+        bytes[3] = 78; // record length 2 bytes / 0x4E20 / decimal 20,000
+        bytes[4] = 32; // record length
+        bytes[5] = 1;  // message type
+        bytes[6] = 0;  // message length 3 bytes / 0x004E17 / decimal 19,991
+        bytes[7] = 78;
+        bytes[8] = 23;
+
+        SocketFactory socketFactory = SocketFactory.getDefault();
+        try (Socket client = socketFactory.createSocket("localhost", tlsConnector.getLocalPort()))
+        {
+            client.getOutputStream().write(bytes);
+
+            // Sleep to see if the server spins.
+            Thread.sleep(1000);
+            assertThat(unwraps.get(), lessThan(128L));
+
+            // Read until -1 or read timeout.
+            client.setSoTimeout(1000);
+            IO.readBytes(client.getInputStream());
+        }
     }
 
     @Test
@@ -195,63 +266,64 @@ public class SSLEngineTest
         server.setHandler(new HelloWorldHandler());
         server.start();
 
-        final int loops=10;
-        final int numConns=20;
+        final int loops = 10;
+        final int numConns = 20;
 
-        Socket[] client=new Socket[numConns];
+        Socket[] client = new Socket[numConns];
 
-        SSLContext ctx=SSLContext.getInstance("TLSv1.2");
-        ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
+        SSLContext ctx = SSLContext.getInstance("TLSv1.2");
+        ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
 
-        int port=connector.getLocalPort();
+        int port = connector.getLocalPort();
 
         try
         {
-            for (int l=0;l<loops;l++)
+            for (int l = 0; l < loops; l++)
             {
                 // System.err.print('.');
                 try
                 {
-                    for (int i=0; i<numConns; ++i)
+                    for (int i = 0; i < numConns; ++i)
                     {
                         // System.err.println("write:"+i);
-                        client[i]=ctx.getSocketFactory().createSocket("localhost",port);
-                        OutputStream os=client[i].getOutputStream();
+                        client[i] = ctx.getSocketFactory().createSocket("localhost", port);
+                        OutputStream os = client[i].getOutputStream();
 
                         os.write(REQUEST0.getBytes());
                         os.write(REQUEST0.getBytes());
                         os.flush();
                     }
 
-                    for (int i=0; i<numConns; ++i)
+                    for (int i = 0; i < numConns; ++i)
                     {
                         // System.err.println("flush:"+i);
-                        OutputStream os=client[i].getOutputStream();
+                        OutputStream os = client[i].getOutputStream();
                         os.write(REQUEST1.getBytes());
                         os.flush();
                     }
 
-                    for (int i=0; i<numConns; ++i)
+                    for (int i = 0; i < numConns; ++i)
                     {
                         // System.err.println("read:"+i);
                         // Read the response.
-                        String responses=readResponse(client[i]);
+                        String responses = readResponse(client[i]);
                         // Check the responses
-                        assertThat(String.format("responses loop=%d connection=%d",l,i),RESPONSE0+RESPONSE0+RESPONSE1,is(responses));
+                        assertThat(String.format("responses loop=%d connection=%d", l, i), RESPONSE0 + RESPONSE0 + RESPONSE1, is(responses));
                     }
                 }
                 finally
                 {
-                    for (int i=0; i<numConns; ++i)
+                    for (int i = 0; i < numConns; ++i)
                     {
-                        if (client[i]!=null)
+                        if (client[i] != null)
                         {
                             try
                             {
                                 assertThat("Client should read EOF", client[i].getInputStream().read(), is(-1));
                             }
-                            catch(SocketException e)
+                            catch (SocketException e)
                             {
+                                // no op
                             }
                         }
                     }
@@ -272,10 +344,10 @@ public class SSLEngineTest
         server.start();
 
         SSLContext context = SSLContext.getInstance("SSL");
-        context.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
+        context.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
         HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
 
-        URL url = new URL("https://localhost:"+connector.getLocalPort()+"/test");
+        URL url = new URL("https://localhost:" + connector.getLocalPort() + "/test");
 
         HttpURLConnection conn = (HttpURLConnection)url.openConnection();
         if (conn instanceof HttpsURLConnection)
@@ -295,7 +367,7 @@ public class SSLEngineTest
         conn.setDoInput(true);
         conn.setDoOutput(true);
         conn.setRequestMethod("POST");
-        conn.setRequestProperty("Content-Type","text/plain");
+        conn.setRequestProperty("Content-Type", "text/plain");
         conn.setChunkedStreamingMode(128);
         conn.connect();
         byte[] b = new byte[BODY_SIZE];
@@ -309,13 +381,15 @@ public class SSLEngineTest
 
         int len = 0;
         InputStream is = conn.getInputStream();
-        int bytes=0;
+        int bytes = 0;
         while ((len = is.read(b)) > -1)
-            bytes+=len;
+        {
+            bytes += len;
+        }
         is.close();
 
-        assertEquals(BODY_SIZE,handler.bytes);
-        assertEquals(BODY_SIZE,bytes);
+        assertEquals(BODY_SIZE, handler.bytes);
+        assertEquals(BODY_SIZE, bytes);
     }
 
     /**
@@ -327,30 +401,30 @@ public class SSLEngineTest
      */
     private static String readResponse(Socket client) throws IOException
     {
-        BufferedReader br=null;
-        StringBuilder sb=new StringBuilder(1000);
+        BufferedReader br = null;
+        StringBuilder sb = new StringBuilder(1000);
 
         try
         {
             client.setSoTimeout(5000);
-            br=new BufferedReader(new InputStreamReader(client.getInputStream()));
+            br = new BufferedReader(new InputStreamReader(client.getInputStream()));
 
             String line;
 
-            while ((line=br.readLine())!=null)
+            while ((line = br.readLine()) != null)
             {
                 sb.append(line);
                 sb.append('\n');
             }
         }
-        catch(SocketTimeoutException e)
+        catch (SocketTimeoutException e)
         {
-            System.err.println("Test timedout: "+e.toString());
+            System.err.println("Test timedout: " + e.toString());
             e.printStackTrace(); // added to see if we can get more info from failures on CI
         }
         finally
         {
-            if (br!=null)
+            if (br != null)
             {
                 br.close();
             }
@@ -364,22 +438,24 @@ public class SSLEngineTest
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             // System.err.println("HANDLE "+request.getRequestURI());
-            String ssl_id = (String)request.getAttribute("javax.servlet.request.ssl_session_id");
-            assertNotNull(ssl_id);
-            
-            if (request.getParameter("dump")!=null)
+            String sslId = (String)request.getAttribute("javax.servlet.request.ssl_session_id");
+            assertNotNull(sslId);
+
+            if (request.getParameter("dump") != null)
             {
-                ServletOutputStream out=response.getOutputStream();
+                ServletOutputStream out = response.getOutputStream();
                 byte[] buf = new byte[Integer.parseInt(request.getParameter("dump"))];
                 // System.err.println("DUMP "+buf.length);
-                for (int i=0;i<buf.length;i++)
-                    buf[i]=(byte)('0'+(i%10));
+                for (int i = 0; i < buf.length; i++)
+                {
+                    buf[i] = (byte)('0' + (i % 10));
+                }
                 out.write(buf);
                 out.close();
             }
             else
             {
-                PrintWriter out=response.getWriter();
+                PrintWriter out = response.getWriter();
                 out.print(HELLO_WORLD);
                 out.close();
             }
@@ -388,7 +464,7 @@ public class SSLEngineTest
 
     private static class StreamHandler extends AbstractHandler
     {
-        private int bytes=0;
+        private int bytes = 0;
 
         @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -400,7 +476,7 @@ public class SSLEngineTest
             InputStream is = request.getInputStream();
             while ((len = is.read(b)) > -1)
             {
-                bytes+=len;
+                bytes += len;
             }
 
             OutputStream os = response.getOutputStream();
@@ -412,5 +488,4 @@ public class SSLEngineTest
             response.flushBuffer();
         }
     }
-
 }
