[tomcat7] 06/06: Fixed CVE-2014-0230: Add a new limit for the amount of data Tomcat will swallow for an aborted upload
Emmanuel Bourg
ebourg-guest at moszumanska.debian.org
Mon Jan 11 10:16:30 UTC 2016
This is an automated email from the git hooks/post-receive script.
ebourg-guest pushed a commit to branch wheezy
in repository tomcat7.
commit 8e55a425b5f15895cfcd6e48589bc9f40078eaf4
Author: Emmanuel Bourg <ebourg at apache.org>
Date: Fri Jan 8 12:55:06 2016 +0100
Fixed CVE-2014-0230: Add a new limit for the amount of data Tomcat will swallow for an aborted upload
---
debian/changelog | 4 +
debian/patches/CVE-2014-0230.patch | 406 +++++++++++++++++++++++++++++++++++++
debian/patches/series | 1 +
3 files changed, 411 insertions(+)
diff --git a/debian/changelog b/debian/changelog
index 061daa3..4263c4e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -17,6 +17,10 @@ tomcat7 (7.0.28-4+deb7u3) wheezy-security; urgency=high
subsequent attempts at reading after an error to fail fast. This prevents
remote attackers from conducting HTTP request smuggling attacks or causing
a denial of service by streaming data with malformed chunked requests.
+ * Fixed CVE-2014-0230: Add a new limit for the amount of data Tomcat will
+ swallow for an aborted upload. This prevents remote attackers from causing
+ a denial of service (thread consumption) via a series of aborted upload
+ attempts.
-- Emmanuel Bourg <ebourg at apache.org> Mon, 04 Jan 2016 12:03:34 +0100
diff --git a/debian/patches/CVE-2014-0230.patch b/debian/patches/CVE-2014-0230.patch
new file mode 100644
index 0000000..7eb135b
--- /dev/null
+++ b/debian/patches/CVE-2014-0230.patch
@@ -0,0 +1,406 @@
+Description: CVE-2014-0230: Add a new limit, defaulting to 2MB and configurable via JMX,
+ for the amount of data Tomcat will swallow for an aborted upload. This prevents remote
+ attackers from causing a denial of service (thread consumption) via a series of aborted
+ upload attempts.
+Origin: backport, https://svn.apache.org/r1603781
+ https://svn.apache.org/r1603811
+ https://svn.apache.org/r1609176
+ https://svn.apache.org/r1659295
+--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
++++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+@@ -681,14 +681,15 @@
+ /**
+ * Initialize standard input and output filters.
+ */
+- protected void initializeFilters(int maxTrailerSize, int maxExtensionSize) {
++ protected void initializeFilters(int maxTrailerSize, int maxExtensionSize,
++ int maxSwallowSize) {
+ // Create and add the identity filters.
+- getInputBuffer().addFilter(new IdentityInputFilter());
++ getInputBuffer().addFilter(new IdentityInputFilter(maxSwallowSize));
+ getOutputBuffer().addFilter(new IdentityOutputFilter());
+
+ // Create and add the chunked filters.
+ getInputBuffer().addFilter(
+- new ChunkedInputFilter(maxTrailerSize, maxExtensionSize));
++ new ChunkedInputFilter(maxTrailerSize, maxExtensionSize, maxSwallowSize));
+ 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
+@@ -163,6 +163,16 @@
+
+
+ /**
++ * Maximum amount of request body to swallow.
++ */
++ private int maxSwallowSize = 2 * 1024 * 1024;
++ public int getMaxSwallowSize() { return maxSwallowSize; }
++ public void setMaxSwallowSize(int maxSwallowSize) {
++ this.maxSwallowSize = maxSwallowSize;
++ }
++
++
++ /**
+ * 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 maxExtensionSize) {
++ int maxTrailerSize, int maxExtensionSize, int maxSwallowSize) {
+
+ super(endpoint);
+
+@@ -68,7 +68,7 @@
+ outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
+ response.setOutputBuffer(outputBuffer);
+
+- initializeFilters(maxTrailerSize, maxExtensionSize);
++ initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
+ }
+
+
+--- a/java/org/apache/coyote/http11/Http11AprProtocol.java
++++ b/java/org/apache/coyote/http11/Http11AprProtocol.java
+@@ -258,7 +258,8 @@
+ protected Http11AprProcessor createProcessor() {
+ Http11AprProcessor processor = new Http11AprProcessor(
+ proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,
+- proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
++ proto.getMaxTrailerSize(), proto.getMaxExtensionSize(),
++ proto.getMaxSwallowSize());
+ 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 maxExtensionSize) {
++ int maxTrailerSize, int maxExtensionSize, int maxSwallowSize) {
+
+ super(endpoint);
+
+@@ -73,7 +73,7 @@
+ outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
+ response.setOutputBuffer(outputBuffer);
+
+- initializeFilters(maxTrailerSize, maxExtensionSize);
++ initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
+ }
+
+
+--- a/java/org/apache/coyote/http11/Http11NioProtocol.java
++++ b/java/org/apache/coyote/http11/Http11NioProtocol.java
+@@ -260,7 +260,8 @@
+ public Http11NioProcessor createProcessor() {
+ Http11NioProcessor processor = new Http11NioProcessor(
+ proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.endpoint,
+- proto.getMaxTrailerSize(), proto.getMaxExtensionSize());
++ proto.getMaxTrailerSize(), proto.getMaxExtensionSize(),
++ proto.getMaxSwallowSize());
+ 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 maxExtensionSize) {
++ int maxTrailerSize, int maxExtensionSize, int maxSwallowSize) {
+
+ super(endpoint);
+
+@@ -60,7 +60,7 @@
+ outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
+ response.setOutputBuffer(outputBuffer);
+
+- initializeFilters(maxTrailerSize, maxExtensionSize);
++ initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
+ }
+
+
+--- a/java/org/apache/coyote/http11/Http11Protocol.java
++++ b/java/org/apache/coyote/http11/Http11Protocol.java
+@@ -164,7 +164,8 @@
+ protected Http11Processor createProcessor() {
+ Http11Processor processor = new Http11Processor(
+ proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
+- proto.getMaxTrailerSize(),proto.getMaxExtensionSize());
++ proto.getMaxTrailerSize(),proto.getMaxExtensionSize(),
++ proto.getMaxSwallowSize());
+ 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
+@@ -138,6 +138,9 @@
+ */
+ private long extensionSize;
+
++ private final int maxSwallowSize;
++
++
+ /**
+ * Flag that indicates if an error has occurred.
+ */
+@@ -145,10 +148,11 @@
+
+
+ // ----------------------------------------------------------- Constructors
+- public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
++ public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize, int maxSwallowSize) {
+ this.trailingHeaders.setLimit(maxTrailerSize);
+ this.maxExtensionSize = maxExtensionSize;
+ this.maxTrailerSize = maxTrailerSize;
++ this.maxSwallowSize = maxSwallowSize;
+ }
+
+ // ---------------------------------------------------- InputBuffer Methods
+@@ -239,9 +243,14 @@
+ public long end()
+ throws IOException {
+
++ long swallowed = 0;
++ int read = 0;
+ // Consume extra bytes : parse the stream until the end chunk is found
+- while (doRead(readChunk, null) >= 0) {
+- // NOOP: Just consume the input
++ while ((read = doRead(readChunk, null)) >= 0) {
++ swallowed += read;
++ if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
++ throwIOException(sm.getString("inputFilter.maxSwallow"));
++ }
+ }
+
+ // Return the number of extra bytes which were consumed
+--- a/java/org/apache/coyote/http11/filters/IdentityInputFilter.java
++++ b/java/org/apache/coyote/http11/filters/IdentityInputFilter.java
+@@ -24,6 +24,7 @@
+ import org.apache.coyote.Request;
+ import org.apache.coyote.http11.InputFilter;
+ import org.apache.tomcat.util.buf.ByteChunk;
++import org.apache.tomcat.util.res.StringManager;
+
+ /**
+ * Identity input filter.
+@@ -32,6 +33,9 @@
+ */
+ public class IdentityInputFilter implements InputFilter {
+
++ private static final StringManager sm = StringManager.getManager(
++ IdentityInputFilter.class.getPackage().getName());
++
+
+ // -------------------------------------------------------------- Constants
+
+@@ -75,6 +79,7 @@
+ */
+ protected ByteChunk endChunk = new ByteChunk();
+
++ private final int maxSwallowSize;
+
+ // ------------------------------------------------------------- Properties
+
+@@ -95,6 +100,13 @@
+ }
+
+
++ // ------------------------------------------------------------ Constructor
++
++ public IdentityInputFilter(int maxSwallowSize) {
++ this.maxSwallowSize = maxSwallowSize;
++ }
++
++
+ // ---------------------------------------------------- InputBuffer Methods
+
+
+@@ -160,11 +172,21 @@
+ public long end()
+ throws IOException {
+
++ final boolean maxSwallowSizeExceeded = (maxSwallowSize > -1 && remaining > maxSwallowSize);
++ long swallowed = 0;
++
+ // Consume extra bytes.
+ while (remaining > 0) {
+ int nread = buffer.doRead(endChunk, null);
+ if (nread > 0 ) {
++ swallowed += nread;
+ remaining = remaining - nread;
++ if (maxSwallowSizeExceeded && swallowed > maxSwallowSize) {
++ // Note: We do not fail early so the client has a chance to
++ // read the response before the connection is closed. See:
++ // http://httpd.apache.org/docs/2.0/misc/fin_wait_2.html#appendix
++ throw new IOException(sm.getString("inputFilter.maxSwallow"));
++ }
+ } else { // errors are handled higher up.
+ remaining = 0;
+ }
+--- a/java/org/apache/coyote/http11/filters/LocalStrings.properties
++++ b/java/org/apache/coyote/http11/filters/LocalStrings.properties
+@@ -22,4 +22,6 @@
+ chunkedInputFilter.invalidCrlfNoData=Invalid end of line sequence (no data available to read)
+ chunkedInputFilter.invalidHeader=Invalid chunk header
+ chunkedInputFilter.maxExtension=maxExtensionSize exceeded
+-chunkedInputFilter.maxTrailer=maxTrailerSize exceeded
+\ No newline at end of file
++chunkedInputFilter.maxTrailer=maxTrailerSize exceeded
++
++inputFilter.maxSwallow=maxSwallowSize exceeded
+\ No newline at end of file
+--- a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
++++ b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
+@@ -16,8 +16,13 @@
+ */
+ package org.apache.catalina.core;
+
++import java.io.BufferedReader;
+ import java.io.IOException;
++import java.io.InputStreamReader;
++import java.io.OutputStreamWriter;
+ import java.io.PrintWriter;
++import java.io.Writer;
++import java.net.Socket;
+ import java.util.Arrays;
+ import java.util.Collection;
+
+@@ -32,6 +37,7 @@
+ import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertTrue;
+
++import org.junit.Assert;
+ import org.junit.Test;
+
+ import org.apache.catalina.Context;
+@@ -113,7 +119,7 @@
+ Exception ex = doAbortedUploadTest(client, true, true);
+ assertNull("Limited upload with swallow enabled generates client exception",
+ ex);
+- assertTrue("Limited upload with swallow enabled returns error status code",
++ assertTrue("Limited upload with swallow enabled returns non-500 status code",
+ client.isResponse500());
+ client.reset();
+ }
+@@ -410,4 +416,78 @@
+ }
+ }
+
++
++ @Test
++ public void testChunkedPUTLimit() throws Exception {
++ doTestChunkedPUT(true);
++ }
++
++
++ @Test
++ public void testChunkedPUTNoLimit() throws Exception {
++ doTestChunkedPUT(false);
++ }
++
++
++ public void doTestChunkedPUT(boolean limit) throws Exception {
++
++ Tomcat tomcat = getTomcatInstance();
++ tomcat.addContext("", TEMP_DIR);
++ // No need for target to exist.
++
++ if (!limit) {
++ tomcat.getConnector().setAttribute("maxSwallowSize", "-1");
++ }
++
++ tomcat.start();
++
++ Exception writeEx = null;
++ Exception readEx = null;
++ String responseLine = null;
++ Socket conn = null;
++
++ try {
++ conn = new Socket("localhost", getPort());
++ Writer writer = new OutputStreamWriter(
++ conn.getOutputStream(), "US-ASCII");
++ writer.write("PUT /does-not-exist HTTP/1.1\r\n");
++ writer.write("Host: any\r\n");
++ writer.write("Transfer-encoding: chunked\r\n");
++ writer.write("\r\n");
++
++ // Smarter than the typical client. Attempts to read the response
++ // even if the request is not fully written.
++ try {
++ // Write (or try to write) 16MB
++ for (int i = 0; i < 1024 * 1024; i++) {
++ writer.write("10\r\n");
++ writer.write("0123456789ABCDEF\r\n");
++ }
++ } catch (Exception e) {
++ writeEx = e;
++ }
++
++ try {
++ BufferedReader reader = new BufferedReader(new InputStreamReader(
++ conn.getInputStream(), "US-ASCII"));
++
++ responseLine = reader.readLine();
++ } catch (IOException e) {
++ readEx = e;
++ }
++ } finally {
++ if (conn != null) {
++ conn.close();
++ }
++ }
++
++ if (limit) {
++ Assert.assertNotNull(writeEx);
++ } else {
++ Assert.assertNull(writeEx);
++ Assert.assertNull(readEx);
++ Assert.assertNotNull(responseLine);
++ Assert.assertTrue(responseLine.contains("404"));
++ }
++ }
+ }
+--- a/webapps/docs/config/http.xml
++++ b/webapps/docs/config/http.xml
+@@ -418,6 +418,16 @@
+ If not specified, this attribute is set to 100.</p>
+ </attribute>
+
++ <attribute name="maxSwallowSize" required="false">
++ <p>The maximum number of request body bytes (excluding transfer encoding
++ overhead) that will be swallowed by Tomcat for an aborted upload. An
++ aborted upload is when Tomcat knows that the request body is going to be
++ ignored but the client still sends it. If Tomcat does not swallow the body
++ the client is unlikely to see the response. If not specified the default
++ of 2097152 (2 megabytes) will be used. A value of less than zero indicates
++ that no limit should be enforced.</p>
++ </attribute>
++
+ <attribute name="maxThreads" required="false">
+ <p>The maximum number of request processing threads to be created
+ by this <strong>Connector</strong>, which therefore determines the
+--- a/java/org/apache/catalina/connector/mbeans-descriptors.xml
++++ b/java/org/apache/catalina/connector/mbeans-descriptors.xml
+@@ -97,6 +97,10 @@
+ description="Maximum size of a POST which will be saved by the container during authentication"
+ type="int"/>
+
++ <attribute name="maxSwallowSize"
++ description="The maximum number of request body bytes to be swallowed by Tomcat for an aborted upload"
++ type="int"/>
++
+ <!-- Common -->
+ <attribute name="maxThreads"
+ description="The maximum number of request processing threads to be created"
diff --git a/debian/patches/series b/debian/patches/series
index 9a2673f..16f918d 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -26,3 +26,4 @@ CVE-2014-0099.patch
CVE-2013-4444.patch
CVE-2014-0075.patch
CVE-2014-0227.patch
+CVE-2014-0230.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