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>
signature.asc
Description: Digital signature