Blob Blame History Raw
--- java/org/apache/coyote/http11/AbstractHttp11Processor.java.orig	2014-03-17 16:00:40.592415000 -0400
+++ java/org/apache/coyote/http11/AbstractHttp11Processor.java	2014-03-18 13:39:06.789696000 -0400
@@ -684,13 +684,14 @@
     /**
      * Initialize standard input and output filters.
      */
-    protected void initializeFilters(int maxTrailerSize) {
+    protected void initializeFilters(int maxTrailerSize, int maxExtensionSize) {
         // Create and add the identity filters.
         getInputBuffer().addFilter(new IdentityInputFilter());
         getOutputBuffer().addFilter(new IdentityOutputFilter());
 
         // Create and add the chunked filters.
-        getInputBuffer().addFilter(new ChunkedInputFilter(maxTrailerSize));
+        getInputBuffer().addFilter(
+                new ChunkedInputFilter(maxTrailerSize, maxExtensionSize));
         getOutputBuffer().addFilter(new ChunkedOutputFilter());
 
         // Create and add the void filters.
--- java/org/apache/coyote/http11/AbstractHttp11Protocol.java.orig	2014-03-17 16:00:57.458467000 -0400
+++ java/org/apache/coyote/http11/AbstractHttp11Protocol.java	2014-03-17 16:40:11.035409000 -0400
@@ -151,7 +151,15 @@
         this.maxTrailerSize = maxTrailerSize;
     }
 
-
+     /**
+      * Maximum size of extension information in chunked encoding
+      */
+     private int maxExtensionSize = 8192;
+     public int getMaxExtensionSize() { return maxExtensionSize; }
+     public void setMaxExtensionSize(int maxExtensionSize) {
+         this.maxExtensionSize = maxExtensionSize;
+     }
+ 
     /**
      * This field indicates if the protocol is treated as if it is secure. This
      * normally means https is being used but can be used to fake https e.g
--- java/org/apache/coyote/http11/Http11AprProcessor.java.orig	2014-03-17 16:01:22.889559000 -0400
+++ java/org/apache/coyote/http11/Http11AprProcessor.java	2014-03-17 16:43:14.716027000 -0400
@@ -58,7 +58,7 @@
 
 
     public Http11AprProcessor(int headerBufferSize, AprEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
 
@@ -68,7 +68,7 @@
         outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
--- java/org/apache/coyote/http11/Http11AprProtocol.java.orig	2014-03-17 16:10:16.268358000 -0400
+++ java/org/apache/coyote/http11/Http11AprProtocol.java	2014-03-17 16:50:17.428466000 -0400
@@ -294,7 +294,7 @@
         protected Http11AprProcessor createProcessor() {
             Http11AprProcessor processor = new Http11AprProcessor(
                     proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
--- java/org/apache/coyote/http11/Http11NioProcessor.java.orig	2014-03-17 16:02:20.016748000 -0400
+++ java/org/apache/coyote/http11/Http11NioProcessor.java	2014-03-17 16:51:55.623782000 -0400
@@ -63,7 +63,7 @@
 
 
     public Http11NioProcessor(int maxHttpHeaderSize, NioEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
 
@@ -73,7 +73,7 @@
         outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
--- java/org/apache/coyote/http11/Http11NioProtocol.java.orig	2014-03-17 16:07:26.027787000 -0400
+++ java/org/apache/coyote/http11/Http11NioProtocol.java	2014-03-17 16:53:09.198025000 -0400
@@ -260,7 +260,7 @@
         public Http11NioProcessor createProcessor() {
             Http11NioProcessor processor = new Http11NioProcessor(
                     proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
--- java/org/apache/coyote/http11/Http11Processor.java.orig	2014-03-17 16:07:45.099837000 -0400
+++ java/org/apache/coyote/http11/Http11Processor.java	2014-03-18 12:42:34.018260000 -0400
@@ -50,7 +50,7 @@
 
 
     public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
-            int maxTrailerSize) {
+            int maxTrailerSize, int maxExtensionSize) {
 
         super(endpoint);
         
@@ -60,7 +60,7 @@
         outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
         response.setOutputBuffer(outputBuffer);
 
-        initializeFilters(maxTrailerSize);
+        initializeFilters(maxTrailerSize, maxExtensionSize);
     }
 
 
--- java/org/apache/coyote/http11/Http11Protocol.java.orig	2014-03-17 16:08:00.058113000 -0400
+++ java/org/apache/coyote/http11/Http11Protocol.java	2014-03-17 16:56:04.194609000 -0400
@@ -164,7 +164,7 @@
         protected Http11Processor createProcessor() {
             Http11Processor processor = new Http11Processor(
                     proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
-                    proto.getMaxTrailerSize());
+                    proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
             processor.setAdapter(proto.adapter);
             processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
             processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
--- java/org/apache/coyote/http11/filters/ChunkedInputFilter.java.orig	2014-03-17 16:08:12.213985000 -0400
+++ java/org/apache/coyote/http11/filters/ChunkedInputFilter.java	2014-03-18 13:13:49.468583000 -0400
@@ -118,9 +118,29 @@
      */
     private Request request;
     
+
+    /**
+     * Limit for extension size.
+     */
+    private final long maxExtensionSize;
+
+
+     /**
+     * Limit for trailer size.
+     */
+     private int maxTrailerSize;
+
+
+    /**
+     * Size of extensions processed for this request.
+     */
+    private long extensionSize;
+
     // ----------------------------------------------------------- Constructors
-    public ChunkedInputFilter(int maxTrailerSize) {
+    public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
         this.trailingHeaders.setLimit(maxTrailerSize);
+        this.maxTrailerSize = maxTrailerSize;
+        this.maxExtensionSize = maxExtensionSize;
     }
 
     // ---------------------------------------------------- InputBuffer Methods
@@ -250,6 +270,8 @@
         endChunk = false;
         needCRLFParse = false;
         trailingHeaders.recycle();
+        trailingHeaders.setLimit(maxTrailerSize);
+        extensionSize = 0;
     }
 
 
