--- java/org/apache/coyote/http11/filters/ChunkedInputFilter.java.orig 2014-06-16 18:41:33.642851000 -0400
+++ java/org/apache/coyote/http11/filters/ChunkedInputFilter.java 2014-06-16 19:36:36.796994000 -0400
@@ -29,6 +29,7 @@
import org.apache.tomcat.util.buf.HexUtils;
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,6 +40,9 @@
*/
public class ChunkedInputFilter implements InputFilter {
+ private static final StringManager sm = StringManager.getManager(
+ ChunkedInputFilter.class.getPackage().getName());
+
private static final org.apache.juli.logging.Log log
= org.apache.juli.logging.LogFactory.getLog(ChunkedInputFilter.class);
@@ -138,6 +142,11 @@
*/
private long extensionSize;
+ /**
+ * Flat that indicates an error has occured
+ */
+ private boolean error;
+
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
@@ -161,6 +170,8 @@
public int doRead(ByteChunk chunk, Request req)
throws IOException {
+ checkError();
+
if (endChunk)
return -1;
@@ -171,7 +182,8 @@
if (remaining <= 0) {
if (!parseChunkHeader()) {
- throw new IOException("Invalid chunk header");
+ throwIOException(sm.getString(
+ "chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
@@ -183,9 +195,9 @@
if (pos >= lastValid) {
if (readBytes() < 0) {
- throw new IOException(
- "Unexpected end of stream whilst reading request body");
+ throwIOException(sm.getString("chunkedInputFilter.eos"));
}
+
}
if (remaining > (lastValid - pos)) {
@@ -232,6 +244,8 @@
public long end()
throws IOException {
+ checkError();
+
// Consume extra bytes : parse the stream until the end chunk is found
while (doRead(readChunk, null) >= 0) {
// NOOP: Just consume the input
@@ -274,6 +288,7 @@
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
+ error = false;
}
@@ -286,6 +301,22 @@
return ENCODING;
}
+ private void throwIOException(String msg) throws IOException {
+ error = true;
+ throw new IOException(msg);
+ }
+
+ private void throwEOFException(String msg) throws IOException {
+ error = true;
+ throw new IOException(msg);
+ }
+
+ private void checkError() throws IOException {
+ if (error) {
+ throw new IOException(
+ sm.getString("chunkedInputFilter.error"));
+ }
+ }
// ------------------------------------------------------ Protected Methods
@@ -322,7 +353,7 @@
int result = 0;
boolean eol = false;
- boolean readDigit = false;
+ int readDigit = 0;
boolean extension = false;
while (!eol) {
@@ -346,10 +377,9 @@
} else if (!extension) {
//don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
- if (charValue != -1) {
- readDigit = true;
- result *= 16;
- result += charValue;
+ if (charValue != -1 && readDigit < 8) {
+ readDigit++;
+ result = (result << 4) | charValue;
} else {
//we shouldn't allow invalid, non hex characters
//in the chunked header
@@ -362,7 +392,8 @@
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
- throw new IOException("maxExtensionSize exceeded");
+ throwIOException(sm.getString(
+ "chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
@@ -371,7 +402,7 @@
}
}
- if (!readDigit)
+ if (readDigit == 0 || result < 0)
return false;
if (result == 0)
@@ -411,20 +442,27 @@
while (!eol) {
if (pos >= lastValid) {
- if (readBytes() <= 0)
- throw new IOException("Invalid CRLF");
+ if (readBytes() <= 0) {
+ throwIOException(sm.getString(
+ "chunkedInputFilter.invaldCrlfNoData"));
+ }
}
if (buf[pos] == Constants.CR) {
- if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+ if (crfound) {
+ throwIOException(sm.getString(
+ "chunkedInputFilter.invaldCrlfCRCR"));
+ }
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++;
@@ -453,8 +491,10 @@
// 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];
@@ -478,8 +518,10 @@
// 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];
@@ -530,7 +572,8 @@
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() -1;
if (trailingHeaders.getEnd() > newlimit) {
- throw new IOException("Exceeded maxTrailerSize");
+ throw new IOException(
+ sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
@@ -542,8 +585,11 @@
// 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];
@@ -567,8 +613,10 @@
// 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];
--- java/org/apache/coyote/http11/filters/LocalStrings.properties.orig 2014-06-16 18:41:33.647850000 -0400
+++ java/org/apache/coyote/http11/filters/LocalStrings.properties 2014-06-16 19:22:22.740111000 -0400
@@ -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
--- webapps/docs/changelog.xml.orig 2014-06-16 18:41:33.658857000 -0400
+++ webapps/docs/changelog.xml 2014-06-16 19:37:59.354278000 -0400
@@ -336,6 +336,12 @@
<subsection name="Coyote">
<changelog>
<fix>
+ Resolves: CVE-2014-0075. Improve processing of chunk size from
+ chunked headers. Avoid overflow and use a bit of shift instead
+ of multiplication as it is marginally faster. (mark/kkolinko)
+ Patch applied by Red Hat Jun 16 2014
+ </fix>
+ <fix>
<bug>54947</bug>: Fix the HTTP NIO connector that incorrectly rejected a
request if the CRLF terminating the request line was split across
multiple packets. Patch by Konstantin Preißer. (markt)