You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2017/04/12 14:25:50 UTC

svn commit: r1791124 - in /tomcat/trunk: java/org/apache/coyote/http2/Http2UpgradeHandler.java test/org/apache/coyote/http2/Http2TestBase.java test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java webapps/docs/changelog.xml

Author: markt
Date: Wed Apr 12 14:25:50 2017
New Revision: 1791124

URL: http://svn.apache.org/viewvc?rev=1791124&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=60970
Fix infinite loop if application tries to write a large header to the response when using HTTP/2. 

Added:
    tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
    tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1791124&r1=1791123&r2=1791124&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Wed Apr 12 14:25:50 2017
@@ -546,29 +546,36 @@ class Http2UpgradeHandler extends Abstra
             while (state != State.COMPLETE) {
                 state = getHpackEncoder().encode(coyoteResponse.getMimeHeaders(), target);
                 target.flip();
-                ByteUtil.setThreeBytes(header, 0, target.limit());
-                if (first) {
-                    first = false;
-                    header[3] = FrameType.HEADERS.getIdByte();
-                    if (stream.getOutputBuffer().hasNoBody()) {
-                        header[4] = FLAG_END_OF_STREAM;
+                if (state == State.COMPLETE || target.limit() > 0) {
+                    ByteUtil.setThreeBytes(header, 0, target.limit());
+                    if (first) {
+                        first = false;
+                        header[3] = FrameType.HEADERS.getIdByte();
+                        if (stream.getOutputBuffer().hasNoBody()) {
+                            header[4] = FLAG_END_OF_STREAM;
+                        }
+                    } else {
+                        header[3] = FrameType.CONTINUATION.getIdByte();
+                    }
+                    if (state == State.COMPLETE) {
+                        header[4] += FLAG_END_OF_HEADERS;
+                    }
+                    if (log.isDebugEnabled()) {
+                        log.debug(target.limit() + " bytes");
+                    }
+                    ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue());
+                    try {
+                        socketWrapper.write(true, header, 0, header.length);
+                        socketWrapper.write(true, target);
+                        socketWrapper.flush(true);
+                    } catch (IOException ioe) {
+                        handleAppInitiatedIOException(ioe);
                     }
-                } else {
-                    header[3] = FrameType.CONTINUATION.getIdByte();
-                }
-                if (state == State.COMPLETE) {
-                    header[4] += FLAG_END_OF_HEADERS;
-                }
-                if (log.isDebugEnabled()) {
-                    log.debug(target.limit() + " bytes");
                 }
-                ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue());
-                try {
-                    socketWrapper.write(true, header, 0, header.length);
-                    socketWrapper.write(true, target);
-                    socketWrapper.flush(true);
-                } catch (IOException ioe) {
-                    handleAppInitiatedIOException(ioe);
+                if (state == State.UNDERFLOW) {
+                    target = ByteBuffer.allocate(target.capacity() * 2);
+                } else {
+                    target.clear();
                 }
             }
         }

Modified: tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java?rev=1791124&r1=1791123&r2=1791124&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java (original)
+++ tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Wed Apr 12 14:25:50 2017
@@ -27,6 +27,7 @@ import java.nio.charset.StandardCharsets
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Random;
 
 import javax.net.SocketFactory;
 import javax.servlet.ServletException;
@@ -59,6 +60,8 @@ public abstract class Http2TestBase exte
     // response now included a date header
     protected static final String DEFAULT_DATE = "Wed, 11 Nov 2015 19:18:42 GMT";
 
+    private static final String HEADER_IGNORED = "x-ignore";
+
     static final String DEFAULT_CONNECTION_HEADER_VALUE = "Upgrade, HTTP2-Settings";
     private static final byte[] EMPTY_SETTINGS_FRAME =
         { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 };
@@ -918,7 +921,12 @@ public abstract class Http2TestBase exte
             if ("date".equals(name)) {
                 value = DEFAULT_DATE;
             }
-            trace.append(lastStreamId + "-Header-[" + name + "]-[" + value + "]\n");
+            // Some header values vary so ignore them
+            if (HEADER_IGNORED.equals(name)) {
+                trace.append(lastStreamId + "-Header-[" + name + "]-[...]\n");
+            } else {
+                trace.append(lastStreamId + "-Header-[" + name + "]-[" + value + "]\n");
+            }
         }
 
 
@@ -1131,6 +1139,26 @@ public abstract class Http2TestBase exte
         }
     }
 
+
+    static class LargeHeaderServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+            StringBuilder headerValue = new StringBuilder();
+            Random random = new Random();
+            while (headerValue.length() < 2048) {
+                headerValue.append(Long.toString(random.nextLong()));
+            }
+            resp.setHeader(HEADER_IGNORED, headerValue.toString());
+            resp.getWriter().print("OK");
+        }
+    }
+
 
     static class SettingValue {
         private final int setting;

Added: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java?rev=1791124&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java (added)
+++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java Wed Apr 12 14:25:50 2017
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http2;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+
+public class TestHttp2UpgradeHandler extends Http2TestBase {
+
+    // https://bz.apache.org/bugzilla/show_bug.cgi?id=60970
+    @Test
+    public void testLargeHeader() throws Exception {
+        enableHttp2();
+
+        Tomcat tomcat = getTomcatInstance();
+
+        Context ctxt = tomcat.addContext("", null);
+        Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+        ctxt.addServletMappingDecoded("/simple", "simple");
+        Tomcat.addServlet(ctxt, "large", new LargeHeaderServlet());
+        ctxt.addServletMappingDecoded("/large", "large");
+
+        tomcat.start();
+
+        openClientConnection();
+        doHttpUpgrade();
+        sendClientPreface();
+        validateHttp2InitialResponse();
+
+        byte[] frameHeader = new byte[9];
+        ByteBuffer headersPayload = ByteBuffer.allocate(128);
+        buildGetRequest(frameHeader, headersPayload, null, 3, "/large");
+        writeFrame(frameHeader, headersPayload);
+
+        // Headers
+        parser.readFrame(true);
+        parser.readFrame(true);
+        // Body
+        parser.readFrame(true);
+
+        Assert.assertEquals(
+                "3-HeadersStart\n" +
+                "3-Header-[:status]-[200]\n" +
+                "3-Header-[x-ignore]-[...]\n" +
+                "3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
+                "3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
+                "3-HeadersEnd\n" +
+                "3-Body-2\n" +
+                "3-EndOfStream\n", output.getTrace());
+    }
+
+}

Propchange: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1791124&r1=1791123&r2=1791124&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Wed Apr 12 14:25:50 2017
@@ -97,6 +97,10 @@
       <fix>
         Align cipher configuration parsing with current OpenSSL master. (markt)
       </fix>
+      <fix>
+        <bug>60970</bug>: Fix infinite loop if application tries to write a
+        large header to the response when using HTTP/2. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org