Source: tomcat6 Version: 6.0.35-6+deb7u1 Severity: important Tags: security patch upstream fixed-upstream Hi there, The following vulnerability affects current tomcat 6.x in squeeze and wheezy. According to CVE-2014-0227 [cve], "Apache Tomcat 6.x before 6.0.42, 7.x before 7.0.55, and 8.x before 8.0.9 does not properly handle attempts to continue reading data after an error has occurred, which allows remote attackers to conduct HTTP request smuggling attacks or cause a denial of service (resource consumption) by streaming data with malformed chunked transfer coding". I have prepared the attached patch, based on [fix]. If you fix the vulnerability please also make sure to include the CVE (Common Vulnerabilities & Exposures) id in your changelog entry. [cve] https://security-tracker.debian.org/tracker/CVE-2014-0227 [fix] https://svn.apache.org/viewvc?view=revision&revision=1603628 Please adjust the affected versions in the BTS as needed. Cheers, Santiago
Description: Improvements to ChunkedInputFilter
- Clean-up
- i18n for ChunkedInputFilter error message
- Add error flag to allow subsequent attempts at reading after an error to
fail fast
Fixes CVE-2014-0227
Origin: https://svn.apache.org/viewvc?view=revision&revision=1603628
Index: tomcat6-6.0.41/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
===================================================================
--- tomcat6-6.0.41.orig/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
+++ tomcat6-6.0.41/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.coyote.http11.filters;
import java.io.EOFException;
@@ -29,6 +28,7 @@ import org.apache.coyote.http11.Constant
import org.apache.coyote.http11.InputFilter;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.res.StringManager;
/**
* Chunked input filter. Parses chunked data according to
@@ -39,9 +39,11 @@ import org.apache.tomcat.util.http.MimeH
*/
public class ChunkedInputFilter implements InputFilter {
+ private static final StringManager sm = StringManager.getManager(
+ ChunkedInputFilter.class.getPackage().getName());
- // -------------------------------------------------------------- Constants
+ // -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
@@ -49,7 +51,6 @@ public class ChunkedInputFilter implemen
// ----------------------------------------------------- Static Initializer
-
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
}
@@ -57,7 +58,6 @@ public class ChunkedInputFilter implemen
// ----------------------------------------------------- Instance Variables
-
/**
* Next buffer in the pipeline.
*/
@@ -120,6 +120,11 @@ public class ChunkedInputFilter implemen
/**
+ * Flag that indicates if an error has occurred.
+ */
+ private boolean error;
+
+ /**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
@@ -130,13 +135,10 @@ public class ChunkedInputFilter implemen
* Request being parsed.
*/
private Request request;
-
- // ------------------------------------------------------------- Properties
// ---------------------------------------------------- InputBuffer Methods
-
/**
* Read bytes.
*
@@ -146,11 +148,12 @@ public class ChunkedInputFilter implemen
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
- public int doRead(ByteChunk chunk, Request req)
- throws IOException {
-
- if (endChunk)
+ public int doRead(ByteChunk chunk, Request req) throws IOException {
+ if (endChunk) {
return -1;
+ }
+
+ checkError();
if(needCRLFParse) {
needCRLFParse = false;
@@ -159,7 +162,7 @@ public class ChunkedInputFilter implemen
if (remaining <= 0) {
if (!parseChunkHeader()) {
- throw new IOException("Invalid chunk header");
+ throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
@@ -171,8 +174,7 @@ public class ChunkedInputFilter implemen
if (pos >= lastValid) {
if (readBytes() < 0) {
- throw new IOException(
- "Unexpected end of stream whilst reading request body");
+ throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
@@ -197,13 +199,11 @@ public class ChunkedInputFilter implemen
}
return result;
-
}
// ---------------------------------------------------- InputFilter Methods
-
/**
* Read the content length from the request.
*/
@@ -215,16 +215,13 @@ public class ChunkedInputFilter implemen
/**
* End the current request.
*/
- public long end()
- throws IOException {
-
+ public long end() throws IOException {
// Consume extra bytes : parse the stream until the end chunk is found
while (doRead(readChunk, null) >= 0) {
}
// Return the number of extra bytes which were consumed
- return (lastValid - pos);
-
+ return lastValid - pos;
}
@@ -232,7 +229,7 @@ public class ChunkedInputFilter implemen
* Amount of bytes still available in a buffer.
*/
public int available() {
- return (lastValid - pos);
+ return lastValid - pos;
}
@@ -258,6 +255,7 @@ public class ChunkedInputFilter implemen
trailingHeaders.setLimit(org.apache.coyote.Constants.MAX_TRAILER_SIZE);
}
extensionSize = 0;
+ error = false;
}
@@ -272,12 +270,10 @@ public class ChunkedInputFilter implemen
// ------------------------------------------------------ Protected Methods
-
/**
* Read bytes from the previous buffer.
*/
- protected int readBytes()
- throws IOException {
+ protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
@@ -285,7 +281,6 @@ public class ChunkedInputFilter implemen
buf = readChunk.getBytes();
return nRead;
-
}
@@ -298,8 +293,7 @@ public class ChunkedInputFilter implemen
* we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
* according to spec
*/
- protected boolean parseChunkHeader()
- throws IOException {
+ protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
@@ -340,7 +334,7 @@ public class ChunkedInputFilter implemen
extensionSize++;
if (org.apache.coyote.Constants.MAX_EXTENSION_SIZE > -1 &&
extensionSize > org.apache.coyote.Constants.MAX_EXTENSION_SIZE) {
- throw new IOException("maxExtensionSize exceeded");
+ throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
@@ -348,21 +342,22 @@ public class ChunkedInputFilter implemen
if (!eol) {
pos++;
}
-
}
- if (readDigit == 0 || result < 0)
+ if (readDigit == 0 || result < 0) {
return false;
+ }
- if (result == 0)
+ if (result == 0) {
endChunk = true;
+ }
remaining = result;
- if (remaining < 0)
+ if (remaining < 0) {
return false;
+ }
return true;
-
}
@@ -389,26 +384,27 @@ public class ChunkedInputFilter implemen
boolean crfound = false;
while (!eol) {
-
if (pos >= lastValid) {
- if (readBytes() <= 0)
- throw new IOException("Invalid CRLF");
+ if (readBytes() <= 0) {
+ throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
+ }
}
if (buf[pos] == Constants.CR) {
- if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ if (crfound) {
+ throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
+ }
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
- throw new IOException("Invalid CRLF, no CR character encountered.");
+ throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
- throw new IOException("Invalid CRLF");
+ throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
pos++;
-
}
}
@@ -417,7 +413,6 @@ public class ChunkedInputFilter implemen
* Parse end chunk data.
*/
protected boolean parseEndChunk() throws IOException {
-
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
@@ -434,8 +429,9 @@ public class ChunkedInputFilter implemen
// Read new bytes if needed
if (pos >= lastValid) {
- if (readBytes() <0)
- throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+ if (readBytes() <0) {
+ throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
+ }
}
chr = buf[pos];
@@ -459,8 +455,9 @@ public class ChunkedInputFilter implemen
// Read new bytes if needed
if (pos >= lastValid) {
- if (readBytes() <0)
- throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+ if (readBytes() <0) {
+ throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
+ }
}
chr = buf[pos];
@@ -500,8 +497,9 @@ public class ChunkedInputFilter implemen
// Read new bytes if needed
if (pos >= lastValid) {
- if (readBytes() <0)
- throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+ if (readBytes() <0) {
+ throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
+ }
}
chr = buf[pos];
@@ -512,7 +510,7 @@ public class ChunkedInputFilter implemen
if (trailingHeaders.getLimit() != -1) {
int newlimit = trailingHeaders.getLimit() -1;
if (trailingHeaders.getEnd() > newlimit) {
- throw new IOException("Exceeded maxTrailerSize");
+ throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
}
@@ -527,8 +525,9 @@ public class ChunkedInputFilter implemen
// Read new bytes if needed
if (pos >= lastValid) {
- if (readBytes() <0)
- throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+ if (readBytes() <0) {
+ throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
+ }
}
chr = buf[pos];
@@ -552,8 +551,9 @@ public class ChunkedInputFilter implemen
// Read new bytes if needed
if (pos >= lastValid) {
- if (readBytes() <0)
- throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+ if (readBytes() <0) {
+ throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
+ }
}
chr = buf[pos];
@@ -574,4 +574,23 @@ public class ChunkedInputFilter implemen
return true;
}
+
+
+ private void throwIOException(String msg) throws IOException {
+ error = true;
+ throw new IOException(msg);
+ }
+
+
+ private void throwEOFException(String msg) throws IOException {
+ error = true;
+ throw new EOFException(msg);
+ }
+
+
+ private void checkError() throws IOException {
+ if (error) {
+ throw new IOException(sm.getString("chunkedInputFilter.error"));
+ }
+ }
}
Index: tomcat6-6.0.41/java/org/apache/coyote/http11/filters/LocalStrings.properties
===================================================================
--- /dev/null
+++ tomcat6-6.0.41/java/org/apache/coyote/http11/filters/LocalStrings.properties
@@ -0,0 +1,25 @@
+# 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.
+
+chunkedInputFilter.error=No data available due to previous error
+chunkedInputFilter.eos=Unexpected end of stream while reading request body
+chunkedInputFilter.eosTrailer=Unexpected end of stream while reading trailer headers
+chunkedInputFilter.invalidCrlf=Invalid end of line sequence (character other than CR or LF found)
+chunkedInputFilter.invalidCrlfCRCR=Invalid end of line sequence (CRCR)
+chunkedInputFilter.invalidCrlfNoCR=Invalid end of line sequence (No CR before LF)
+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
Index: tomcat6-6.0.41/webapps/docs/changelog.xml
===================================================================
--- tomcat6-6.0.41.orig/webapps/docs/changelog.xml
+++ tomcat6-6.0.41/webapps/docs/changelog.xml
@@ -272,6 +272,15 @@
</fix>
</changelog>
</subsection>
+ <subsection name="Coyote">
+ <changelog>
+ <fix>
+ Various improvements to ChunkedInputFilter including clean-up, i18n for
+ error messages and adding an error flag to allow subsequent attempts at
+ reading after an error to fail fast. (markt)
+ </fix>
+ </changelog>
+ </subsection>
<subsection name="Jasper">
<changelog>
<fix>
Attachment:
signature.asc
Description: Digital signature