@@ -299,7 +321,7 @@
         int result = 0;
         boolean eol = false;
         boolean readDigit = false;
-        boolean trailer = false;
+        boolean extension = false;
 
         while (!eol) {
 
@@ -311,9 +333,13 @@
             if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
                 parseCRLF(false);
                 eol = true;
-            } else if (buf[pos] == Constants.SEMI_COLON) {
-                trailer = true;
-            } else if (!trailer) { 
+            } else if (buf[pos] == Constants.SEMI_COLON && !extension) {
+                // First semi-colon marks the start of the extension. Further
+                // semi-colons may appear to separate multiple chunk-extensions.
+                // These need to be processed as part of parsing the extensions.
+                extension = true;
+                extensionSize++;
+            } else if (!extension) {
                 //don't read data after the trailer
                 int charValue = HexUtils.getDec(buf[pos]);
                 if (charValue != -1) {
@@ -325,13 +351,20 @@
                     //in the chunked header
                     return false;
                 }
-            }
-
-            // Parsing the CRLF increments pos
-            if (!eol) {
-                pos++;
-            }
+            } else {
+            // Extension 'parsing'
+            // Note that the chunk-extension is neither parsed nor
+            // validated. Currently it is simply ignored.
+                extensionSize++;
+                if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
+                    throw new IOException("maxExtensionSize exceeded");
+                }
+           }
+        }
 
+        // Parsing the CRLF increments pos
+        if (!eol) {
+            pos++;
         }
 
         if (!readDigit)
@@ -489,12 +522,17 @@
                 chr = buf[pos];
                 if ((chr == Constants.SP) || (chr == Constants.HT)) {
                     pos++;
+                    // If we swallow whitespace, make sure it counts towards the
+                    // limit placed on trailing header size
+                    int newlimit = trailingHeaders.getLimit() -1;
+                    if (trailingHeaders.getEnd() > newlimit) {
+                        throw new IOException("Exceeded maxTrailerSize");
+                    }
+                    trailingHeaders.setLimit(newlimit);
                 } else {
                     space = false;
                 }
-    
             }
