[tomcat7] 02/02: Fix CVE-2013-4322: Denial of service

Emmanuel Bourg ebourg-guest at moszumanska.debian.org
Fri Feb 28 22:39:55 UTC 2014


This is an automated email from the git hooks/post-receive script.

ebourg-guest pushed a commit to branch wheezy
in repository tomcat7.

commit b5930b1e6bf69c11442dac142b1b1dd519b6494a
Author: Emmanuel Bourg <ebourg at apache.org>
Date:   Fri Feb 28 23:37:10 2014 +0100

    Fix CVE-2013-4322: Denial of service
---
 debian/changelog                        |   7 +-
 debian/patches/0024-CVE-2013-4322.patch | 347 ++++++++++++++++++++++++++++++++
 debian/patches/series                   |   1 +
 3 files changed, 352 insertions(+), 3 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index b3a3655..2ccff36 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -11,9 +11,10 @@ tomcat7 (7.0.28-4+deb7u1) wheezy-security; urgency=high
   * Fix CVE-2013-2071: A runtime exception in AsyncListener.onComplete()
     prevents the request from being recycled. This may expose elements of a
     previous request to a current request.
-  * Fix CVE-2012-3544: When processing a request submitted using the chunked
-    transfer encoding, Tomcat ignored but did not limit any extensions that
-    were included. This allows a client to perform a limited denial of service
+  * Fix CVE-2012-3544 and CVE-2013-4322: When processing a request submitted
+    using the chunked transfer encoding, Tomcat ignored but did not limit any
+    extensions that were included. This allows a client to perform a limited
+    denial of service.
     by streaming an unlimited amount of data to the server.
   * Fix CVE-2013-4286: Reject requests with multiple content-length headers
     or with a content-length header when chunked encoding is being used.
diff --git a/debian/patches/0024-CVE-2013-4322.patch b/debian/patches/0024-CVE-2013-4322.patch
new file mode 100644
index 0000000..55252a2
--- /dev/null
+++ b/debian/patches/0024-CVE-2013-4322.patch
@@ -0,0 +1,347 @@
+Description: Fix for CVE-2013-4322: Add support for limiting the size of chunk
+ extensions when using chunked encoding
+Origin: backport from Tomcat 7.0.50, http://svn.apache.org/r1521864 and http://svn.apache.org/r1549523
+--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
++++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+@@ -681,13 +681,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.
+--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
++++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+@@ -153,6 +153,16 @@
+ 
+ 
+     /**
++     * 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
+      * behind a reverse proxy.
+--- a/java/org/apache/coyote/http11/Http11AprProcessor.java
++++ b/java/org/apache/coyote/http11/Http11AprProcessor.java
+@@ -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);
+     }
+ 
+ 
+--- a/java/org/apache/coyote/http11/Http11AprProtocol.java
++++ b/java/org/apache/coyote/http11/Http11AprProtocol.java
+@@ -258,7 +258,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());
+--- a/java/org/apache/coyote/http11/Http11NioProcessor.java
++++ b/java/org/apache/coyote/http11/Http11NioProcessor.java
+@@ -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);
+     }
+ 
+ 
+--- a/java/org/apache/coyote/http11/Http11NioProtocol.java
++++ b/java/org/apache/coyote/http11/Http11NioProtocol.java
+@@ -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());
+--- a/java/org/apache/coyote/http11/Http11Processor.java
++++ b/java/org/apache/coyote/http11/Http11Processor.java
+@@ -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);
+     }
+ 
+ 
+--- a/java/org/apache/coyote/http11/Http11Protocol.java
++++ b/java/org/apache/coyote/http11/Http11Protocol.java
+@@ -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());
+--- a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
++++ b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
+@@ -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.maxExtensionSize = maxExtensionSize;
++        this.maxTrailerSize = maxTrailerSize;
+     }
+ 
+     // ---------------------------------------------------- InputBuffer Methods
+@@ -247,6 +267,8 @@
+         endChunk = false;
+         needCRLFParse = false;
+         trailingHeaders.recycle();
++        trailingHeaders.setLimit(maxTrailerSize);
++        extensionSize = 0;
+     }
+ 
+ 
+@@ -294,7 +316,7 @@
+         int result = 0;
+         boolean eol = false;
+         boolean readDigit = false;
+-        boolean trailer = false;
++        boolean extension = false;
+ 
+         while (!eol) {
+ 
+@@ -306,9 +328,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
+                 if (HexUtils.getDec(buf[pos]) != -1) {
+                     readDigit = true;
+@@ -319,6 +345,14 @@
+                     //in the chunked header
+                     return false;
+                 }
++            } 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
+@@ -483,6 +517,13 @@
+                 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;
+                 }
+--- a/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
++++ b/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java
+@@ -38,6 +38,8 @@
+ 
+ public class TestChunkedInputFilter extends TomcatBaseTest {
+ 
++    private static final int EXT_SIZE_LIMIT = 10;
++
+     @Test
+     public void testTrailingHeaders() throws Exception {
+         // Setup Tomcat instance
+@@ -120,6 +122,76 @@
+         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
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -258,6 +258,10 @@
+         <bug>53373</bug>: Allow whitespace around delimiters in <Context>
+         aliases for readability. (schultz)
+       </fix>
++      <fix>
++        Add support for limiting the size of chunk extensions when using chunked
++        encoding. (markt)
++      </fix>
+     </changelog>
+   </subsection>
+   <subsection name="Coyote">
+--- a/webapps/docs/config/http.xml
++++ b/webapps/docs/config/http.xml
+@@ -398,6 +398,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>
diff --git a/debian/patches/series b/debian/patches/series
index 8c41b6a..8d52425 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -19,3 +19,4 @@ cve-2012-3439-tests.patch
 0021-CVE-2012-3544.patch
 0022-update-test-certificates.patch
 0023-CVE-2013-4286.patch
+0024-CVE-2013-4322.patch

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/tomcat7.git



More information about the pkg-java-commits mailing list