-    
             // Reading bytes until the end of the line
             while (!eol) {
     
--- test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java.orig	2014-03-17 16:08:33.031999000 -0400
+++ test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java	2014-03-17 17:40:23.853592000 -0400
@@ -41,6 +41,7 @@
 public class TestChunkedInputFilter extends TomcatBaseTest {
 
     private static final String LF = "\n";
+    private static final int EXT_SIZE_LIMIT = 10;
 
     @Test
     public void testChunkHeaderCRLF() throws Exception {
@@ -202,6 +203,79 @@
         assertTrue(client.isResponse500());
     }
 
+
+    @Test
+    public void testExtensionSizeLimitOneBelow() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT - 1, true);
+    }
+
+
+    @Test
+    public void testExtensionSizeLimitExact() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT, true);
+    }
+
+
+    @Test
+    public void testExtensionSizeLimitOneOver() throws Exception {
+        doTestExtensionSizeLimit(EXT_SIZE_LIMIT + 1, false);
+    }
+
+    private void doTestExtensionSizeLimit(int len, boolean ok) 
+        throws Exception {
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+
+        tomcat.getConnector().setProperty(
+                "maxExtensionSize", Integer.toString(EXT_SIZE_LIMIT));
+
+        // Must have a real docBase - just use temp
+        Context ctx =
+            tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+
+        Tomcat.addServlet(ctx, "servlet", new EchoHeaderServlet());
+        ctx.addServletMapping("/", "servlet");
+
+        tomcat.start();
+
+        String extName = ";foo=";
+        StringBuilder extValue = new StringBuilder(len);
+        for (int i = 0; i < (len - extName.length()); i++) {
+            extValue.append("x");
+        }
+
+        String[] request = new String[]{
+            "POST /echo-params.jsp HTTP/1.1" + SimpleHttpClient.CRLF +
+            "Host: any" + SimpleHttpClient.CRLF +
+            "Transfer-encoding: chunked" + SimpleHttpClient.CRLF +
+            "Content-Type: application/x-www-form-urlencoded" +
+                    SimpleHttpClient.CRLF +
+            "Connection: close" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF +
+            "3" + extName + extValue.toString() + SimpleHttpClient.CRLF +
+            "a=0" + SimpleHttpClient.CRLF +
+            "4" + SimpleHttpClient.CRLF +
+            "&b=1" + SimpleHttpClient.CRLF +
+            "0" + SimpleHttpClient.CRLF +
+            SimpleHttpClient.CRLF };
+
+        TrailerClient client =
+            new TrailerClient(tomcat.getConnector().getLocalPort());
+        client.setRequest(request);
+
+        client.connect();
+        client.processRequest();
+
+        if (ok) {
+           assertTrue(client.isResponse200());
+        } else {
+           assertTrue(client.isResponse500());
+        }
+    }
+
+    
+
+
     @Test
     public void testNoTrailingHeaders() throws Exception {
         // Setup Tomcat instance
--- webapps/docs/changelog.xml.orig	2014-03-17 16:08:46.095050000 -0400
+++ webapps/docs/changelog.xml	2014-03-17 17:44:14.163385000 -0400
@@ -394,6 +394,11 @@
 <section name="Tomcat 7.0.40 (markt)" rtext="2013-05-09">
   <subsection name="Catalina">
     <changelog>
+      <fix>
+        Add support for limiting the size of chunk extensions when using chunked
+        encoding. (markt)
+        CVE-2013-4322 patch applied by Red Hat.
+      </fix>
       <update>
         Update Tomcat's internal copy of Commons FileUpload to FileUpload 1.3.
         (markt)
--- webapps/docs/config/http.xml.orig	2014-03-17 16:08:59.013101000 -0400
+++ webapps/docs/config/http.xml	2014-03-17 18:10:13.965639000 -0400
@@ -399,6 +399,12 @@
       and connections are not counted.</p>
     </attribute>
 
+    <attribute name="maxExtensionSize" required="false">
+      <p>Limits the total length of chunk extensions in chunked HTTP requests.
+      If the value is <code>-1</code>, no limit will be imposed. If not
+      specified, the default value of <code>8192</code> will be used.</p>
+    </attribute>
+
     <attribute name="maxHttpHeaderSize" required="false">
       <p>The maximum size of the request and response HTTP header, specified
       in bytes. If not specified, this attribute is set to 8192 (8 KB).</p>